UNPKG

rc-new-window-mod

Version:

popup new browser window with react

234 lines (233 loc) 8.21 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const react_1 = __importDefault(require("react")); const react_dom_1 = __importDefault(require("react-dom")); const debounce_1 = __importDefault(require("lodash/debounce")); const BrowserPopupWindow_1 = require("./BrowserPopupWindow"); const onNewWindowResize = (0, debounce_1.default)(() => { // add/remove element on main document, force it to dispatch resize observer event on the popup window let div = document.createElement('div'); document.body.append(div); div.remove(); // TODO update resize event }, 5000); /** * The NewWindow class object. * @public */ class NewWindow extends react_1.default.PureComponent { /** * The NewWindow function constructor. * @param {Object} props */ constructor(props) { super(props); this.released = false; this.container = document.createElement('div'); this.state = { mounted: false }; this.onMainWindowUnload = () => { if (this.window) { this.window.close(); } }; /** * Release the new window and anything that was bound to it. */ this.release = (event) => { // This method can be called once. if (this.released) { return; } this.released = true; if (this.windowCheckerInterval) { clearInterval(this.windowCheckerInterval); this.windowCheckerInterval = null; } window.removeEventListener('beforeunload', this.onMainWindowUnload); this.window.removeEventListener('beforeunload', this.release); if (event) { // Call any function bound to the `onUnload` prop. const { onClose } = this.props; if (typeof onClose === 'function') { onClose(); } } }; } /** * Render the NewWindow component. */ render() { if (!this.state.mounted) return null; return react_dom_1.default.createPortal(this.props.children, this.container); } componentDidMount() { this.openChild(); this.setState({ mounted: true }); } /** * Create the new window when NewWindow component mount. */ openChild() { const { url, title, name, width, height, initPopupInnerRect, initPopupOuterRect, onBlock, onOpen, onClose, } = this.props; let features = { width, height }; if (initPopupOuterRect) { features = initPopupOuterRect(); const [topBorder, sideBorder, bottomBorder] = BrowserPopupWindow_1.popupWindowBorder; if (!BrowserPopupWindow_1.isSafari) { features.width -= sideBorder * 2; features.height -= topBorder + bottomBorder; } } else if (initPopupInnerRect) { features = initPopupInnerRect(); const [topBorder, sideBorder] = BrowserPopupWindow_1.popupWindowBorder; features.left -= sideBorder; features.top -= topBorder; if (BrowserPopupWindow_1.isSafari) { features.height += topBorder; } } else { features.left = window.top.outerWidth / 2 + window.top.screenX - width / 2; features.top = window.top.outerHeight / 2 + window.top.screenY - height / 2; } // Open a new window. this.window = window.open(url, name, toWindowFeatures(features)); // Check if the new window was successfully opened. if (this.window) { window.addEventListener('beforeunload', this.onMainWindowUnload); this.window.addEventListener('resize', onNewWindowResize); this.window.document.title = title || document.title; this.window.document.body.appendChild(this.container); // If specified, copy styles from parent window's document. if (this.props.copyStyles) { setTimeout(() => copyStyles(document, this.window.document), 0); } if (typeof onOpen === 'function') { onOpen(this.window); } if (url && onClose) { this.windowCheckerInterval = setInterval(() => { if (!this.window || this.window.closed) { this.release(true); } }, 50); } // Release anything bound to this component before the new window unload. this.window.addEventListener('beforeunload', this.release); } else { // Handle error on opening of new window. if (typeof onBlock === 'function') { onBlock(); } else { console.warn('A new window could not be opened. Maybe it was blocked.'); } } } /** * Close the opened window (if any) when NewWindow will unmount. */ componentWillUnmount() { if (this.window) { this.release(); this.window.close(); } } } NewWindow.supported = BrowserPopupWindow_1.popupSupported; /** * NewWindow default props. */ NewWindow.defaultProps = { url: '', name: '', width: 640, height: 480, copyStyles: true, }; /** * Utility functions. * @private */ /** * Copy styles from a source document to a target. * @param {Object} source * @param {Object} target * @private */ function copyStyles(source, target) { Array.from(source.styleSheets).forEach(styleSheet => { // For <style> elements let rules; if (styleSheet.href) { // for <link> elements loading CSS from a URL const newLinkEl = source.createElement('link'); newLinkEl.rel = 'stylesheet'; newLinkEl.href = styleSheet.href; target.head.appendChild(newLinkEl); } else { try { rules = styleSheet.cssRules; } catch (err) { // can't access crossdomain rules } if (rules) { const newStyleEl = source.createElement('style'); // Write the text of each rule into the body of the style element Array.from(styleSheet.cssRules).forEach(cssRule => { const { cssText, type } = cssRule; let returnText = cssText; // Check if the cssRule type is CSSImportRule (3) or CSSFontFaceRule (5) to handle local imports on a about:blank page // '/custom.css' turns to 'http://my-site.com/custom.css' if ([3, 5].includes(type)) { returnText = cssText .split('url(') .map(line => { if (line[1] === '/') { return `${line.slice(0, 1)}${window.location.origin}${line.slice(1)}`; } return line; }) .join('url('); } newStyleEl.appendChild(source.createTextNode(returnText)); }); target.head.appendChild(newStyleEl); } } }); } /** * Convert features props to window features format (name=value,other=value). * @param {Object} obj * @return {String} * @private */ function toWindowFeatures(obj) { return Object.keys(obj) .reduce((features, name) => { const value = obj[name]; if (typeof value === 'boolean') { features.push(`${name}=${value ? 'yes' : 'no'}`); } else { features.push(`${name}=${value}`); } return features; }, []) .join(','); } /** * Component export. * @private */ exports.default = NewWindow;