diff --git a/README.md b/README.md index 20cf728..01a82e4 100644 --- a/README.md +++ b/README.md @@ -41,15 +41,27 @@ import WinBox from 'react-winbox'; Or you can do more one step, to make a genuine 'windows manager', just like: ```tsx -const [windows, setWindows] = useState(); +const [windows, setWindows] = useState([]); // ... // some code to maintain a list of necessary windows info... // ... +const handleClose = (id) => { + let state = [...windows]; + const index = state.findIndex(info => info.id === id); + if (index !== -1) { + state.splice(index, 1); + setTimeout(() => setWindows(state)); + } +}; return ( <> {windows.map(info => ( - // assign any prop you want to WinBox - + handleClose(info.id)} + {...info.neededProps} // assign any props you want to WinBox + >
Some children
))} @@ -60,6 +72,7 @@ return ( ## Notice - To open a winbox, just create it in your virtual DOM, that's enough. - To close a winbox, just do not render it. It's safe. +- `onclose` is called BEFORE the winbox goes to close process. So if you want to destroy the React WinBox component in it, be sure to wrap destroy actions within `setTimeout` so that they occur after the winbox.js DOM is truly closed,e.g. `setTimeout(() => setState({showWindow: false}))`. - To change some properties of the window, just change the properties of WinBox Component. (the properties need [official methods](https://github.com/nextapps-de/winbox#manage-window-content) support. BTW, don't forget to setState or forceUpdate to rerender the parent of the WinBox!) - If you want to operate the pure WinBox.js object manually (In winbox@0.2.1, it's needed only when you want to call `mount()` method), you can find a `winBoxObj` in the component ref. !!! Take care of the relationship of statement between WinBox Component and `winBoxObj`. @@ -75,7 +88,7 @@ return ( type WinBoxPropType = { title: string id?: string - children?: ReactChild | Iterable | null + children?: ReactElement | ReactElement[] | null url?: string // When you use this, the children elements will be ignored. noAnimation?: boolean, diff --git a/dist/index.d.ts b/dist/index.d.ts index 5e49d1c..b0fc16b 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,7 +1,7 @@ -import { Component, ReactElement } from 'react'; +import React, { Component, ReactElement } from 'react'; import OriginalWinBox from 'winbox/src/js/winbox'; import 'winbox/dist/css/winbox.min.css'; -declare type WinBoxPropType = { +export declare type WinBoxPropType = { title: string; id?: string; children?: ReactElement | ReactElement[] | null; @@ -36,12 +36,14 @@ declare type WinBoxPropType = { width?: string | number; fullscreen?: boolean; /** + * This callback is called BEFORE the winbox goes to close process. So if you want to destroy the React WinBox component in it, be sure to wrap destroy actions within `setTimeout` so that they occur after the winbox.js DOM is truly closed,e.g. `setTimeout(() => setState({showWindow: false}))` + * * see the following document for more detail about the argument and the return value. * @see https://github.com/nextapps-de/winbox * @param force whether you should not abort the winbox to close. - * @return canBeClosed - true if the winbox can be closed + * @return noDefaultClose - true if the winbox does not need the default close process, for example, when it needs a confirmation to close instead of being closed suddenly. */ - onclose?: (force?: boolean) => boolean; + onclose?: (force?: boolean) => boolean | undefined | void; onmove?: (x: number, y: number) => any; onresize?: (width: number, height: number) => any; onblur?: () => any; @@ -72,13 +74,12 @@ declare class WinBox extends Component { isMax: () => boolean; isMin: () => boolean; isClosed: () => boolean; - renderChildren: () => void; maintainStyle: () => void; maintain: (args?: { force?: boolean | undefined; prevProps?: WinBoxPropType | undefined; } | undefined) => void; handleClose: () => void; - render(): JSX.Element; + render(): React.ReactPortal | null; } export default WinBox; diff --git a/dist/index.js b/dist/index.js index c79e685..2251e90 100644 --- a/dist/index.js +++ b/dist/index.js @@ -50,27 +50,6 @@ var WinBox = /** @class */ (function (_super) { _this.isMax = function () { var _a, _b; return ((_b = (_a = _this.winBoxObj) === null || _a === void 0 ? void 0 : _a.max) !== null && _b !== void 0 ? _b : false); }; _this.isMin = function () { var _a, _b; return ((_b = (_a = _this.winBoxObj) === null || _a === void 0 ? void 0 : _a.min) !== null && _b !== void 0 ? _b : false); }; _this.isClosed = function () { return (_this.state.closed); }; - _this.renderChildren = function () { - var _a, _b; - if (!_this.winBoxObj) - return; // because of twice calling in the strictMode, there can't be a `!this.state.closed` - if (Object.keys(_this.props).indexOf('url') !== -1 && _this.props.url) - return; // do nothing if url is set. - if ( /*!this.reactRoot ||*/_this.reactRootTarget !== _this.winBoxObj.body) { - // this.reactRoot = hydrateRoot(this.winBoxObj.body, this.props.children); // downgraded - _this.reactRootTarget = _this.winBoxObj.body; - } - if (_this.props.children) { - if (Array.isArray(_this.props.children)) { - var children = _this.props.children; - react_dom_1.default.render(children !== null && children !== void 0 ? children : [], (_a = _this.reactRootTarget) !== null && _a !== void 0 ? _a : null); - } - else { - var children = _this.props.children; - react_dom_1.default.render(children, (_b = _this.reactRootTarget) !== null && _b !== void 0 ? _b : null); - } - } - }; _this.maintainStyle = function () { if (!_this.winBoxObj) return; @@ -92,16 +71,20 @@ var WinBox = /** @class */ (function (_super) { return; var _s = args !== null && args !== void 0 ? args : {}, force = _s.force, prevProps = _s.prevProps; if (force || (prevProps === null || prevProps === void 0 ? void 0 : prevProps.title) !== _this.props.title) { - (_a = _this.winBoxObj) === null || _a === void 0 ? void 0 : _a.setTitle(_this.props.title); + if (_this.props.title !== undefined) + (_a = _this.winBoxObj) === null || _a === void 0 ? void 0 : _a.setTitle(_this.props.title); } if (force || (prevProps === null || prevProps === void 0 ? void 0 : prevProps.fullscreen) !== _this.props.fullscreen) { - (_b = _this.winBoxObj) === null || _b === void 0 ? void 0 : _b.fullscreen(_this.props.fullscreen); + if (_this.props.fullscreen !== undefined) + (_b = _this.winBoxObj) === null || _b === void 0 ? void 0 : _b.fullscreen(_this.props.fullscreen); } if (force || (prevProps === null || prevProps === void 0 ? void 0 : prevProps.min) !== _this.props.min) { - (_c = _this.winBoxObj) === null || _c === void 0 ? void 0 : _c.minimize(_this.props.min); + if (_this.props.min !== undefined) + (_c = _this.winBoxObj) === null || _c === void 0 ? void 0 : _c.minimize(_this.props.min); } if (force || (prevProps === null || prevProps === void 0 ? void 0 : prevProps.max) !== _this.props.max) { - (_d = _this.winBoxObj) === null || _d === void 0 ? void 0 : _d.maximize(_this.props.max); + if (_this.props.max !== undefined) + (_d = _this.winBoxObj) === null || _d === void 0 ? void 0 : _d.maximize(_this.props.max); } if (force || (prevProps === null || prevProps === void 0 ? void 0 : prevProps.width) !== _this.props.width @@ -133,9 +116,9 @@ var WinBox = /** @class */ (function (_super) { (_q = _this.winBoxObj) === null || _q === void 0 ? void 0 : _q.move(); } if (force || (prevProps === null || prevProps === void 0 ? void 0 : prevProps.url) !== _this.props.url) { - (_r = _this.winBoxObj) === null || _r === void 0 ? void 0 : _r.setUrl(_this.props.url); + if (_this.props.url !== undefined) + (_r = _this.winBoxObj) === null || _r === void 0 ? void 0 : _r.setUrl(_this.props.url); } - _this.renderChildren(); _this.maintainStyle(); }; _this.handleClose = function () { @@ -158,15 +141,14 @@ var WinBox = /** @class */ (function (_super) { if (this.props.id !== undefined && this.props.id !== null && document.getElementById(this.props.id)) throw 'duplicated window id'; this.winBoxObj = new winbox_1.default(__assign(__assign({ width: 300, height: 200, top: 0, bottom: 0, left: 0, right: 0 }, this.props), { class: "".concat((_a = this.props.className) !== null && _a !== void 0 ? _a : ''), onClose: function () { - var _a, _b, _c; - if ((_c = (_b = (_a = _this.props).onclose) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : true) { // the default is true - _this.handleClose(); // only when ture, do close process. + var _a, _b; + if ((_b = (_a = _this.props).onclose) === null || _b === void 0 ? void 0 : _b.call(_a)) { return true; } + _this.handleClose(); // only when false, do close process. return false; } })); - this.renderChildren(); - this.maintainStyle(); + this.forceUpdate(); } catch (e) { console.error(e); @@ -179,7 +161,10 @@ var WinBox = /** @class */ (function (_super) { }; WinBox.prototype.componentWillUnmount = function () { var _a; - (_a = this.winBoxObj) === null || _a === void 0 ? void 0 : _a.close(true); + try { + (_a = this.winBoxObj) === null || _a === void 0 ? void 0 : _a.close(true); + } + catch (ignored) { } }; WinBox.prototype.forceUpdate = function (callback) { var _a; @@ -194,7 +179,11 @@ var WinBox = /** @class */ (function (_super) { _super.prototype.forceUpdate.call(this, callback); }; WinBox.prototype.render = function () { - return ((0, jsx_runtime_1.jsx)("div", { "data-closed": this.state.closed })); + if (Object.keys(this.props).indexOf('url') !== -1 && this.props.url) + return null; // do nothing if url is set. + if (!this.winBoxObj || !this.winBoxObj.body) + return null; + return react_dom_1.default.createPortal((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: this.props.children }), this.winBoxObj.body); }; return WinBox; }(react_1.Component)); diff --git a/package-lock.json b/package-lock.json index c088a8c..daa1103 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "react-winbox", - "version": "1.3.8", + "version": "1.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "react-winbox", - "version": "1.3.8", + "version": "1.4.0", "license": "MIT", "dependencies": { "winbox": "^0.2.1" }, "devDependencies": { - "@types/react": "^16.0.41", - "@types/react-dom": "^16.0.11", + "@types/react": "^18.0.9", + "@types/react-dom": "^18.0.4", "@types/winbox": "^0.2.1", "@typescript-eslint/eslint-plugin": "^5.19.0", "@typescript-eslint/parser": "^5.19.0", @@ -21,8 +21,8 @@ "typescript": "^4.6.3" }, "peerDependencies": { - "react": "^16.14.0", - "react-dom": "^16.14.0" + "react": ">=16.14.0", + "react-dom": ">=16.14.0" } }, "node_modules/@eslint/eslintrc": { @@ -121,9 +121,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "16.14.25", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.25.tgz", - "integrity": "sha512-cXRVHd7vBT5v1is72mmvmsg9stZrbJO04DJqFeh3Yj2tVKO6vmxg5BI+ybI6Ls7ROXRG3aFbZj9x0WA3ZAoDQw==", + "version": "18.0.9", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.9.tgz", + "integrity": "sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw==", "dev": true, "dependencies": { "@types/prop-types": "*", @@ -132,12 +132,12 @@ } }, "node_modules/@types/react-dom": { - "version": "16.9.14", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.14.tgz", - "integrity": "sha512-FIX2AVmPTGP30OUJ+0vadeIFJJ07Mh1m+U0rxfgyW34p3rTlXI+nlenvAxNn4BP36YyI9IJ/+UJ7Wu22N1pI7A==", + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.4.tgz", + "integrity": "sha512-FgTtbqPOCI3dzZPZoC2T/sx3L34qxy99ITWn4eoSA95qPyXDMH0ALoAqUp49ITniiJFsXUVBtalh/KffMpg21Q==", "dev": true, "dependencies": { - "@types/react": "^16" + "@types/react": "*" } }, "node_modules/@types/scheduler": { @@ -2558,9 +2558,9 @@ "dev": true }, "@types/react": { - "version": "16.14.25", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.25.tgz", - "integrity": "sha512-cXRVHd7vBT5v1is72mmvmsg9stZrbJO04DJqFeh3Yj2tVKO6vmxg5BI+ybI6Ls7ROXRG3aFbZj9x0WA3ZAoDQw==", + "version": "18.0.9", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.9.tgz", + "integrity": "sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw==", "dev": true, "requires": { "@types/prop-types": "*", @@ -2569,12 +2569,12 @@ } }, "@types/react-dom": { - "version": "16.9.14", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.14.tgz", - "integrity": "sha512-FIX2AVmPTGP30OUJ+0vadeIFJJ07Mh1m+U0rxfgyW34p3rTlXI+nlenvAxNn4BP36YyI9IJ/+UJ7Wu22N1pI7A==", + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.4.tgz", + "integrity": "sha512-FgTtbqPOCI3dzZPZoC2T/sx3L34qxy99ITWn4eoSA95qPyXDMH0ALoAqUp49ITniiJFsXUVBtalh/KffMpg21Q==", "dev": true, "requires": { - "@types/react": "^16" + "@types/react": "*" } }, "@types/scheduler": { diff --git a/package.json b/package.json index 3147b27..1e67600 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-winbox", - "version": "1.3.8", + "version": "1.4.0", "description": "The React component for WinBox.js. Full Reactful props and state. A window manager for React", "private": false, "homepage": "https://github.com/rickonono3/react-winbox", @@ -19,8 +19,8 @@ "react-dom": ">=16.14.0" }, "devDependencies": { - "@types/react": ">=16.0.41", - "@types/react-dom": ">=16.0.11", + "@types/react": "^18.0.9", + "@types/react-dom": "^18.0.4", "@types/winbox": "^0.2.1", "@typescript-eslint/eslint-plugin": "^5.19.0", "@typescript-eslint/parser": "^5.19.0", diff --git a/src/index.tsx b/src/index.tsx index a83e6a4..fc113ef 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,9 +1,9 @@ -import React, {Component, ReactChild, ReactElement, ReactNode} from 'react'; +import React, {Component, ReactElement} from 'react'; import OriginalWinBox from 'winbox/src/js/winbox'; import 'winbox/dist/css/winbox.min.css'; -import ReactDOM, {Container, render, Renderer} from 'react-dom'; +import ReactDOM, {Container, Renderer} from 'react-dom'; -type WinBoxPropType = { +export type WinBoxPropType = { title: string id?: string children?: ReactElement | ReactElement[] | null @@ -43,12 +43,14 @@ type WinBoxPropType = { fullscreen?: boolean, /** + * This callback is called BEFORE the winbox goes to close process. So if you want to destroy the React WinBox component in it, be sure to wrap destroy actions within `setTimeout` so that they occur after the winbox.js DOM is truly closed,e.g. `setTimeout(() => setState({showWindow: false}))` + * * see the following document for more detail about the argument and the return value. * @see https://github.com/nextapps-de/winbox * @param force whether you should not abort the winbox to close. - * @return canBeClosed - true if the winbox can be closed + * @return noDefaultClose - true if the winbox does not need the default close process, for example, when it needs a confirmation to close instead of being closed suddenly. */ - onclose?: (force?: boolean) => boolean, + onclose?: (force?: boolean) => boolean | undefined | void, onmove?: (x: number, y: number) => any, onresize?: (width: number, height: number) => any, onblur?: () => any, @@ -99,15 +101,14 @@ class WinBox extends Component { ...this.props, class: `${this.props.className ?? ''}`, onClose: () => { - if (this.props.onclose?.() ?? true) { // the default is true - this.handleClose(); // only when ture, do close process. + if (this.props.onclose?.()) { return true; } + this.handleClose(); // only when false, do close process. return false; }, }); - this.renderChildren(); - this.maintainStyle(); + this.forceUpdate(); } catch (e) { console.error(e); this.winBoxObj?.close(true); @@ -120,7 +121,9 @@ class WinBox extends Component { } componentWillUnmount() { - this.winBoxObj?.close(true); + try { + this.winBoxObj?.close(true); + } catch (ignored) {} } public forceUpdate(callback?: () => void): void { @@ -145,25 +148,6 @@ class WinBox extends Component { public isClosed = (): boolean => (this.state.closed); - renderChildren = () => { - if (!this.winBoxObj) return; // because of twice calling in the strictMode, there can't be a `!this.state.closed` - if (Object.keys(this.props).indexOf('url') !== -1 && this.props.url) - return; // do nothing if url is set. - if (/*!this.reactRoot ||*/ this.reactRootTarget !== this.winBoxObj.body) { - // this.reactRoot = hydrateRoot(this.winBoxObj.body, this.props.children); // downgraded - this.reactRootTarget = this.winBoxObj.body; - } - if (this.props.children) { - if (Array.isArray(this.props.children)) { - const children = this.props.children as ReactElement[]; - ReactDOM.render(children ?? [], this.reactRootTarget ?? null); - } else { - const children = this.props.children as ReactElement; - ReactDOM.render(children, this.reactRootTarget ?? null); - } - } - }; - maintainStyle = () => { if (!this.winBoxObj) return; this.winBoxObj[this.props.noAnimation ? 'addClass' : 'removeClass']('no-animation'); @@ -183,16 +167,20 @@ class WinBox extends Component { if (!this.winBoxObj) return; const {force, prevProps} = args ?? {}; if (force || prevProps?.title !== this.props.title) { - this.winBoxObj?.setTitle(this.props.title); + if (this.props.title !== undefined) + this.winBoxObj?.setTitle(this.props.title); } if (force || prevProps?.fullscreen !== this.props.fullscreen) { - this.winBoxObj?.fullscreen(this.props.fullscreen); + if (this.props.fullscreen !== undefined) + this.winBoxObj?.fullscreen(this.props.fullscreen); } if (force || prevProps?.min !== this.props.min) { - this.winBoxObj?.minimize(this.props.min); + if (this.props.min !== undefined) + this.winBoxObj?.minimize(this.props.min); } if (force || prevProps?.max !== this.props.max) { - this.winBoxObj?.maximize(this.props.max); + if (this.props.max !== undefined) + this.winBoxObj?.maximize(this.props.max); } if (force || prevProps?.width !== this.props.width @@ -227,9 +215,9 @@ class WinBox extends Component { this.winBoxObj?.move(); } if (force || prevProps?.url !== this.props.url) { - this.winBoxObj?.setUrl(this.props.url); + if (this.props.url !== undefined) + this.winBoxObj?.setUrl(this.props.url); } - this.renderChildren(); this.maintainStyle(); }; @@ -240,9 +228,11 @@ class WinBox extends Component { }; render() { - return ( -
- ); + if (Object.keys(this.props).indexOf('url') !== -1 && this.props.url) + return null; // do nothing if url is set. + if (!this.winBoxObj || !this.winBoxObj.body) + return null; + return ReactDOM.createPortal(<>{this.props.children}, this.winBoxObj.body); } } diff --git a/yarn.lock b/yarn.lock index a30eb78..29175ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -62,17 +62,17 @@ "resolved" "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.5.tgz" "version" "15.7.5" -"@types/react-dom@^16.0.11": - "integrity" "sha512-FIX2AVmPTGP30OUJ+0vadeIFJJ07Mh1m+U0rxfgyW34p3rTlXI+nlenvAxNn4BP36YyI9IJ/+UJ7Wu22N1pI7A==" - "resolved" "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.14.tgz" - "version" "16.9.14" +"@types/react-dom@^18.0.4": + "integrity" "sha512-FgTtbqPOCI3dzZPZoC2T/sx3L34qxy99ITWn4eoSA95qPyXDMH0ALoAqUp49ITniiJFsXUVBtalh/KffMpg21Q==" + "resolved" "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.4.tgz" + "version" "18.0.4" dependencies: - "@types/react" "^16" + "@types/react" "*" -"@types/react@^16", "@types/react@^16.0.41": - "integrity" "sha512-cXRVHd7vBT5v1is72mmvmsg9stZrbJO04DJqFeh3Yj2tVKO6vmxg5BI+ybI6Ls7ROXRG3aFbZj9x0WA3ZAoDQw==" - "resolved" "https://registry.npmjs.org/@types/react/-/react-16.14.25.tgz" - "version" "16.14.25" +"@types/react@*", "@types/react@^18.0.9": + "integrity" "sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw==" + "resolved" "https://registry.npmjs.org/@types/react/-/react-18.0.9.tgz" + "version" "18.0.9" dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -1072,7 +1072,7 @@ "resolved" "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz" "version" "1.2.3" -"react-dom@^16.14.0": +"react-dom@>=16.14.0": "integrity" "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==" "resolved" "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz" "version" "16.14.0" @@ -1087,7 +1087,7 @@ "resolved" "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz" "version" "16.13.1" -"react@^16.14.0": +"react@^16.14.0", "react@>=16.14.0": "integrity" "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==" "resolved" "https://registry.npmjs.org/react/-/react-16.14.0.tgz" "version" "16.14.0"