rc-new-window-mod
Version:
popup new browser window with react
234 lines (233 loc) • 8.21 kB
JavaScript
"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;