Skip to content

Commit f2f457a

Browse files
authored
Merge pull request #65 from JakeGinnivan/typescript
Add support for React 19 + convert to typescript
2 parents 8f54b82 + 93a6eeb commit f2f457a

9 files changed

+85
-102
lines changed

_webpack.dist.config.js

-32
This file was deleted.

demo/demo.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import Popout from '../lib/react-popout.jsx';
2+
import Popout from '../lib/react-popout';
33

44
export default class Example extends React.Component {
55
constructor(props) {

demo/demo2.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import Popout from '../lib/react-popout.jsx';
2+
import Popout from '../lib/react-popout';
33

44
export default class Example2 extends React.Component {
55
constructor(props) {

demo/demo3.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import Popout from '../lib/react-popout.jsx';
2+
import Popout from '../lib/react-popout';
33

44
export default class Example3 extends React.Component {
55
constructor(props) {

jsconfig.json

-14
This file was deleted.

lib/react-popout.jsx lib/react-popout.tsx

+47-44
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import React from 'react';
2-
import { createRoot } from 'react-dom/client';
3-
import PropTypes from 'prop-types';
1+
import React, { PropsWithChildren } from 'react';
2+
import { createRoot, Root } from 'react-dom/client';
43

54
const DEFAULT_OPTIONS = {
65
toolbar: 'no',
@@ -12,44 +11,47 @@ const DEFAULT_OPTIONS = {
1211
resizable: 'yes',
1312
width: 500,
1413
height: 400,
15-
top: (o, w) => (w.innerHeight - o.height) / 2 + w.screenY,
16-
left: (o, w) => (w.innerWidth - o.width) / 2 + w.screenX
14+
top: (o: any, w: any) => (w.innerHeight - o.height) / 2 + w.screenY,
15+
left: (o: any, w: any) => (w.innerWidth - o.width) / 2 + w.screenX,
1716
};
1817

1918
const ABOUT_BLANK = 'about:blank';
2019

20+
interface PopoutWindowProps extends PropsWithChildren<any> {
21+
options: object;
22+
url: string;
23+
containerId: string;
24+
containerClassName?: string;
25+
onError: () => void;
26+
window?: Window;
27+
title?: string;
28+
}
29+
30+
interface PopoutWindowState {
31+
popoutWindow: Window | null;
32+
container: HTMLDivElement | null;
33+
openedWindowComponent: React.Component | null;
34+
}
35+
2136
/**
2237
* @class PopoutWindow
2338
*/
24-
export default class PopoutWindow extends React.Component {
39+
export default class PopoutWindow extends React.Component<PopoutWindowProps, PopoutWindowState> {
40+
private interval!: number;
41+
private root!: Root;
42+
2543
static defaultProps = {
2644
url: ABOUT_BLANK,
2745
containerId: 'popout-content-container',
2846
containerClassName: '',
29-
onError: () => {}
30-
};
31-
32-
/**
33-
*
34-
* @type {{title: *, url: *, onClosing: *, options: *, window: *, containerId: *}}
35-
*/
36-
static propTypes = {
37-
title: PropTypes.string.isRequired,
38-
url: PropTypes.string,
39-
onClosing: PropTypes.func,
40-
options: PropTypes.object,
41-
window: PropTypes.object,
42-
containerId: PropTypes.string,
43-
containerClassName: PropTypes.string,
44-
children: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
45-
onError: PropTypes.func
47+
onError: () => {},
4648
};
4749

4850
/**
4951
* @constructs PopoutWindow
5052
* @param props
5153
*/
52-
constructor(props) {
54+
constructor(props: PopoutWindowProps) {
5355
super(props);
5456

5557
this.mainWindowClosed = this.mainWindowClosed.bind(this);
@@ -59,21 +61,24 @@ export default class PopoutWindow extends React.Component {
5961
this.state = {
6062
openedWindowComponent: null,
6163
popoutWindow: null,
62-
container: null
64+
container: null,
6365
};
6466
}
6567

66-
createOptions(ownerWindow) {
68+
createOptions(ownerWindow: Window) {
6769
const mergedOptions = Object.assign({}, DEFAULT_OPTIONS, this.props.options);
6870

6971
return Object.keys(mergedOptions)
7072
.map(
71-
key =>
73+
(key) =>
7274
key +
7375
'=' +
76+
// @ts-ignore
7477
(typeof mergedOptions[key] === 'function'
75-
? mergedOptions[key].call(this, mergedOptions, ownerWindow)
76-
: mergedOptions[key])
78+
? // @ts-ignore
79+
mergedOptions[key].call(this, mergedOptions, ownerWindow)
80+
: // @ts-ignore
81+
mergedOptions[key]),
7782
)
7883
.join(',');
7984
}
@@ -90,44 +95,45 @@ export default class PopoutWindow extends React.Component {
9095
}
9196
}
9297

93-
componentWillReceiveProps(newProps) {
98+
componentWillReceiveProps(newProps: PopoutWindowProps) {
9499
if (newProps.title !== this.props.title && this.state.popoutWindow) {
95-
this.state.popoutWindow.document.title = newProps.title;
100+
this.state.popoutWindow.document.title = newProps.title!;
96101
}
97102
}
98103

99104
componentDidUpdate() {
100-
this.renderToContainer(this.state.container, this.state.popoutWindow, this.props.children);
105+
this.renderToContainer(this.state.container!, this.state.popoutWindow!, this.props.children);
101106
}
102107

103108
componentWillUnmount() {
104109
this.mainWindowClosed();
105110
}
106111

107-
popoutWindowLoaded(popoutWindow) {
112+
popoutWindowLoaded(popoutWindow: Window) {
108113
if (!this.state.container) {
109114
// Popout window is passed from openPopoutWindow if no url is specified.
110115
// In this case this.state.popoutWindow will not yet be set, so use the argument.
111116
popoutWindow = this.state.popoutWindow || popoutWindow;
112-
popoutWindow.document.title = this.props.title;
117+
popoutWindow.document.title = this.props.title!;
113118
let container = popoutWindow.document.createElement('div');
114119
container.id = this.props.containerId;
115-
container.className = this.props.containerClassName;
120+
container.className = this.props.containerClassName!;
116121
popoutWindow.document.body.appendChild(container);
117122

118123
this.setState({ container });
119124
this.renderToContainer(container, popoutWindow, this.props.children);
120125
}
121126
}
122127

123-
openPopoutWindow(ownerWindow) {
128+
openPopoutWindow(ownerWindow: Window) {
124129
const popoutWindow = ownerWindow.open(this.props.url, this.props.name || this.props.title, this.createOptions(ownerWindow));
125130
if (!popoutWindow) {
126131
this.props.onError();
127132
return;
128133
}
129134
this.setState({ popoutWindow });
130135

136+
// @ts-ignore
131137
popoutWindow.addEventListener('load', this.popoutWindowLoaded);
132138
popoutWindow.addEventListener('unload', this.popoutWindowUnloading);
133139

@@ -153,8 +159,8 @@ export default class PopoutWindow extends React.Component {
153159
*
154160
* @param popoutWindow
155161
*/
156-
checkForPopoutWindowClosure(popoutWindow) {
157-
this.interval = setInterval(() => {
162+
checkForPopoutWindowClosure(popoutWindow: Window) {
163+
this.interval = window.setInterval(() => {
158164
if (popoutWindow.closed) {
159165
clearInterval(this.interval);
160166
this.props.onClosing && this.props.onClosing(popoutWindow);
@@ -170,18 +176,15 @@ export default class PopoutWindow extends React.Component {
170176
popoutWindowUnloading() {
171177
if (this.state.container) {
172178
clearInterval(this.interval);
173-
this.root.unmount(this.state.container);
179+
this.root.unmount();
174180
this.props.onClosing && this.props.onClosing(this.state.popoutWindow);
175181
}
176182
}
177183

178-
renderToContainer(container, popoutWindow, children) {
184+
renderToContainer(container: HTMLDivElement, popoutWindow: Window, children: React.ReactNode | ((window: Window) => React.ReactNode)) {
179185
// For SSR we might get updated but there will be no container.
180186
if (container) {
181-
let renderedComponent = children;
182-
if (typeof children === 'function') {
183-
renderedComponent = children(popoutWindow);
184-
}
187+
const renderedComponent = typeof children === 'function' ? children(popoutWindow) : children;
185188

186189
if (!this.root) {
187190
this.root = createRoot(container);

package.json

+13-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
{
22
"name": "react-popout",
3-
"version": "3.0.4",
3+
"version": "4.0.0",
44
"description": "Wraps window.open in a react component, allowing the contents to be part of your react render tree",
55
"main": "dist/react-popout.min.js",
6+
"types": "dist/react-popout.d.ts",
67
"repository": {
78
"type": "git",
89
"url": "https://github.com/JakeGinnivan/react-popout"
@@ -19,7 +20,9 @@
1920
"scripts": {
2021
"prebuild:demo": "npm run build:dist",
2122
"demo": "webpack-dev-server --progress --colors --hot -d --port 8880 --config _webpack.demo.config.js",
22-
"build:dist": "webpack --config _webpack.dist.config.js --optimize-minimize",
23+
"build:dist": "npm run build:compile && npm run build:minify",
24+
"build:compile": "tsc",
25+
"build:minify": "esbuild dist/react-popout.js --minify --outfile=dist/react-popout.min.js",
2326
"build:demo": "webpack --config _webpack.demo.config.js --optimize-minimize",
2427
"test": "mocha \"test/**/*spec.js*\" --compilers js:babel-register --reporter dot",
2528
"prepublish": "npm run build:dist"
@@ -33,7 +36,10 @@
3336
"babel-loader": "^7.1.2",
3437
"babel-preset-es2015": "^6.6.0",
3538
"babel-preset-react": "^6.5.0",
39+
"esbuild": "^0.24.2",
3640
"babel-preset-stage-0": "^6.5.0",
41+
"@types/react": "^19.0.2",
42+
"@types/react-dom": "^19.0.2",
3743
"babel-runtime": "^6.26.0",
3844
"eslint-config-airbnb": "latest",
3945
"eslint-config-prettier": "latest",
@@ -43,14 +49,14 @@
4349
"eslint-plugin-react": "latest",
4450
"mocha": "^3.5.3",
4551
"node-libs-browser": "^2.0.0",
46-
"prettier": "latest",
52+
"prettier": "^3.4.2",
4753
"webpack": "^3.6.0",
48-
"webpack-dev-server": "^2.8.2"
54+
"webpack-dev-server": "^2.8.2",
55+
"typescript": "^5.7.2"
4956
},
5057
"dependencies": {
51-
"prop-types": "^15.8.1",
52-
"react": "^18.2.0",
53-
"react-dom": "^18.2.0"
58+
"react": "^19.0.0",
59+
"react-dom": "^19.0.0"
5460
},
5561
"peerDependencies": {
5662
"react": "^18.2.0",

tests.webpack.js

-2
This file was deleted.

tsconfig.json

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es2020",
4+
"module": "commonjs",
5+
"declaration": true,
6+
"outDir": "dist/",
7+
"lib": [
8+
"es2020", "dom"
9+
],
10+
"jsx": "react",
11+
"strict": true,
12+
"noImplicitAny": true,
13+
"strictNullChecks": true,
14+
"alwaysStrict": true,
15+
"allowSyntheticDefaultImports": true,
16+
"esModuleInterop": true
17+
},
18+
"include": ["lib"],
19+
"exclude": [
20+
"node_modules"
21+
]
22+
}

0 commit comments

Comments
 (0)