Skip to content

Commit

Permalink
Merge pull request #65 from JakeGinnivan/typescript
Browse files Browse the repository at this point in the history
Add support for React 19 + convert to typescript
  • Loading branch information
marchaos authored Dec 31, 2024
2 parents 8f54b82 + 93a6eeb commit f2f457a
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 102 deletions.
32 changes: 0 additions & 32 deletions _webpack.dist.config.js

This file was deleted.

2 changes: 1 addition & 1 deletion demo/demo.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import Popout from '../lib/react-popout.jsx';
import Popout from '../lib/react-popout';

export default class Example extends React.Component {
constructor(props) {
Expand Down
2 changes: 1 addition & 1 deletion demo/demo2.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import Popout from '../lib/react-popout.jsx';
import Popout from '../lib/react-popout';

export default class Example2 extends React.Component {
constructor(props) {
Expand Down
2 changes: 1 addition & 1 deletion demo/demo3.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import Popout from '../lib/react-popout.jsx';
import Popout from '../lib/react-popout';

export default class Example3 extends React.Component {
constructor(props) {
Expand Down
14 changes: 0 additions & 14 deletions jsconfig.json

This file was deleted.

91 changes: 47 additions & 44 deletions lib/react-popout.jsx → lib/react-popout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import PropTypes from 'prop-types';
import React, { PropsWithChildren } from 'react';
import { createRoot, Root } from 'react-dom/client';

const DEFAULT_OPTIONS = {
toolbar: 'no',
Expand All @@ -12,44 +11,47 @@ const DEFAULT_OPTIONS = {
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
top: (o: any, w: any) => (w.innerHeight - o.height) / 2 + w.screenY,
left: (o: any, w: any) => (w.innerWidth - o.width) / 2 + w.screenX,
};

const ABOUT_BLANK = 'about:blank';

interface PopoutWindowProps extends PropsWithChildren<any> {
options: object;
url: string;
containerId: string;
containerClassName?: string;
onError: () => void;
window?: Window;
title?: string;
}

interface PopoutWindowState {
popoutWindow: Window | null;
container: HTMLDivElement | null;
openedWindowComponent: React.Component | null;
}

/**
* @class PopoutWindow
*/
export default class PopoutWindow extends React.Component {
export default class PopoutWindow extends React.Component<PopoutWindowProps, PopoutWindowState> {
private interval!: number;
private root!: Root;

static defaultProps = {
url: ABOUT_BLANK,
containerId: 'popout-content-container',
containerClassName: '',
onError: () => {}
};

/**
*
* @type {{title: *, url: *, onClosing: *, options: *, window: *, containerId: *}}
*/
static propTypes = {
title: PropTypes.string.isRequired,
url: PropTypes.string,
onClosing: PropTypes.func,
options: PropTypes.object,
window: PropTypes.object,
containerId: PropTypes.string,
containerClassName: PropTypes.string,
children: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
onError: PropTypes.func
onError: () => {},
};

/**
* @constructs PopoutWindow
* @param props
*/
constructor(props) {
constructor(props: PopoutWindowProps) {
super(props);

this.mainWindowClosed = this.mainWindowClosed.bind(this);
Expand All @@ -59,21 +61,24 @@ export default class PopoutWindow extends React.Component {
this.state = {
openedWindowComponent: null,
popoutWindow: null,
container: null
container: null,
};
}

createOptions(ownerWindow) {
createOptions(ownerWindow: Window) {
const mergedOptions = Object.assign({}, DEFAULT_OPTIONS, this.props.options);

return Object.keys(mergedOptions)
.map(
key =>
(key) =>
key +
'=' +
// @ts-ignore
(typeof mergedOptions[key] === 'function'
? mergedOptions[key].call(this, mergedOptions, ownerWindow)
: mergedOptions[key])
? // @ts-ignore
mergedOptions[key].call(this, mergedOptions, ownerWindow)
: // @ts-ignore
mergedOptions[key]),
)
.join(',');
}
Expand All @@ -90,44 +95,45 @@ export default class PopoutWindow extends React.Component {
}
}

componentWillReceiveProps(newProps) {
componentWillReceiveProps(newProps: PopoutWindowProps) {
if (newProps.title !== this.props.title && this.state.popoutWindow) {
this.state.popoutWindow.document.title = newProps.title;
this.state.popoutWindow.document.title = newProps.title!;
}
}

componentDidUpdate() {
this.renderToContainer(this.state.container, this.state.popoutWindow, this.props.children);
this.renderToContainer(this.state.container!, this.state.popoutWindow!, this.props.children);
}

componentWillUnmount() {
this.mainWindowClosed();
}

popoutWindowLoaded(popoutWindow) {
popoutWindowLoaded(popoutWindow: Window) {
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;
popoutWindow.document.title = this.props.title!;
let container = popoutWindow.document.createElement('div');
container.id = this.props.containerId;
container.className = this.props.containerClassName;
container.className = this.props.containerClassName!;
popoutWindow.document.body.appendChild(container);

this.setState({ container });
this.renderToContainer(container, popoutWindow, this.props.children);
}
}

openPopoutWindow(ownerWindow) {
openPopoutWindow(ownerWindow: Window) {
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);

Expand All @@ -153,8 +159,8 @@ export default class PopoutWindow extends React.Component {
*
* @param popoutWindow
*/
checkForPopoutWindowClosure(popoutWindow) {
this.interval = setInterval(() => {
checkForPopoutWindowClosure(popoutWindow: Window) {
this.interval = window.setInterval(() => {
if (popoutWindow.closed) {
clearInterval(this.interval);
this.props.onClosing && this.props.onClosing(popoutWindow);
Expand All @@ -170,18 +176,15 @@ export default class PopoutWindow extends React.Component {
popoutWindowUnloading() {
if (this.state.container) {
clearInterval(this.interval);
this.root.unmount(this.state.container);
this.root.unmount();
this.props.onClosing && this.props.onClosing(this.state.popoutWindow);
}
}

renderToContainer(container, popoutWindow, children) {
renderToContainer(container: HTMLDivElement, popoutWindow: Window, children: React.ReactNode | ((window: Window) => React.ReactNode)) {
// For SSR we might get updated but there will be no container.
if (container) {
let renderedComponent = children;
if (typeof children === 'function') {
renderedComponent = children(popoutWindow);
}
const renderedComponent = typeof children === 'function' ? children(popoutWindow) : children;

if (!this.root) {
this.root = createRoot(container);
Expand Down
20 changes: 13 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"name": "react-popout",
"version": "3.0.4",
"version": "4.0.0",
"description": "Wraps window.open in a react component, allowing the contents to be part of your react render tree",
"main": "dist/react-popout.min.js",
"types": "dist/react-popout.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/JakeGinnivan/react-popout"
Expand All @@ -19,7 +20,9 @@
"scripts": {
"prebuild:demo": "npm run build:dist",
"demo": "webpack-dev-server --progress --colors --hot -d --port 8880 --config _webpack.demo.config.js",
"build:dist": "webpack --config _webpack.dist.config.js --optimize-minimize",
"build:dist": "npm run build:compile && npm run build:minify",
"build:compile": "tsc",
"build:minify": "esbuild dist/react-popout.js --minify --outfile=dist/react-popout.min.js",
"build:demo": "webpack --config _webpack.demo.config.js --optimize-minimize",
"test": "mocha \"test/**/*spec.js*\" --compilers js:babel-register --reporter dot",
"prepublish": "npm run build:dist"
Expand All @@ -33,7 +36,10 @@
"babel-loader": "^7.1.2",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"esbuild": "^0.24.2",
"babel-preset-stage-0": "^6.5.0",
"@types/react": "^19.0.2",
"@types/react-dom": "^19.0.2",
"babel-runtime": "^6.26.0",
"eslint-config-airbnb": "latest",
"eslint-config-prettier": "latest",
Expand All @@ -43,14 +49,14 @@
"eslint-plugin-react": "latest",
"mocha": "^3.5.3",
"node-libs-browser": "^2.0.0",
"prettier": "latest",
"prettier": "^3.4.2",
"webpack": "^3.6.0",
"webpack-dev-server": "^2.8.2"
"webpack-dev-server": "^2.8.2",
"typescript": "^5.7.2"
},
"dependencies": {
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"peerDependencies": {
"react": "^18.2.0",
Expand Down
2 changes: 0 additions & 2 deletions tests.webpack.js

This file was deleted.

22 changes: 22 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"declaration": true,
"outDir": "dist/",
"lib": [
"es2020", "dom"
],
"jsx": "react",
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"alwaysStrict": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
},
"include": ["lib"],
"exclude": [
"node_modules"
]
}

0 comments on commit f2f457a

Please sign in to comment.