UNPKG

react-popout

Version:

Wraps window.open in a react component, allowing the contents to be part of your react render tree

159 lines (158 loc) 5.93 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 client_1 = require("react-dom/client"); const DEFAULT_OPTIONS = { toolbar: 'no', location: 'no', directories: 'no', status: 'no', menubar: 'no', scrollbars: 'yes', resizable: 'yes', width: 500, height: 400, top: (o, w) => (w.innerHeight - o.height) / 2 + w.screenY, left: (o, w) => (w.innerWidth - o.width) / 2 + w.screenX, }; const ABOUT_BLANK = 'about:blank'; /** * @class PopoutWindow */ class PopoutWindow extends react_1.default.Component { /** * @constructs PopoutWindow * @param props */ constructor(props) { super(props); this.mainWindowClosed = this.mainWindowClosed.bind(this); this.popoutWindowUnloading = this.popoutWindowUnloading.bind(this); this.popoutWindowLoaded = this.popoutWindowLoaded.bind(this); this.state = { openedWindowComponent: null, popoutWindow: null, container: null, }; } createOptions(ownerWindow) { const mergedOptions = Object.assign({}, DEFAULT_OPTIONS, this.props.options); return Object.keys(mergedOptions) .map((key) => key + '=' + // @ts-ignore (typeof mergedOptions[key] === 'function' ? // @ts-ignore mergedOptions[key].call(this, mergedOptions, ownerWindow) : // @ts-ignore mergedOptions[key])) .join(','); } componentDidMount() { const ownerWindow = this.props.window || window; // May not exist if server-side rendering if (ownerWindow) { this.openPopoutWindow(ownerWindow); // Close any open popouts when page unloads/refreshes ownerWindow.addEventListener('unload', this.mainWindowClosed); } } componentWillReceiveProps(newProps) { if (newProps.title !== this.props.title && this.state.popoutWindow) { this.state.popoutWindow.document.title = newProps.title; } } componentDidUpdate() { this.renderToContainer(this.state.container, this.state.popoutWindow, this.props.children); } componentWillUnmount() { this.mainWindowClosed(); } popoutWindowLoaded(popoutWindow) { if (!this.state.container) { // Popout window is passed from openPopoutWindow if no url is specified. // In this case this.state.popoutWindow will not yet be set, so use the argument. popoutWindow = this.state.popoutWindow || popoutWindow; popoutWindow.document.title = this.props.title; let container = popoutWindow.document.createElement('div'); container.id = this.props.containerId; container.className = this.props.containerClassName; popoutWindow.document.body.appendChild(container); this.setState({ container }); this.renderToContainer(container, popoutWindow, this.props.children); } } openPopoutWindow(ownerWindow) { const popoutWindow = ownerWindow.open(this.props.url, this.props.name || this.props.title, this.createOptions(ownerWindow)); if (!popoutWindow) { this.props.onError(); return; } this.setState({ popoutWindow }); // @ts-ignore popoutWindow.addEventListener('load', this.popoutWindowLoaded); popoutWindow.addEventListener('unload', this.popoutWindowUnloading); if (this.props.url === ABOUT_BLANK) { // If they have no specified a URL, then we need to forcefully call popoutWindowLoaded() popoutWindow.document.readyState === 'complete' && this.popoutWindowLoaded(popoutWindow); } else { // If they have a specified URL, then we need to check if the window closes without a listener this.checkForPopoutWindowClosure(popoutWindow); } } /** * API method to close the window. */ closeWindow() { this.mainWindowClosed(); } /** * Use if a URL was passed to the popout window. Checks every 500ms if the window has been closed. * Calls the onClosing() prop if the window is closed. * * @param popoutWindow */ checkForPopoutWindowClosure(popoutWindow) { this.interval = window.setInterval(() => { if (popoutWindow.closed) { clearInterval(this.interval); this.props.onClosing && this.props.onClosing(popoutWindow); } }, 500); } mainWindowClosed() { this.state.popoutWindow && this.state.popoutWindow.close(); (this.props.window || window).removeEventListener('unload', this.mainWindowClosed); } popoutWindowUnloading() { if (this.state.container) { clearInterval(this.interval); this.root.unmount(); this.props.onClosing && this.props.onClosing(this.state.popoutWindow); } } renderToContainer(container, popoutWindow, children) { // For SSR we might get updated but there will be no container. if (container) { const renderedComponent = typeof children === 'function' ? children(popoutWindow) : children; if (!this.root) { this.root = (0, client_1.createRoot)(container); } this.root.render(renderedComponent); } } render() { return null; } } PopoutWindow.defaultProps = { url: ABOUT_BLANK, containerId: 'popout-content-container', containerClassName: '', onError: () => { }, }; exports.default = PopoutWindow;