From 5f8f65b6aba5b41cba1ed2bdc8be51974f0f8f47 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Mon, 6 Mar 2023 23:26:02 -0800 Subject: [PATCH 01/16] initial work to rewrite the client in typescript --- noxfile.py | 15 +- src/client/package-lock.json | 168 +++++++++-- src/client/package.json | 6 +- src/client/packages/app/package.json | 14 +- src/client/packages/app/src/index.js | 34 --- src/client/packages/app/src/index.tsx | 14 + src/client/packages/app/tsconfig.json | 19 ++ src/client/packages/client/package.json | 12 +- src/client/packages/client/src/components.js | 220 --------------- src/client/packages/client/src/components.tsx | 236 ++++++++++++++++ src/client/packages/client/src/contexts.js | 6 - .../packages/client/src/element-utils.js | 82 ------ .../packages/client/src/import-source.js | 134 --------- src/client/packages/client/src/index.js | 4 - src/client/packages/client/src/index.ts | 4 + src/client/packages/client/src/messages.ts | 32 +++ src/client/packages/client/src/mount.js | 105 ------- .../packages/client/src/reactpy-server.ts | 171 ++++++++++++ .../packages/client/src/reactpy-vdom.tsx | 263 ++++++++++++++++++ src/client/packages/client/src/server.js | 46 --- src/client/packages/client/tsconfig.json | 19 ++ .../packages/event-to-object/package.json | 23 ++ .../src/index.js} | 4 +- .../tests/event-to-object.test.js | 16 +- .../tests/tooling/dom.js | 0 .../tests/tooling/setup.js | 0 src/reactpy/core/vdom.py | 2 +- 27 files changed, 971 insertions(+), 678 deletions(-) delete mode 100644 src/client/packages/app/src/index.js create mode 100644 src/client/packages/app/src/index.tsx create mode 100644 src/client/packages/app/tsconfig.json delete mode 100644 src/client/packages/client/src/components.js create mode 100644 src/client/packages/client/src/components.tsx delete mode 100644 src/client/packages/client/src/contexts.js delete mode 100644 src/client/packages/client/src/element-utils.js delete mode 100644 src/client/packages/client/src/import-source.js delete mode 100644 src/client/packages/client/src/index.js create mode 100644 src/client/packages/client/src/index.ts create mode 100644 src/client/packages/client/src/messages.ts delete mode 100644 src/client/packages/client/src/mount.js create mode 100644 src/client/packages/client/src/reactpy-server.ts create mode 100644 src/client/packages/client/src/reactpy-vdom.tsx delete mode 100644 src/client/packages/client/src/server.js create mode 100644 src/client/packages/client/tsconfig.json create mode 100644 src/client/packages/event-to-object/package.json rename src/client/packages/{client/src/event-to-object.js => event-to-object/src/index.js} (98%) rename src/client/packages/{client => event-to-object}/tests/event-to-object.test.js (98%) rename src/client/packages/{client => event-to-object}/tests/tooling/dom.js (100%) rename src/client/packages/{client => event-to-object}/tests/tooling/setup.js (100%) diff --git a/noxfile.py b/noxfile.py index 0d1a02ae4..da6ed2cba 100644 --- a/noxfile.py +++ b/noxfile.py @@ -232,21 +232,18 @@ def check_docs(session: Session) -> None: @group.session -def check_javascript_suite(session: Session) -> None: - """Run the Javascript-based test suite and ensure it bundles succesfully""" - session.run("npm", "run", "test", external=True) +def check_javascript_tests(session: Session) -> None: + session.run("npm", "run", "check:tests", external=True) @group.session -def check_javascript_build(session: Session) -> None: - """Run the Javascript-based test suite and ensure it bundles succesfully""" - session.run("npm", "run", "test", external=True) +def check_javascript_format(session: Session) -> None: + session.run("npm", "run", "check:format", external=True) @group.session -def check_javascript_format(session: Session) -> None: - """Check that Javascript style guidelines are being followed""" - session.run("npm", "run", "check-format", external=True) +def check_javascript_types(session: Session) -> None: + session.run("npm", "run", "check:types", external=True) @group.session diff --git a/src/client/package-lock.json b/src/client/package-lock.json index 1570074f6..67611e9c6 100644 --- a/src/client/package-lock.json +++ b/src/client/package-lock.json @@ -54,6 +54,44 @@ "resolved": "packages/client", "link": true }, + "node_modules/@types/json-pointer": { + "version": "1.0.31", + "resolved": "https://registry.npmjs.org/@types/json-pointer/-/json-pointer-1.0.31.tgz", + "integrity": "sha512-hTPul7Um6LqsHXHQpdkXTU7Oysjsf+9k4Yfmg6JhSKG/jj9QuQGyMUdj6trPH6WHiIdxw7nYSROgOxeFmCVK2w==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.0.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", + "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", + "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, "node_modules/abab": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", @@ -224,6 +262,12 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", "dev": true }, + "node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", + "dev": true + }, "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -703,6 +747,10 @@ "node": ">=0.10.0" } }, + "node_modules/event-to-object": { + "resolved": "packages/event-to-object", + "link": true + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -828,11 +876,6 @@ "node": ">= 0.4.0" } }, - "node_modules/htm": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/htm/-/htm-3.0.4.tgz", - "integrity": "sha512-VRdvxX3tmrXuT/Ovt59NMp/ORMFi4bceFMDjos1PV4E0mV+5votuID8R60egR9A4U8nLt238R/snlJGz3UYiTQ==" - }, "node_modules/html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -1590,6 +1633,19 @@ "node": ">= 0.8.0" } }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", @@ -1921,7 +1977,9 @@ "preact": "^10.7.0" }, "devDependencies": { - "prettier": "^2.5.1" + "@types/react-dom": "^18.0.11", + "prettier": "^2.5.1", + "typescript": "^4.9.5" } }, "packages/app/node_modules/@reactpy/client": { @@ -1934,20 +1992,36 @@ "version": "0.45.0", "license": "MIT", "dependencies": { - "htm": "^3.0.3", + "event-to-object": "file:packages/event-to-object", "json-pointer": "^0.6.2" }, "devDependencies": { + "@types/json-pointer": "^1.0.31", + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", "jsdom": "16.5.0", "lodash": "^4.17.21", "prettier": "^2.5.1", - "uvu": "^0.5.1" + "typescript": "^4.9.5" }, "peerDependencies": { "react": ">=16", "react-dom": ">=16" } }, + "packages/client/node_modules/event-to-object": { + "resolved": "packages/client/packages/event-to-object", + "link": true + }, + "packages/client/packages/event-to-object": {}, + "packages/event-to-object": { + "version": "0.1.0", + "license": "MIT", + "devDependencies": { + "prettier": "^2.5.1", + "uvu": "^0.5.1" + } + }, "packages/idom-app-react": { "name": "@reactpy/app", "version": "1.0.0-a5", @@ -2004,8 +2078,10 @@ "version": "file:packages/app", "requires": { "@reactpy/client": "file:packages/client", + "@types/react-dom": "^18.0.11", "preact": "^10.7.0", - "prettier": "^2.5.1" + "prettier": "^2.5.1", + "typescript": "^4.9.5" }, "dependencies": { "@reactpy/client": { @@ -2016,14 +2092,60 @@ "@reactpy/client": { "version": "file:packages/client", "requires": { - "htm": "^3.0.3", + "@types/json-pointer": "^1.0.31", + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "event-to-object": "file:packages/event-to-object", "jsdom": "16.5.0", "json-pointer": "^0.6.2", "lodash": "^4.17.21", "prettier": "^2.5.1", - "uvu": "^0.5.1" + "typescript": "^4.9.5" + }, + "dependencies": { + "event-to-object": { + "version": "file:packages/client/packages/event-to-object" + } + } + }, + "@types/json-pointer": { + "version": "1.0.31", + "resolved": "https://registry.npmjs.org/@types/json-pointer/-/json-pointer-1.0.31.tgz", + "integrity": "sha512-hTPul7Um6LqsHXHQpdkXTU7Oysjsf+9k4Yfmg6JhSKG/jj9QuQGyMUdj6trPH6WHiIdxw7nYSROgOxeFmCVK2w==", + "dev": true + }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true + }, + "@types/react": { + "version": "18.0.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", + "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "18.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", + "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", + "dev": true, + "requires": { + "@types/react": "*" } }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, "abab": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", @@ -2167,6 +2289,12 @@ } } }, + "csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", + "dev": true + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -2424,6 +2552,13 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "event-to-object": { + "version": "file:packages/event-to-object", + "requires": { + "prettier": "^2.5.1", + "uvu": "^0.5.1" + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -2523,11 +2658,6 @@ "function-bind": "^1.1.1" } }, - "htm": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/htm/-/htm-3.0.4.tgz", - "integrity": "sha512-VRdvxX3tmrXuT/Ovt59NMp/ORMFi4bceFMDjos1PV4E0mV+5votuID8R60egR9A4U8nLt238R/snlJGz3UYiTQ==" - }, "html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -3095,6 +3225,12 @@ "prelude-ls": "~1.1.2" } }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + }, "universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", diff --git a/src/client/package.json b/src/client/package.json index d480580f2..d8a74ce43 100644 --- a/src/client/package.json +++ b/src/client/package.json @@ -9,9 +9,11 @@ }, "scripts": { "build": "vite build", - "check-format": "npm --workspaces run check-format", + "publish": "npm --workspaces publish", "format": "npm --workspaces run format", - "test": "npm --workspaces test" + "check:format": "npm --workspaces run check-format", + "check:tests": "npm --workspaces check:tests", + "check:types": "npm --workspaces run check:types" }, "version": "1.0.0", "workspaces": [ diff --git a/src/client/packages/app/package.json b/src/client/packages/app/package.json index 0424577a9..d8432a620 100644 --- a/src/client/packages/app/package.json +++ b/src/client/packages/app/package.json @@ -6,18 +6,22 @@ }, "description": "A client application for ReactPy implemented in React", "devDependencies": { - "prettier": "^2.5.1" + "prettier": "^2.5.1", + "typescript": "^4.9.5", + "@types/react-dom": "^18.0.11" }, "license": "MIT", - "main": "src/index.js", + "main": "src/index.tsx", "name": "@reactpy/app", "repository": { "type": "git", "url": "https://github.com/reactive-python/reactpy" }, "scripts": { - "check-format": "prettier --check ./src", "format": "prettier --write ./src", - "test": "echo 'no tests'" - } + "check:format": "prettier --check ./src", + "check:tests": "echo 'no tests'", + "check:types": "tsc" + }, + "version": "0.45.0" } diff --git a/src/client/packages/app/src/index.js b/src/client/packages/app/src/index.js deleted file mode 100644 index 27d62ce09..000000000 --- a/src/client/packages/app/src/index.js +++ /dev/null @@ -1,34 +0,0 @@ -import { mountWithLayoutServer, LayoutServerInfo } from "@reactpy/client"; - -export function mount(mountPoint) { - const serverInfo = new LayoutServerInfo({ - host: document.location.hostname, - port: document.location.port, - query: queryParams.user.toString(), - secure: document.location.protocol == "https:", - }); - mountWithLayoutServer(mountPoint, serverInfo, shouldReconnect() ? 45 : 0); -} - -function shouldReconnect() { - return queryParams.reserved.get("noReconnect") === null; -} - -const queryParams = (() => { - const reservedParams = new URLSearchParams(); - const userParams = new URLSearchParams(window.location.search); - - const reservedParamNames = ["noReconnect"]; - reservedParamNames.forEach((name) => { - const value = userParams.get(name); - if (value !== null) { - reservedParams.append(name, userParams.get(name)); - userParams.delete(name); - } - }); - - return { - reserved: reservedParams, - user: userParams, - }; -})(); diff --git a/src/client/packages/app/src/index.tsx b/src/client/packages/app/src/index.tsx new file mode 100644 index 000000000..a2cfa4b50 --- /dev/null +++ b/src/client/packages/app/src/index.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { render } from "react-dom"; +import { Layout, SimpleReactPyServer } from "@reactpy/client"; + +export function mount(root: HTMLElement) { + const server = new SimpleReactPyServer({ + serverApi: { + baseUrl: document.location.origin, + routePath: document.location.pathname, + queryString: document.location.search, + }, + }); + render(, root); +} diff --git a/src/client/packages/app/tsconfig.json b/src/client/packages/app/tsconfig.json new file mode 100644 index 000000000..ef9ae16f9 --- /dev/null +++ b/src/client/packages/app/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["DOM", "DOM.Iterable", "esnext"], + "allowJs": false, + "skipLibCheck": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + }, + "include": ["src"] +} diff --git a/src/client/packages/client/package.json b/src/client/packages/client/package.json index e9bf4cc13..6ae942190 100644 --- a/src/client/packages/client/package.json +++ b/src/client/packages/client/package.json @@ -1,15 +1,18 @@ { "author": "Ryan Morshead", "dependencies": { - "htm": "^3.0.3", + "event-to-object": "file:packages/event-to-object", "json-pointer": "^0.6.2" }, "description": "A client for ReactPy implemented in React", "devDependencies": { + "@types/json-pointer": "^1.0.31", + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", "jsdom": "16.5.0", "lodash": "^4.17.21", "prettier": "^2.5.1", - "uvu": "^0.5.1" + "typescript": "^4.9.5" }, "files": [ "src/**/*.js" @@ -26,9 +29,10 @@ "url": "https://github.com/reactive-python/reactpy" }, "scripts": { - "check-format": "prettier --check ./src ./tests", "format": "prettier --write ./src ./tests", - "test": "uvu tests" + "check:format": "prettier --check ./src ./tests", + "check:tests": "echo 'no tests'", + "check:types": "tsc" }, "type": "module", "version": "0.1.0" diff --git a/src/client/packages/client/src/components.js b/src/client/packages/client/src/components.js deleted file mode 100644 index 4f2953d32..000000000 --- a/src/client/packages/client/src/components.js +++ /dev/null @@ -1,220 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import htm from "htm"; -import { set as setJsonPointer } from "json-pointer"; - -import { useImportSource } from "./import-source.js"; -import { LayoutContext } from "./contexts.js"; - -import { - createElementAttributes, - createElementChildren, -} from "./element-utils.js"; - -const html = htm.bind(React.createElement); - -export function Layout({ saveUpdateHook, sendEvent, loadImportSource }) { - const currentModel = React.useState({})[0]; - const forceUpdate = useForceUpdate(); - - const patchModel = React.useCallback( - ({ path, model }) => { - if (!path) { - Object.assign(currentModel, model); - } else { - setJsonPointer(currentModel, path, model); - } - forceUpdate(); - }, - [currentModel] - ); - - React.useEffect(() => saveUpdateHook(patchModel), [patchModel]); - - if (!Object.keys(currentModel).length) { - return html`<${React.Fragment} />`; - } - - return html` - <${LayoutContext.Provider} value=${{ sendEvent, loadImportSource }}> - <${Element} model=${currentModel} /> - - `; -} - -export function Element({ model }) { - if (model.error !== undefined) { - if (model.error) { - return html`
${model.error}
`; - } else { - return null; - } - } else if (model.tagName == "script") { - return html`<${ScriptElement} model=${model} />`; - } else if (["input", "select", "textarea"].includes(model.tagName)) { - return html`<${UserInputElement} model=${model} />`; - } else if (model.importSource) { - return html`<${ImportedElement} model=${model} />`; - } else { - return html`<${StandardElement} model=${model} />`; - } -} - -function StandardElement({ model }) { - const layoutContext = React.useContext(LayoutContext); - - let type; - if (model.tagName == "") { - type = React.Fragment; - } else { - type = model.tagName; - } - - // Use createElement here to avoid warning about variable numbers of children not - // having keys. Warning about this must now be the responsibility of the server - // providing the models instead of the client rendering them. - return React.createElement( - type, - createElementAttributes(model, layoutContext.sendEvent), - ...createElementChildren( - model, - (model) => html`<${Element} key=${model.key} model=${model} />` - ) - ); -} - -// Element with a value attribute controlled by user input -function UserInputElement({ model }) { - const ref = React.useRef(); - const layoutContext = React.useContext(LayoutContext); - - const props = createElementAttributes(model, layoutContext.sendEvent); - - // Because we handle events asynchronously, we must leave the value uncontrolled in - // order to allow all changes committed by the user to be recorded in the order they - // occur. If we don't the user may commit multiple changes before we render next - // causing the content of prior changes to be overwritten by subsequent changes. - let value = props.value; - delete props.value; - - // Instead of controlling the value, we set it in an effect. - React.useEffect(() => { - if (value !== undefined) { - ref.current.value = value; - } - }, [ref.current, value]); - - // Track a buffer of observed values in order to avoid flicker - const observedValues = React.useState([])[0]; - if (observedValues) { - if (value === observedValues[0]) { - observedValues.shift(); - value = observedValues[observedValues.length - 1]; - } else { - observedValues.length = 0; - } - } - - const givenOnChange = props.onChange; - if (typeof givenOnChange === "function") { - props.onChange = (event) => { - observedValues.push(event.target.value); - givenOnChange(event); - }; - } - - // Use createElement here to avoid warning about variable numbers of children not - // having keys. Warning about this must now be the responsibility of the server - // providing the models instead of the client rendering them. - return React.createElement( - model.tagName, - { - ...props, - ref: (target) => { - ref.current = target; - }, - }, - ...createElementChildren( - model, - (model) => html`<${Element} key=${model.key} model=${model} />` - ) - ); -} - -function ScriptElement({ model }) { - const ref = React.useRef(); - React.useEffect(() => { - if (model?.children?.length > 1) { - console.error("Too many children for 'script' element."); - } - - let scriptContent = model?.children?.[0]; - - let scriptElement; - if (model.attributes) { - scriptElement = document.createElement("script"); - for (const [k, v] of Object.entries(model.attributes)) { - scriptElement.setAttribute(k, v); - } - scriptElement.appendChild(document.createTextNode(scriptContent)); - ref.current.appendChild(scriptElement); - } else { - let scriptResult = eval(scriptContent); - if (typeof scriptResult == "function") { - return scriptResult(); - } - } - }, [model.key]); - return html`
`; -} - -function ImportedElement({ model }) { - const layoutContext = React.useContext(LayoutContext); - - const importSourceFallback = model.importSource.fallback; - const importSource = useImportSource(model.importSource); - - if (!importSource) { - // display a fallback if one was given - if (!importSourceFallback) { - return html`
`; - } else if (typeof importSourceFallback === "string") { - return html`
${importSourceFallback}
`; - } else { - return html`<${StandardElement} model=${importSourceFallback} />`; - } - } else { - return html`<${_ImportedElement} - model=${model} - importSource=${importSource} - />`; - } -} - -function _ImportedElement({ model, importSource }) { - const layoutContext = React.useContext(LayoutContext); - const mountPoint = React.useRef(null); - const sourceBinding = React.useRef(null); - - React.useEffect(() => { - sourceBinding.current = importSource.bind(mountPoint.current); - if (!importSource.data.unmountBeforeUpdate) { - return sourceBinding.current.unmount; - } - }, []); - - // this effect must run every time in case the model has changed - React.useEffect(() => { - sourceBinding.current.render(model); - if (importSource.data.unmountBeforeUpdate) { - return sourceBinding.current.unmount; - } - }); - - return html`
`; -} - -function useForceUpdate() { - const [, updateState] = React.useState(); - return React.useCallback(() => updateState({}), []); -} diff --git a/src/client/packages/client/src/components.tsx b/src/client/packages/client/src/components.tsx new file mode 100644 index 000000000..fdddabb58 --- /dev/null +++ b/src/client/packages/client/src/components.tsx @@ -0,0 +1,236 @@ +import React, { + createElement, + createContext, + useState, + useCallback, + useRef, + useContext, + useEffect, + Fragment, + ComponentType, + MutableRefObject, + SyntheticEvent, + ChangeEvent, +} from "react"; +// @ts-ignore +import { set as setJsonPointer } from "json-pointer"; +import { LayoutUpdateMessage } from "./messages"; +import { + ReactPyVdom, + ReactPyComponent, + createChildren, + createAttributes, + ReactPyVdomImportSource, + loadImportSource, + ImportSourceBinding, +} from "./reactpy-vdom"; +import { ReactPyServer } from "./reactpy-server"; + +const LayoutServer = createContext(null as any); + +export function Layout(props: { server: ReactPyServer }): JSX.Element { + const currentModel: ReactPyVdom = useState({ tagName: "" })[0]; + const forceUpdate = useForceUpdate(); + + useEffect(() => { + props.server + .receiveMessage("layout-update") + .then(({ path, model }) => { + setJsonPointer(currentModel, path, model); + forceUpdate(); + }); + }, [currentModel, props.server]); + + return ( + + + + ); +} + +export function Element({ model }: { model: ReactPyVdom }): JSX.Element | null { + if (model.error !== undefined) { + if (model.error) { + return
{model.error}
; + } else { + return null; + } + } + + let SpecializedElement: ReactPyComponent; + if (model.tagName in SPECIAL_ELEMENTS) { + SpecializedElement = + SPECIAL_ELEMENTS[model.tagName as keyof typeof SPECIAL_ELEMENTS]; + } else if (model.importSource) { + SpecializedElement = ImportedElement; + } else { + SpecializedElement = StandardElement; + } + + return ; +} + +function StandardElement({ model }: { model: ReactPyVdom }) { + const server = React.useContext(LayoutServer); + + let type: string | ComponentType; + if (model.tagName == "") { + type = Fragment; + } else { + type = model.tagName; + } + + // Use createElement here to avoid warning about variable numbers of children not + // having keys. Warning about this must now be the responsibility of the server + // providing the models instead of the client rendering them. + return createElement( + type, + createAttributes(model, server), + ...createChildren(model, (model) => ( + + )), + ); +} + +function UserInputElement({ model }: { model: ReactPyVdom }): JSX.Element { + const server = useContext(LayoutServer); + const props = createAttributes(model, server); + const [value, setValue] = React.useState(props.value); + + // honor changes to value from the server via props + React.useEffect(() => setValue(props.value), [props.value]); + + const givenOnChange = props.onChange; + if (typeof givenOnChange === "function") { + props.onChange = (event: ChangeEvent) => { + // immediately update the value to give the user feedback + setValue(event.target.value); + // allow the server to respond (and possibly change the value) + givenOnChange(event); + }; + } + + // Use createElement here to avoid warning about variable numbers of children not + // having keys. Warning about this must now be the responsibility of the server + // providing the models instead of the client rendering them. + return React.createElement( + model.tagName, + // overwrite + { ...props, value }, + ...createChildren(model, (model) => ( + + )), + ); +} + +function ScriptElement({ model }: { model: ReactPyVdom }) { + const ref = useRef(null); + + React.useEffect(() => { + if (!ref.current) { + return; + } + const scriptContent = model?.children?.filter( + (value): value is string => typeof value == "string", + )[0]; + + let scriptElement: HTMLScriptElement; + if (model.attributes) { + scriptElement = document.createElement("script"); + for (const [k, v] of Object.entries(model.attributes)) { + scriptElement.setAttribute(k, v); + } + if (scriptContent) { + scriptElement.appendChild(document.createTextNode(scriptContent)); + } + ref.current.appendChild(scriptElement); + } else if (scriptContent) { + let scriptResult = eval(scriptContent); + if (typeof scriptResult == "function") { + return scriptResult(); + } + } + }, [model.key, ref.current]); + + return
; +} + +function ImportedElement({ model }: { model: ReactPyVdom }) { + const importSourceVdom = model.importSource; + const importSourceRef = useImportSource(model); + + if (!importSourceVdom) { + return null; + } + + const importSourceFallback = importSourceVdom.fallback; + + if (!importSourceVdom) { + // display a fallback if one was given + if (!importSourceFallback) { + return null; + } else if (typeof importSourceFallback === "string") { + return
{importSourceFallback}
; + } else { + return ; + } + } else { + return
; + } +} + +function useForceUpdate() { + const [state, setState] = useState(false); + return useCallback(() => setState(!state), []); +} + +function useImportSource(model: ReactPyVdom): MutableRefObject { + const vdomImportSource = model.importSource; + + const mountPoint = useRef(null); + const server = React.useContext(LayoutServer); + const [binding, setBinding] = useState(null); + + React.useEffect(() => { + let unmounted = false; + + if (vdomImportSource) { + loadImportSource(vdomImportSource, server).then((bind) => { + if (!unmounted && mountPoint.current) { + setBinding(bind(mountPoint.current)); + } + }); + } + + return () => { + unmounted = true; + if ( + binding && + vdomImportSource && + !vdomImportSource.unmountBeforeUpdate + ) { + binding.unmount(); + } + }; + }, [server, vdomImportSource, setBinding, mountPoint.current]); + + // this effect must run every time in case the model has changed + useEffect(() => { + if (!(binding && vdomImportSource)) { + return; + } + binding.render(model); + if (vdomImportSource.unmountBeforeUpdate) { + return binding.unmount; + } + }); + + return mountPoint; +} + +const SPECIAL_ELEMENTS = { + input: UserInputElement, + script: ScriptElement, + select: UserInputElement, + textarea: UserInputElement, +}; diff --git a/src/client/packages/client/src/contexts.js b/src/client/packages/client/src/contexts.js deleted file mode 100644 index c6ad86978..000000000 --- a/src/client/packages/client/src/contexts.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from "react"; - -export const LayoutContext = React.createContext({ - sendEvent: undefined, - loadImportSource: undefined, -}); diff --git a/src/client/packages/client/src/element-utils.js b/src/client/packages/client/src/element-utils.js deleted file mode 100644 index 01610804e..000000000 --- a/src/client/packages/client/src/element-utils.js +++ /dev/null @@ -1,82 +0,0 @@ -import { serializeEvent } from "./event-to-object.js"; - -export function createElementChildren(model, createElement) { - if (!model.children) { - return []; - } else { - return model.children - .filter((x) => x) // filter nulls - .map((child) => { - switch (typeof child) { - case "object": - return createElement(child); - case "string": - return child; - } - }); - } -} - -export function createElementAttributes(model, sendEvent) { - const attributes = Object.assign({}, model.attributes); - - if (model.eventHandlers) { - for (const [eventName, eventSpec] of Object.entries(model.eventHandlers)) { - attributes[eventName] = createEventHandler(sendEvent, eventSpec); - } - } - - return Object.fromEntries(Object.entries(attributes).map(normalizeAttribute)); -} - -function createEventHandler(sendEvent, eventSpec) { - return function () { - const data = Array.from(arguments).map((value) => { - if (typeof value === "object" && value.nativeEvent) { - if (eventSpec["preventDefault"]) { - value.preventDefault(); - } - if (eventSpec["stopPropagation"]) { - value.stopPropagation(); - } - return serializeEvent(value); - } else { - return value; - } - }); - sendEvent({ - data: data, - target: eventSpec["target"], - }); - }; -} - -function normalizeAttribute([key, value]) { - let normKey = key; - let normValue = value; - - if (key === "style" && typeof value === "object") { - normValue = Object.fromEntries( - Object.entries(value).map(([k, v]) => [snakeToCamel(k), v]) - ); - } else if ( - key.startsWith("data_") || - key.startsWith("aria_") || - DASHED_HTML_ATTRS.includes(key) - ) { - normKey = key.replaceAll("_", "-"); - } else { - normKey = snakeToCamel(key); - } - return [normKey, normValue]; -} - -function snakeToCamel(str) { - return str.replace(/([_][a-z])/g, (group) => - group.toUpperCase().replace("_", "") - ); -} - -// see list of HTML attributes with dashes in them: -// https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes#attribute_list -const DASHED_HTML_ATTRS = ["accept_charset", "http_equiv"]; diff --git a/src/client/packages/client/src/import-source.js b/src/client/packages/client/src/import-source.js deleted file mode 100644 index 7c09c7c5d..000000000 --- a/src/client/packages/client/src/import-source.js +++ /dev/null @@ -1,134 +0,0 @@ -import React from "react"; - -import { LayoutContext } from "./contexts.js"; - -import { - createElementAttributes, - createElementChildren, -} from "./element-utils.js"; - -export function useImportSource(modelImportSource) { - const layoutContext = React.useContext(LayoutContext); - const [importSource, setImportSource] = React.useState(null); - - React.useEffect(() => { - let unmounted = false; - - loadModelImportSource(layoutContext, modelImportSource).then((src) => { - if (!unmounted) { - setImportSource(src); - } - }); - - return () => { - unmounted = true; - }; - }, [layoutContext, modelImportSource, setImportSource]); - - return importSource; -} - -function loadModelImportSource(layoutContext, importSource) { - return layoutContext - .loadImportSource(importSource.source, importSource.sourceType) - .then((module) => { - if (typeof module.bind === "function") { - return { - data: importSource, - bind: (node) => { - const shortImportSource = { - source: importSource.source, - sourceType: importSource.sourceType, - }; - const binding = module.bind(node, layoutContext); - if ( - typeof binding.create === "function" && - typeof binding.render === "function" && - typeof binding.unmount === "function" - ) { - return { - render: (model) => - binding.render( - createElementFromModuleBinding( - layoutContext, - importSource, - module, - binding, - model - ) - ), - unmount: binding.unmount, - }; - } else { - console.error( - `${importSource.source} returned an impropper binding` - ); - } - }, - }; - } else { - console.error( - `${importSource.source} did not export a function 'bind'` - ); - } - }); -} - -function createElementFromModuleBinding( - layoutContext, - currentImportSource, - module, - binding, - model -) { - let type; - if (model.importSource) { - if (!isImportSourceEqual(currentImportSource, model.importSource)) { - console.error( - "Parent element import source " + - stringifyImportSource(currentImportSource) + - " does not match child's import source " + - stringifyImportSource(model.importSource) - ); - return null; - } else if (!module[model.tagName]) { - console.error( - "Module from source " + - stringifyImportSource(currentImportSource) + - ` does not export ${model.tagName}` - ); - return null; - } else { - type = module[model.tagName]; - } - } else { - type = model.tagName; - } - return binding.create( - type, - createElementAttributes(model, layoutContext.sendEvent), - createElementChildren(model, (child) => - createElementFromModuleBinding( - layoutContext, - currentImportSource, - module, - binding, - child - ) - ) - ); -} - -function isImportSourceEqual(source1, source2) { - return ( - source1.source === source2.source && - source1.sourceType === source2.sourceType - ); -} - -function stringifyImportSource(importSource) { - return JSON.stringify({ - source: importSource.source, - sourceType: importSource.sourceType, - }); -} diff --git a/src/client/packages/client/src/index.js b/src/client/packages/client/src/index.js deleted file mode 100644 index 8f7769652..000000000 --- a/src/client/packages/client/src/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./mount.js"; -export * from "./contexts.js"; -export * from "./components.js"; -export * from "./server.js"; diff --git a/src/client/packages/client/src/index.ts b/src/client/packages/client/src/index.ts new file mode 100644 index 000000000..fc24a7789 --- /dev/null +++ b/src/client/packages/client/src/index.ts @@ -0,0 +1,4 @@ +export * from "./components"; +export * from "./messages"; +export * from "./reactpy-server"; +export * from "./reactpy-vdom"; diff --git a/src/client/packages/client/src/messages.ts b/src/client/packages/client/src/messages.ts new file mode 100644 index 000000000..eb33cdea5 --- /dev/null +++ b/src/client/packages/client/src/messages.ts @@ -0,0 +1,32 @@ +import { ReactPyVdom } from "./reactpy-vdom"; + +export interface IMessage { + type: string; +} + +export type ConnectionOpenMessage = { + type: "connection-open"; +}; + +export type ConnectionCloseMessage = { + type: "connection-close"; +}; + +export type LayoutUpdateMessage = { + type: "layout-update"; + path: string; + model: ReactPyVdom; +}; + +export type LayoutEventMessage = { + type: "layout-event"; + target: string; + data: any; +}; + +export type IncomingMessage = + | LayoutUpdateMessage + | ConnectionOpenMessage + | ConnectionCloseMessage; +export type OutgoingMessage = LayoutEventMessage; +export type Message = IncomingMessage | OutgoingMessage; diff --git a/src/client/packages/client/src/mount.js b/src/client/packages/client/src/mount.js deleted file mode 100644 index c7472eb12..000000000 --- a/src/client/packages/client/src/mount.js +++ /dev/null @@ -1,105 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import { Layout } from "./components.js"; - -export function mountLayout(mountElement, layoutProps) { - ReactDOM.render(React.createElement(Layout, layoutProps), mountElement); -} - -export function mountLayoutWithWebSocket( - element, - endpoint, - loadImportSource, - maxReconnectTimeout -) { - mountLayoutWithReconnectingWebSocket( - element, - endpoint, - loadImportSource, - maxReconnectTimeout - ); -} - -function mountLayoutWithReconnectingWebSocket( - element, - endpoint, - loadImportSource, - maxReconnectTimeout, - mountState = { - everMounted: false, - reconnectAttempts: 0, - reconnectTimeoutRange: 0, - } -) { - const socket = new WebSocket(endpoint); - - const updateHookPromise = new LazyPromise(); - - socket.onopen = (event) => { - console.info(`ReactPy WebSocket connected.`); - - if (mountState.everMounted) { - ReactDOM.unmountComponentAtNode(element); - } - _resetOpenMountState(mountState); - - mountLayout(element, { - loadImportSource, - saveUpdateHook: updateHookPromise.resolve, - sendEvent: (event) => socket.send(JSON.stringify(event)), - }); - }; - - socket.onmessage = (event) => { - const message = JSON.parse(event.data); - updateHookPromise.promise.then((update) => update(message)); - }; - - socket.onclose = (event) => { - if (!maxReconnectTimeout) { - console.info(`ReactPy WebSocket connection lost.`); - return; - } - - const reconnectTimeout = _nextReconnectTimeout( - maxReconnectTimeout, - mountState - ); - - console.info( - `ReactPy WebSocket connection lost. Reconnecting in ${reconnectTimeout} seconds...` - ); - - setTimeout(function () { - mountState.reconnectAttempts++; - mountLayoutWithReconnectingWebSocket( - element, - endpoint, - loadImportSource, - maxReconnectTimeout, - mountState - ); - }, reconnectTimeout * 1000); - }; -} - -function _resetOpenMountState(mountState) { - mountState.everMounted = true; - mountState.reconnectAttempts = 0; - mountState.reconnectTimeoutRange = 0; -} - -function _nextReconnectTimeout(maxReconnectTimeout, mountState) { - const timeout = - Math.floor(Math.random() * mountState.reconnectTimeoutRange) || 1; - mountState.reconnectTimeoutRange = - (mountState.reconnectTimeoutRange + 5) % maxReconnectTimeout; - return timeout; -} - -function LazyPromise() { - this.promise = new Promise((resolve, reject) => { - this.resolve = resolve; - this.reject = reject; - }); -} diff --git a/src/client/packages/client/src/reactpy-server.ts b/src/client/packages/client/src/reactpy-server.ts new file mode 100644 index 000000000..ffb35b2d4 --- /dev/null +++ b/src/client/packages/client/src/reactpy-server.ts @@ -0,0 +1,171 @@ +import { OutgoingMessage, IncomingMessage } from "./messages"; +import { ReactPyModule } from "./reactpy-vdom"; + +export interface ReactPyServer { + receiveMessage: (type: M["type"]) => Promise; + sendMessage: (message: OutgoingMessage) => Promise; + loadModule: (moduleName: string) => Promise; +} + +export type StandardServerUrlProps = { + baseUrl: string; + routePath: string; + queryString: string; +}; + +export type StandardServerReconnectProps = { + maxInterval?: number; + maxRetries?: number; + backoffRate?: number; + intervalJitter?: number; +}; + +export class SimpleReactPyServer implements ReactPyServer { + private readonly urls: StandardServerUrls; + private readonly handlers: { + [key in IncomingMessage["type"]]?: ((message: any) => void)[]; + }; + private readonly socket: { current?: WebSocket }; + + constructor(props: { + serverApi: StandardServerUrlProps; + reconnectOptions?: StandardServerReconnectProps; + }) { + this.handlers = {}; + + this.urls = getStandardServerUrls(props.serverApi); + + this.socket = startReconnectingWebSocket({ + url: this.urls.stream, + onOpen: () => this.handleIncoming({ type: "connection-open" }), + onMessage: ({ data }) => this.handleIncoming(JSON.parse(data)), + onClose: () => this.handleIncoming({ type: "connection-close" }), + + ...props.reconnectOptions, + }); + } + + async receiveMessage(type: M["type"]): Promise { + let handlers: ((message: any) => void)[]; + if (type in this.handlers) { + handlers = this.handlers[type] as any; + } else { + handlers = []; + this.handlers[type] = handlers; + } + return new Promise(handlers.push); + } + + async sendMessage(message: OutgoingMessage): Promise { + this.socket.current?.send(JSON.stringify(message)); + } + + loadModule(moduleName: string): Promise { + return import(`${this.urls.modules}/${moduleName}`); + } + + private handleIncoming(message: IncomingMessage): void { + if (!message.type) { + console.warn("Received message without type", message); + return; + } + + const messageHandlers: ((m: any) => void)[] | undefined = + this.handlers[message.type]; + if (!messageHandlers) { + console.warn("Received message without handler", message); + return; + } + + messageHandlers.forEach((h) => h(message)); + delete this.handlers[message.type]; + } +} + +type StandardServerUrls = { + base: URL; + stream: string; + modules: string; + assets: string; +}; + +function getStandardServerUrls( + props: StandardServerUrlProps, +): StandardServerUrls { + const base = new URL(`${props.baseUrl || document.location.origin}/_reactpy`); + const modules = `${base}/modules`; + const assets = `${base}/assets`; + + const wsProtocol = `ws${base.protocol === "https:" ? "s" : ""}`; + const wsHost = `${base.host}:${base.port}`; + const wsPath = `${props.routePath || ""}${props.routePath || ""}`; + + const stream = `${wsProtocol}://${wsHost}${wsPath}`; + + return { base, modules, assets, stream }; +} + +function startReconnectingWebSocket( + props: { + url: string; + onOpen: () => void; + onMessage: (message: MessageEvent) => void; + onClose: () => void; + } & StandardServerReconnectProps, +) { + const { + maxInterval = 60000, + maxRetries = 50, + backoffRate = 1.1, + intervalJitter = 0.1, + } = props; + + let retries = 0; + let interval = 200; + const socket: { current?: WebSocket } = {}; + + const connect = () => { + socket.current = new WebSocket(props.url); + socket.current.onopen = () => { + interval = 0; + retries = 0; + props.onOpen(); + }; + socket.current.onmessage = props.onMessage; + socket.current.onclose = () => { + props.onClose(); + if (retries >= maxRetries) { + return; + } + setTimeout(connect, interval); + interval = nextInterval( + interval, + backoffRate, + maxInterval, + intervalJitter, + ); + retries++; + }; + }; + + connect(); + + return socket; +} + +function nextInterval( + currentInterval: number, + backoffRate: number, + maxInterval: number, + intervalJitter: number, +) { + return Math.min( + currentInterval * + // increase interval by backoff rate + backoffRate + + // add random jitter + (Math.random() * intervalJitter * 2 - intervalJitter), + // don't exceed max interval + maxInterval, + ); +} diff --git a/src/client/packages/client/src/reactpy-vdom.tsx b/src/client/packages/client/src/reactpy-vdom.tsx new file mode 100644 index 000000000..a94e98bcf --- /dev/null +++ b/src/client/packages/client/src/reactpy-vdom.tsx @@ -0,0 +1,263 @@ +import React, { ComponentType, ReactNode } from "react"; +import { ReactPyServer } from "./reactpy-server"; +// @ts-ignore +import serializeEvent from "event-to-object"; +import { IncomingMessage, Message, OutgoingMessage } from "./messages"; + +export async function loadImportSource( + vdomImportSource: ReactPyVdomImportSource, + server: ReactPyServer, +): Promise { + let module: ReactPyModule; + if (vdomImportSource.sourceType === "URL") { + module = await import(vdomImportSource.source); + } else { + module = await server.loadModule(vdomImportSource.source); + } + if (typeof module.bind !== "function") { + throw `${vdomImportSource.source} did not export a function 'bind'`; + } + + return (node: HTMLElement) => { + const binding = module.bind(node, { + sendMessage: server.sendMessage, + receiveMessage: server.receiveMessage, + }); + if ( + !( + typeof binding.create === "function" && + typeof binding.render === "function" && + typeof binding.unmount === "function" + ) + ) { + console.error(`${vdomImportSource.source} returned an impropper binding`); + return null; + } + + return { + render: (model) => + binding.render( + createImportSourceElement({ + server, + module, + binding, + model, + currentImportSource: vdomImportSource, + }), + ), + unmount: binding.unmount, + }; + }; +} + +function createImportSourceElement(props: { + server: ReactPyServer; + module: ReactPyModule; + binding: ReactPyModuleBinding; + model: ReactPyVdom; + currentImportSource: ReactPyVdomImportSource; +}): any { + let type: any; + if (props.model.importSource) { + if ( + !isImportSourceEqual(props.currentImportSource, props.model.importSource) + ) { + console.error( + "Parent element import source " + + stringifyImportSource(props.currentImportSource) + + " does not match child's import source " + + stringifyImportSource(props.model.importSource), + ); + return null; + } else if (!props.module[props.model.tagName]) { + console.error( + "Module from source " + + stringifyImportSource(props.currentImportSource) + + ` does not export ${props.model.tagName}`, + ); + return null; + } else { + type = props.module[props.model.tagName]; + } + } else { + type = props.model.tagName; + } + return props.binding.create( + type, + createAttributes(props.model, props.server), + createChildren(props.model, (child) => + createImportSourceElement({ + ...props, + model: child, + }), + ), + ); +} + +function isImportSourceEqual( + source1: ReactPyVdomImportSource, + source2: ReactPyVdomImportSource, +) { + return ( + source1.source === source2.source && + source1.sourceType === source2.sourceType + ); +} + +function stringifyImportSource(importSource: ReactPyVdomImportSource) { + return JSON.stringify({ + source: importSource.source, + sourceType: importSource.sourceType, + }); +} + +export function createChildren( + model: ReactPyVdom, + createChild: (model: ReactPyVdom) => Child, +): (Child | string)[] { + if (!model.children) { + return []; + } else { + return model.children + .filter((x) => x) // filter nulls + .map((child) => { + switch (typeof child) { + case "object": + return createChild(child); + case "string": + return child; + } + }); + } +} + +export function createAttributes( + model: ReactPyVdom, + server: ReactPyServer, +): { [key: string]: any } { + return Object.fromEntries( + Object.entries({ + // Normal HTML attributes + ...model.attributes, + // Construct event handlers + ...Object.fromEntries( + Object.entries(model.eventHandlers || {}).map(([name, handler]) => + createEventHandler(server, name, handler), + ), + ), + // Convert snake_case to camelCase names + }).map(normalizeAttribute), + ); +} + +function createEventHandler( + { sendMessage }: ReactPyServer, + name: string, + { target, preventDefault, stopPropagation }: ReactPyVdomEventHandler, +): [string, () => void] { + return [ + name, + function () { + const data = Array.from(arguments).map((value) => { + if (typeof value === "object" && value.nativeEvent) { + if (preventDefault) { + value.preventDefault(); + } + if (stopPropagation) { + value.stopPropagation(); + } + return serializeEvent(value); + } else { + return value; + } + }); + sendMessage({ type: "layout-event", data, target }); + }, + ]; +} + +function normalizeAttribute([key, value]: [string, any]): [string, any] { + let normKey = key; + let normValue = value; + + if (key === "style" && typeof value === "object") { + normValue = Object.fromEntries( + Object.entries(value).map(([k, v]) => [snakeToCamel(k), v]), + ); + } else if ( + key.startsWith("data_") || + key.startsWith("aria_") || + DASHED_HTML_ATTRS.includes(key) + ) { + normKey = key.split("_").join("-"); + } else { + normKey = snakeToCamel(key); + } + return [normKey, normValue]; +} + +function snakeToCamel(str: string): string { + return str.replace(/([_][a-z])/g, (group) => + group.toUpperCase().replace("_", ""), + ); +} + +// see list of HTML attributes with dashes in them: +// https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes#attribute_list +const DASHED_HTML_ATTRS = ["accept_charset", "http_equiv"]; + +export type ReactPyComponent = ComponentType<{ model: ReactPyVdom }>; + +export type ReactPyVdom = { + tagName: string; + key?: string; + attributes?: { [key: string]: string }; + children?: (ReactPyVdom | string)[]; + error?: string; + eventHandlers?: { [key: string]: ReactPyVdomEventHandler }; + importSource?: ReactPyVdomImportSource; +}; + +export type ReactPyVdomEventHandler = { + target: string; + preventDefault?: boolean; + stopPropagation?: boolean; +}; + +export type ReactPyVdomImportSource = { + sourceType: "URL" | "NAME"; + source: string; + fallback?: string | ReactPyVdom; + unmountBeforeUpdate?: boolean; +}; + +export type ReactPyModule = { + bind: ( + node: HTMLElement, + context: ReactPyModuleBindingContext, + ) => ReactPyModuleBinding; +} & { [key: string]: any }; + +export type ReactPyModuleBindingContext = { + sendMessage: (message: OutgoingMessage) => void; + receiveMessage: (type: M["type"]) => Promise; +}; + +export type ReactPyModuleBinding = { + create: ( + type: any, + props?: any, + children?: (any | string | ReactPyVdom)[], + ) => any; + render: (element: any) => void; + unmount: () => void; +}; + +export type BindImportSource = ( + node: HTMLElement, +) => ImportSourceBinding | null; + +export type ImportSourceBinding = { + render: (model: ReactPyVdom) => void; + unmount: () => void; +}; diff --git a/src/client/packages/client/src/server.js b/src/client/packages/client/src/server.js deleted file mode 100644 index 557f228f7..000000000 --- a/src/client/packages/client/src/server.js +++ /dev/null @@ -1,46 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import { mountLayoutWithWebSocket } from "./mount.js"; - -export function mountWithLayoutServer( - element, - serverInfo, - maxReconnectTimeout -) { - const loadImportSource = (source, sourceType) => - import( - sourceType == "NAME" ? serverInfo.path.module(source) : source - ).catch((error) => { - // Added a catch to silence a build warning caller so we just re-throw. - // The caller is actually responsible for catching this error. - throw error; - }); - - mountLayoutWithWebSocket( - element, - serverInfo.path.stream, - loadImportSource, - maxReconnectTimeout - ); -} - -export function LayoutServerInfo({ host, port, path, query, secure }) { - const wsProtocol = `ws${secure ? "s" : ""}`; - const wsBaseUrl = `${wsProtocol}://${host}:${port}`; - - let pathName = path || new URL(document.baseURI).pathname; - if (pathName.endsWith("/")) { - pathName = pathName.slice(0, -1); - } - - if (query) { - query = `?${query}`; - } else { - query = ""; - } - - this.path = { - stream: `${wsBaseUrl}/_reactpy/stream${pathName}${query}`, - module: (source) => `/_reactpy/modules/${source}`, - }; -} diff --git a/src/client/packages/client/tsconfig.json b/src/client/packages/client/tsconfig.json new file mode 100644 index 000000000..ef9ae16f9 --- /dev/null +++ b/src/client/packages/client/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["DOM", "DOM.Iterable", "esnext"], + "allowJs": false, + "skipLibCheck": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + }, + "include": ["src"] +} diff --git a/src/client/packages/event-to-object/package.json b/src/client/packages/event-to-object/package.json new file mode 100644 index 000000000..2e3d54ae5 --- /dev/null +++ b/src/client/packages/event-to-object/package.json @@ -0,0 +1,23 @@ +{ + "author": "Ryan Morshead", + "dependencies": {}, + "description": "Convert DOM events to JSON serializable objects", + "devDependencies": { + "prettier": "^2.5.1", + "uvu": "^0.5.1" + }, + "license": "MIT", + "main": "src/index.js", + "name": "event-to-object", + "repository": { + "type": "git", + "url": "https://github.com/reactive-python/reactpy" + }, + "scripts": { + "format": "prettier --write ./src", + "check:tests": "uvu tests", + "check:format": "prettier --check ./src", + "check:types": "echo 'no types'" + }, + "version": "0.1.0" +} diff --git a/src/client/packages/client/src/event-to-object.js b/src/client/packages/event-to-object/src/index.js similarity index 98% rename from src/client/packages/client/src/event-to-object.js rename to src/client/packages/event-to-object/src/index.js index e35f652e5..2d2eba981 100644 --- a/src/client/packages/client/src/event-to-object.js +++ b/src/client/packages/event-to-object/src/index.js @@ -1,4 +1,4 @@ -export function serializeEvent(event) { +export default function convert(event) { const data = {}; if (event.type in eventTransforms) { @@ -21,7 +21,7 @@ function serializeDomElement(element) { elementData = defaultElementTransform(element); if (element.tagName in elementTransforms) { elementTransforms[element.tagName].forEach((trans) => - Object.assign(elementData, trans(element)) + Object.assign(elementData, trans(element)), ); } } diff --git a/src/client/packages/client/tests/event-to-object.test.js b/src/client/packages/event-to-object/tests/event-to-object.test.js similarity index 98% rename from src/client/packages/client/tests/event-to-object.test.js rename to src/client/packages/event-to-object/tests/event-to-object.test.js index 596a9dd4e..d73be18ec 100644 --- a/src/client/packages/client/tests/event-to-object.test.js +++ b/src/client/packages/event-to-object/tests/event-to-object.test.js @@ -1,7 +1,7 @@ import { test } from "uvu"; import lodash from "lodash"; import * as assert from "uvu/assert"; -import { serializeEvent } from "../src/event-to-object.js"; +import serializeEvent from "../src/index.js"; import "./tooling/setup.js"; const mockBoundingRect = { @@ -52,7 +52,7 @@ function assertEqualSerializedEventData(eventData, expectedSerializedData) { assert.equal( serializeEvent(lodash.merge({}, commonEventData, eventData)), - lodash.merge({}, commonSerializedEventData, expectedSerializedData) + lodash.merge({}, commonSerializedEventData, expectedSerializedData), ); } @@ -73,7 +73,7 @@ function assertEqualSerializedEventData(eventData, expectedSerializedData) { case: `adds 'value' attribute for ${tagName} element`, tagName, output: { target: { value: allTargetData.value } }, - }) + }), ), ...["AUDIO", "VIDEO"].map((tagName) => ({ case: `adds 'currentTime' attribute for ${tagName} element`, @@ -178,7 +178,7 @@ const allEventData = { eventType, case: "composition", output: { data: "data" }, - }) + }), ), ...["keydown", "keypress", "keyup"].map((eventType) => ({ eventType, @@ -281,7 +281,7 @@ const allEventData = { metaKey: "metaKey", shiftKey: "shiftKey", }, - }) + }), ), { eventType: "scroll", @@ -309,7 +309,7 @@ const allEventData = { pseudoElement: "pseudoElement", elapsedTime: "elapsedTime", }, - }) + }), ), { eventType: "transitionend", @@ -324,7 +324,7 @@ const allEventData = { test(`serializeEvent() adds ${expectation.case} attributes`, () => { assertEqualSerializedEventData( { ...allEventData, type: expectation.eventType }, - expectation.output + expectation.output, ); }); }); @@ -346,7 +346,7 @@ test("serializeEvent() adds text of current selection", () => { { ...allEventData, type: "select" }, { selectedText: "START\nMIDDLE\n", - } + }, ); }); diff --git a/src/client/packages/client/tests/tooling/dom.js b/src/client/packages/event-to-object/tests/tooling/dom.js similarity index 100% rename from src/client/packages/client/tests/tooling/dom.js rename to src/client/packages/event-to-object/tests/tooling/dom.js diff --git a/src/client/packages/client/tests/tooling/setup.js b/src/client/packages/event-to-object/tests/tooling/setup.js similarity index 100% rename from src/client/packages/client/tests/tooling/setup.js rename to src/client/packages/event-to-object/tests/tooling/setup.js diff --git a/src/reactpy/core/vdom.py b/src/reactpy/core/vdom.py index 9579560b9..7daaae6c0 100644 --- a/src/reactpy/core/vdom.py +++ b/src/reactpy/core/vdom.py @@ -84,7 +84,7 @@ }, "unmountBeforeUpdate": {"type": "boolean"}, }, - "required": ["source"], + "required": ["source", "sourceType"], }, "elementOrString": { "type": ["object", "string"], From cb7b4e3e3b39be9d12cbfd33aacdfd78178c56dc Mon Sep 17 00:00:00 2001 From: rmorshea Date: Tue, 7 Mar 2023 10:46:07 -0800 Subject: [PATCH 02/16] minor fixes --- src/client/package.json | 2 +- src/client/packages/app/src/index.tsx | 4 +-- src/client/packages/client/src/components.tsx | 6 ++-- src/client/packages/client/src/index.ts | 2 +- .../{reactpy-server.ts => reactpy-client.ts} | 32 ++++++++----------- .../packages/client/src/reactpy-vdom.tsx | 10 +++--- 6 files changed, 26 insertions(+), 30 deletions(-) rename src/client/packages/client/src/{reactpy-server.ts => reactpy-client.ts} (82%) diff --git a/src/client/package.json b/src/client/package.json index d8a74ce43..ea749e4e4 100644 --- a/src/client/package.json +++ b/src/client/package.json @@ -12,7 +12,7 @@ "publish": "npm --workspaces publish", "format": "npm --workspaces run format", "check:format": "npm --workspaces run check-format", - "check:tests": "npm --workspaces check:tests", + "check:tests": "npm --workspaces run check:tests", "check:types": "npm --workspaces run check:types" }, "version": "1.0.0", diff --git a/src/client/packages/app/src/index.tsx b/src/client/packages/app/src/index.tsx index a2cfa4b50..7c1d70618 100644 --- a/src/client/packages/app/src/index.tsx +++ b/src/client/packages/app/src/index.tsx @@ -1,9 +1,9 @@ import React from "react"; import { render } from "react-dom"; -import { Layout, SimpleReactPyServer } from "@reactpy/client"; +import { Layout, SimpleReactPyClient } from "@reactpy/client"; export function mount(root: HTMLElement) { - const server = new SimpleReactPyServer({ + const server = new SimpleReactPyClient({ serverApi: { baseUrl: document.location.origin, routePath: document.location.pathname, diff --git a/src/client/packages/client/src/components.tsx b/src/client/packages/client/src/components.tsx index fdddabb58..ae417ead0 100644 --- a/src/client/packages/client/src/components.tsx +++ b/src/client/packages/client/src/components.tsx @@ -24,11 +24,11 @@ import { loadImportSource, ImportSourceBinding, } from "./reactpy-vdom"; -import { ReactPyServer } from "./reactpy-server"; +import { ReactPyClient } from "./reactpy-client"; -const LayoutServer = createContext(null as any); +const LayoutServer = createContext(null as any); -export function Layout(props: { server: ReactPyServer }): JSX.Element { +export function Layout(props: { server: ReactPyClient }): JSX.Element { const currentModel: ReactPyVdom = useState({ tagName: "" })[0]; const forceUpdate = useForceUpdate(); diff --git a/src/client/packages/client/src/index.ts b/src/client/packages/client/src/index.ts index fc24a7789..a46dce8b7 100644 --- a/src/client/packages/client/src/index.ts +++ b/src/client/packages/client/src/index.ts @@ -1,4 +1,4 @@ export * from "./components"; export * from "./messages"; -export * from "./reactpy-server"; +export * from "./reactpy-client"; export * from "./reactpy-vdom"; diff --git a/src/client/packages/client/src/reactpy-server.ts b/src/client/packages/client/src/reactpy-client.ts similarity index 82% rename from src/client/packages/client/src/reactpy-server.ts rename to src/client/packages/client/src/reactpy-client.ts index ffb35b2d4..e8aad1342 100644 --- a/src/client/packages/client/src/reactpy-server.ts +++ b/src/client/packages/client/src/reactpy-client.ts @@ -1,39 +1,39 @@ import { OutgoingMessage, IncomingMessage } from "./messages"; import { ReactPyModule } from "./reactpy-vdom"; -export interface ReactPyServer { +export interface ReactPyClient { receiveMessage: (type: M["type"]) => Promise; sendMessage: (message: OutgoingMessage) => Promise; loadModule: (moduleName: string) => Promise; } -export type StandardServerUrlProps = { +type UrlProps = { baseUrl: string; routePath: string; queryString: string; }; -export type StandardServerReconnectProps = { +type ReconnectProps = { maxInterval?: number; maxRetries?: number; backoffRate?: number; intervalJitter?: number; }; -export class SimpleReactPyServer implements ReactPyServer { - private readonly urls: StandardServerUrls; +export class SimpleReactPyClient implements ReactPyClient { + private readonly urls: ServerUrls; private readonly handlers: { [key in IncomingMessage["type"]]?: ((message: any) => void)[]; }; private readonly socket: { current?: WebSocket }; constructor(props: { - serverApi: StandardServerUrlProps; - reconnectOptions?: StandardServerReconnectProps; + serverApi: UrlProps; + reconnectOptions?: ReconnectProps; }) { this.handlers = {}; - this.urls = getStandardServerUrls(props.serverApi); + this.urls = getServerUrls(props.serverApi); this.socket = startReconnectingWebSocket({ url: this.urls.stream, @@ -82,25 +82,21 @@ export class SimpleReactPyServer implements ReactPyServer { } } -type StandardServerUrls = { +type ServerUrls = { base: URL; stream: string; modules: string; assets: string; }; -function getStandardServerUrls( - props: StandardServerUrlProps, -): StandardServerUrls { +function getServerUrls(props: UrlProps): ServerUrls { const base = new URL(`${props.baseUrl || document.location.origin}/_reactpy`); const modules = `${base}/modules`; const assets = `${base}/assets`; - const wsProtocol = `ws${base.protocol === "https:" ? "s" : ""}`; - const wsHost = `${base.host}:${base.port}`; - const wsPath = `${props.routePath || ""}${props.routePath || ""}`; - - const stream = `${wsProtocol}://${wsHost}${wsPath}`; + const streamProtocol = `ws${base.protocol === "https:" ? "s" : ""}`; + const streamPath = `${base.pathname}/stream${props.routePath || ""}`; + const stream = `${streamProtocol}://${base.host}${streamPath}`; return { base, modules, assets, stream }; } @@ -111,7 +107,7 @@ function startReconnectingWebSocket( onOpen: () => void; onMessage: (message: MessageEvent) => void; onClose: () => void; - } & StandardServerReconnectProps, + } & ReconnectProps, ) { const { maxInterval = 60000, diff --git a/src/client/packages/client/src/reactpy-vdom.tsx b/src/client/packages/client/src/reactpy-vdom.tsx index a94e98bcf..44f7ef3d3 100644 --- a/src/client/packages/client/src/reactpy-vdom.tsx +++ b/src/client/packages/client/src/reactpy-vdom.tsx @@ -1,12 +1,12 @@ import React, { ComponentType, ReactNode } from "react"; -import { ReactPyServer } from "./reactpy-server"; +import { ReactPyClient } from "./reactpy-client"; // @ts-ignore import serializeEvent from "event-to-object"; import { IncomingMessage, Message, OutgoingMessage } from "./messages"; export async function loadImportSource( vdomImportSource: ReactPyVdomImportSource, - server: ReactPyServer, + server: ReactPyClient, ): Promise { let module: ReactPyModule; if (vdomImportSource.sourceType === "URL") { @@ -51,7 +51,7 @@ export async function loadImportSource( } function createImportSourceElement(props: { - server: ReactPyServer; + server: ReactPyClient; module: ReactPyModule; binding: ReactPyModuleBinding; model: ReactPyVdom; @@ -133,7 +133,7 @@ export function createChildren( export function createAttributes( model: ReactPyVdom, - server: ReactPyServer, + server: ReactPyClient, ): { [key: string]: any } { return Object.fromEntries( Object.entries({ @@ -151,7 +151,7 @@ export function createAttributes( } function createEventHandler( - { sendMessage }: ReactPyServer, + { sendMessage }: ReactPyClient, name: string, { target, preventDefault, stopPropagation }: ReactPyVdomEventHandler, ): [string, () => void] { From d812d93ecb64e2d09b4837470db899beb26754d3 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Tue, 7 Mar 2023 10:53:22 -0800 Subject: [PATCH 03/16] fix js tests --- .pre-commit-config.yaml | 2 +- noxfile.py | 5 + src/client/package-lock.json | 214 +++++++++++------- src/client/package.json | 2 +- src/client/packages/app/package.json | 2 +- src/client/packages/client/package.json | 13 +- src/client/packages/client/src/components.tsx | 6 +- .../packages/client/src/reactpy-client.ts | 2 +- .../packages/event-to-object/package.json | 18 +- tests/test_sample.py | 2 +- 10 files changed, 160 insertions(+), 106 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9a67422d5..b28e5d978 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,6 +9,6 @@ repos: - id: isort name: isort - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.0.0-alpha.4 + rev: v3.0.0-alpha.6 hooks: - id: prettier diff --git a/noxfile.py b/noxfile.py index da6ed2cba..37fa4db1a 100644 --- a/noxfile.py +++ b/noxfile.py @@ -246,6 +246,11 @@ def check_javascript_types(session: Session) -> None: session.run("npm", "run", "check:types", external=True) +@group.session +def check_javascript_build(session: Session) -> None: + session.run("npm", "run", "build", external=True) + + @group.session def build_javascript(session: Session) -> None: """Build javascript client code""" diff --git a/src/client/package-lock.json b/src/client/package-lock.json index 67611e9c6..0e440f3a3 100644 --- a/src/client/package-lock.json +++ b/src/client/package-lock.json @@ -316,18 +316,18 @@ } }, "node_modules/dequal": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz", - "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", "dev": true, "engines": { "node": ">=0.3.1" @@ -1055,9 +1055,9 @@ } }, "node_modules/kleur": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz", - "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", "dev": true, "engines": { "node": ">=6" @@ -1115,9 +1115,9 @@ } }, "node_modules/mri": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz", - "integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", "dev": true, "engines": { "node": ">=4" @@ -1254,18 +1254,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prettier": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", - "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/prop-types": { "version": "15.7.2", "license": "MIT", @@ -1453,15 +1441,15 @@ } }, "node_modules/sade": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz", - "integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", "dev": true, "dependencies": { "mri": "^1.1.0" }, "engines": { - "node": ">= 6" + "node": ">=6" } }, "node_modules/safe-buffer": { @@ -1567,15 +1555,6 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, - "node_modules/totalist": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-2.0.0.tgz", - "integrity": "sha512-+Y17F0YzxfACxTyjfhnJQEe7afPA0GSpYlFkl2VFMxYP7jshQf9gXV7cH47EfToBumFThfKBvfAcoUn6fdNeRQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/tough-cookie": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", @@ -1699,16 +1678,15 @@ } }, "node_modules/uvu": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.1.tgz", - "integrity": "sha512-JGxttnOGDFs77FaZ0yMUHIzczzQ5R1IlDeNW6Wymw6gAscwMdAffVOP6TlxLIfReZyK8tahoGwWZaTCJzNFDkg==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", "dev": true, "dependencies": { "dequal": "^2.0.0", "diff": "^5.0.0", "kleur": "^4.0.3", - "sade": "^1.7.3", - "totalist": "^2.0.0" + "sade": "^1.7.3" }, "bin": { "uvu": "bin.js" @@ -1978,7 +1956,7 @@ }, "devDependencies": { "@types/react-dom": "^18.0.11", - "prettier": "^2.5.1", + "prettier": "^3.0.0-alpha.6", "typescript": "^4.9.5" } }, @@ -1986,10 +1964,25 @@ "resolved": "packages/app/packages/client", "link": true }, + "packages/app/node_modules/prettier": { + "version": "3.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", + "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "packages/app/packages/client": {}, "packages/client": { "name": "@reactpy/client", - "version": "0.45.0", + "version": "0.1.0", "license": "MIT", "dependencies": { "event-to-object": "file:packages/event-to-object", @@ -1999,9 +1992,7 @@ "@types/json-pointer": "^1.0.31", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", - "jsdom": "16.5.0", - "lodash": "^4.17.21", - "prettier": "^2.5.1", + "prettier": "^3.0.0-alpha.6", "typescript": "^4.9.5" }, "peerDependencies": { @@ -2013,15 +2004,56 @@ "resolved": "packages/client/packages/event-to-object", "link": true }, + "packages/client/node_modules/prettier": { + "version": "3.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", + "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "packages/client/packages/event-to-object": {}, "packages/event-to-object": { "version": "0.1.0", "license": "MIT", + "dependencies": { + "event-to-object": "file:packages/event-to-object", + "json-pointer": "^0.6.2" + }, "devDependencies": { - "prettier": "^2.5.1", + "jsdom": "16.5.0", + "lodash": "^4.17.21", + "prettier": "^3.0.0-alpha.6", "uvu": "^0.5.1" } }, + "packages/event-to-object/node_modules/event-to-object": { + "resolved": "packages/event-to-object/packages/event-to-object", + "link": true + }, + "packages/event-to-object/node_modules/prettier": { + "version": "3.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", + "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "packages/event-to-object/packages/event-to-object": {}, "packages/idom-app-react": { "name": "@reactpy/app", "version": "1.0.0-a5", @@ -2080,12 +2112,18 @@ "@reactpy/client": "file:packages/client", "@types/react-dom": "^18.0.11", "preact": "^10.7.0", - "prettier": "^2.5.1", + "prettier": "^3.0.0-alpha.6", "typescript": "^4.9.5" }, "dependencies": { "@reactpy/client": { "version": "file:packages/app/packages/client" + }, + "prettier": { + "version": "3.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", + "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==", + "dev": true } } }, @@ -2096,15 +2134,19 @@ "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "event-to-object": "file:packages/event-to-object", - "jsdom": "16.5.0", "json-pointer": "^0.6.2", - "lodash": "^4.17.21", - "prettier": "^2.5.1", + "prettier": "^3.0.0-alpha.6", "typescript": "^4.9.5" }, "dependencies": { "event-to-object": { "version": "file:packages/client/packages/event-to-object" + }, + "prettier": { + "version": "3.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", + "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==", + "dev": true } } }, @@ -2334,15 +2376,15 @@ "dev": true }, "dequal": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz", - "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true }, "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", "dev": true }, "domexception": { @@ -2555,8 +2597,23 @@ "event-to-object": { "version": "file:packages/event-to-object", "requires": { - "prettier": "^2.5.1", + "event-to-object": "file:packages/event-to-object", + "jsdom": "16.5.0", + "json-pointer": "^0.6.2", + "lodash": "^4.17.21", + "prettier": "^3.0.0-alpha.6", "uvu": "^0.5.1" + }, + "dependencies": { + "event-to-object": { + "version": "file:packages/event-to-object/packages/event-to-object" + }, + "prettier": { + "version": "3.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", + "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==", + "dev": true + } } }, "extend": { @@ -2805,9 +2862,9 @@ } }, "kleur": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz", - "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", "dev": true }, "levn": { @@ -2849,9 +2906,9 @@ } }, "mri": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz", - "integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", "dev": true }, "nanoid": { @@ -2944,12 +3001,6 @@ "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", "dev": true }, - "prettier": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", - "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", - "dev": true - }, "prop-types": { "version": "15.7.2", "peer": true, @@ -3096,9 +3147,9 @@ } }, "sade": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz", - "integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", "dev": true, "requires": { "mri": "^1.1.0" @@ -3174,12 +3225,6 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, - "totalist": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-2.0.0.tgz", - "integrity": "sha512-+Y17F0YzxfACxTyjfhnJQEe7afPA0GSpYlFkl2VFMxYP7jshQf9gXV7cH47EfToBumFThfKBvfAcoUn6fdNeRQ==", - "dev": true - }, "tough-cookie": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", @@ -3274,16 +3319,15 @@ "dev": true }, "uvu": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.1.tgz", - "integrity": "sha512-JGxttnOGDFs77FaZ0yMUHIzczzQ5R1IlDeNW6Wymw6gAscwMdAffVOP6TlxLIfReZyK8tahoGwWZaTCJzNFDkg==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", "dev": true, "requires": { "dequal": "^2.0.0", "diff": "^5.0.0", "kleur": "^4.0.3", - "sade": "^1.7.3", - "totalist": "^2.0.0" + "sade": "^1.7.3" } }, "verror": { diff --git a/src/client/package.json b/src/client/package.json index ea749e4e4..ff6a2a8bd 100644 --- a/src/client/package.json +++ b/src/client/package.json @@ -11,7 +11,7 @@ "build": "vite build", "publish": "npm --workspaces publish", "format": "npm --workspaces run format", - "check:format": "npm --workspaces run check-format", + "check:format": "npm --workspaces run check:format", "check:tests": "npm --workspaces run check:tests", "check:types": "npm --workspaces run check:types" }, diff --git a/src/client/packages/app/package.json b/src/client/packages/app/package.json index d8432a620..9b886fffd 100644 --- a/src/client/packages/app/package.json +++ b/src/client/packages/app/package.json @@ -6,7 +6,7 @@ }, "description": "A client application for ReactPy implemented in React", "devDependencies": { - "prettier": "^2.5.1", + "prettier": "^3.0.0-alpha.6", "typescript": "^4.9.5", "@types/react-dom": "^18.0.11" }, diff --git a/src/client/packages/client/package.json b/src/client/packages/client/package.json index 6ae942190..dbd036c52 100644 --- a/src/client/packages/client/package.json +++ b/src/client/packages/client/package.json @@ -4,21 +4,16 @@ "event-to-object": "file:packages/event-to-object", "json-pointer": "^0.6.2" }, + "main": "src/index.ts", "description": "A client for ReactPy implemented in React", "devDependencies": { "@types/json-pointer": "^1.0.31", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", - "jsdom": "16.5.0", - "lodash": "^4.17.21", - "prettier": "^2.5.1", + "prettier": "^3.0.0-alpha.6", "typescript": "^4.9.5" }, - "files": [ - "src/**/*.js" - ], "license": "MIT", - "main": "src/index.js", "name": "@reactpy/client", "peerDependencies": { "react": ">=16", @@ -29,8 +24,8 @@ "url": "https://github.com/reactive-python/reactpy" }, "scripts": { - "format": "prettier --write ./src ./tests", - "check:format": "prettier --check ./src ./tests", + "format": "prettier --write ./src", + "check:format": "prettier --check ./src", "check:tests": "echo 'no tests'", "check:types": "tsc" }, diff --git a/src/client/packages/client/src/components.tsx b/src/client/packages/client/src/components.tsx index ae417ead0..12b220330 100644 --- a/src/client/packages/client/src/components.tsx +++ b/src/client/packages/client/src/components.tsx @@ -36,7 +36,11 @@ export function Layout(props: { server: ReactPyClient }): JSX.Element { props.server .receiveMessage("layout-update") .then(({ path, model }) => { - setJsonPointer(currentModel, path, model); + if (path === "") { + Object.assign(currentModel, model); + } else { + setJsonPointer(currentModel, path, model); + } forceUpdate(); }); }, [currentModel, props.server]); diff --git a/src/client/packages/client/src/reactpy-client.ts b/src/client/packages/client/src/reactpy-client.ts index e8aad1342..bca724c93 100644 --- a/src/client/packages/client/src/reactpy-client.ts +++ b/src/client/packages/client/src/reactpy-client.ts @@ -53,7 +53,7 @@ export class SimpleReactPyClient implements ReactPyClient { handlers = []; this.handlers[type] = handlers; } - return new Promise(handlers.push); + return new Promise((r) => handlers.push(r)); } async sendMessage(message: OutgoingMessage): Promise { diff --git a/src/client/packages/event-to-object/package.json b/src/client/packages/event-to-object/package.json index 2e3d54ae5..946aa21a2 100644 --- a/src/client/packages/event-to-object/package.json +++ b/src/client/packages/event-to-object/package.json @@ -1,9 +1,14 @@ { "author": "Ryan Morshead", - "dependencies": {}, - "description": "Convert DOM events to JSON serializable objects", + "dependencies": { + "event-to-object": "file:packages/event-to-object", + "json-pointer": "^0.6.2" + }, + "description": "A client for ReactPy implemented in React", "devDependencies": { - "prettier": "^2.5.1", + "jsdom": "16.5.0", + "lodash": "^4.17.21", + "prettier": "^3.0.0-alpha.6", "uvu": "^0.5.1" }, "license": "MIT", @@ -14,10 +19,11 @@ "url": "https://github.com/reactive-python/reactpy" }, "scripts": { - "format": "prettier --write ./src", + "format": "prettier --write ./src ./tests", + "check:format": "prettier --check ./src ./tests", "check:tests": "uvu tests", - "check:format": "prettier --check ./src", - "check:types": "echo 'no types'" + "check:types": "tsc" }, + "type": "module", "version": "0.1.0" } diff --git a/tests/test_sample.py b/tests/test_sample.py index e7a281765..0044aa6ff 100644 --- a/tests/test_sample.py +++ b/tests/test_sample.py @@ -4,6 +4,6 @@ async def test_sample_app(display: DisplayFixture): await display.show(SampleApp) - + breakpoint() h1 = await display.page.wait_for_selector("h1") assert (await h1.text_content()) == "Sample Application" From 5d79d68ba48bd42207df73c5915468da6d50cd87 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Tue, 7 Mar 2023 22:49:31 -0800 Subject: [PATCH 04/16] misc fixes --- src/client/packages/app/src/index.tsx | 6 +- src/client/packages/client/src/components.tsx | 71 +++++----- src/client/packages/client/src/logger.ts | 5 + .../packages/client/src/reactpy-client.ts | 128 +++++++++++++----- .../packages/client/src/reactpy-vdom.tsx | 48 ++++--- src/reactpy/core/vdom.py | 2 +- tests/test_backend/test_utils.py | 2 +- tests/test_sample.py | 1 - 8 files changed, 157 insertions(+), 106 deletions(-) create mode 100644 src/client/packages/client/src/logger.ts diff --git a/src/client/packages/app/src/index.tsx b/src/client/packages/app/src/index.tsx index 7c1d70618..ca6d93711 100644 --- a/src/client/packages/app/src/index.tsx +++ b/src/client/packages/app/src/index.tsx @@ -3,12 +3,12 @@ import { render } from "react-dom"; import { Layout, SimpleReactPyClient } from "@reactpy/client"; export function mount(root: HTMLElement) { - const server = new SimpleReactPyClient({ - serverApi: { + const client = new SimpleReactPyClient({ + serverLocation: { baseUrl: document.location.origin, routePath: document.location.pathname, queryString: document.location.search, }, }); - render(, root); + render(, root); } diff --git a/src/client/packages/client/src/components.tsx b/src/client/packages/client/src/components.tsx index 12b220330..b6c5f6122 100644 --- a/src/client/packages/client/src/components.tsx +++ b/src/client/packages/client/src/components.tsx @@ -9,7 +9,6 @@ import React, { Fragment, ComponentType, MutableRefObject, - SyntheticEvent, ChangeEvent, } from "react"; // @ts-ignore @@ -20,35 +19,37 @@ import { ReactPyComponent, createChildren, createAttributes, - ReactPyVdomImportSource, loadImportSource, ImportSourceBinding, } from "./reactpy-vdom"; import { ReactPyClient } from "./reactpy-client"; -const LayoutServer = createContext(null as any); +const ClientContext = createContext(null as any); -export function Layout(props: { server: ReactPyClient }): JSX.Element { +export function Layout(props: { client: ReactPyClient }): JSX.Element { const currentModel: ReactPyVdom = useState({ tagName: "" })[0]; const forceUpdate = useForceUpdate(); useEffect(() => { - props.server - .receiveMessage("layout-update") - .then(({ path, model }) => { + props.client.onMessage( + "layout-update", + ({ path, model }) => { if (path === "") { Object.assign(currentModel, model); } else { setJsonPointer(currentModel, path, model); } forceUpdate(); - }); - }, [currentModel, props.server]); + }, + ); + props.client.start(); + return () => props.client.stop(); + }, [currentModel, props.client]); return ( - + - + ); } @@ -75,33 +76,25 @@ export function Element({ model }: { model: ReactPyVdom }): JSX.Element | null { } function StandardElement({ model }: { model: ReactPyVdom }) { - const server = React.useContext(LayoutServer); - - let type: string | ComponentType; - if (model.tagName == "") { - type = Fragment; - } else { - type = model.tagName; - } - + const client = React.useContext(ClientContext); // Use createElement here to avoid warning about variable numbers of children not - // having keys. Warning about this must now be the responsibility of the server + // having keys. Warning about this must now be the responsibility of the client // providing the models instead of the client rendering them. return createElement( - type, - createAttributes(model, server), - ...createChildren(model, (model) => ( - - )), + model.tagName === "" ? Fragment : model.tagName, + createAttributes(model, client), + ...createChildren(model, (child) => { + return ; + }), ); } function UserInputElement({ model }: { model: ReactPyVdom }): JSX.Element { - const server = useContext(LayoutServer); - const props = createAttributes(model, server); + const client = useContext(ClientContext); + const props = createAttributes(model, client); const [value, setValue] = React.useState(props.value); - // honor changes to value from the server via props + // honor changes to value from the client via props React.useEffect(() => setValue(props.value), [props.value]); const givenOnChange = props.onChange; @@ -109,20 +102,20 @@ function UserInputElement({ model }: { model: ReactPyVdom }): JSX.Element { props.onChange = (event: ChangeEvent) => { // immediately update the value to give the user feedback setValue(event.target.value); - // allow the server to respond (and possibly change the value) + // allow the client to respond (and possibly change the value) givenOnChange(event); }; } // Use createElement here to avoid warning about variable numbers of children not - // having keys. Warning about this must now be the responsibility of the server + // having keys. Warning about this must now be the responsibility of the client // providing the models instead of the client rendering them. - return React.createElement( + return createElement( model.tagName, // overwrite { ...props, value }, - ...createChildren(model, (model) => ( - + ...createChildren(model, (child) => ( + )), ); } @@ -184,22 +177,22 @@ function ImportedElement({ model }: { model: ReactPyVdom }) { } function useForceUpdate() { - const [state, setState] = useState(false); - return useCallback(() => setState(!state), []); + const [, setState] = useState(false); + return () => setState((old) => !old); } function useImportSource(model: ReactPyVdom): MutableRefObject { const vdomImportSource = model.importSource; const mountPoint = useRef(null); - const server = React.useContext(LayoutServer); + const client = React.useContext(ClientContext); const [binding, setBinding] = useState(null); React.useEffect(() => { let unmounted = false; if (vdomImportSource) { - loadImportSource(vdomImportSource, server).then((bind) => { + loadImportSource(vdomImportSource, client).then((bind) => { if (!unmounted && mountPoint.current) { setBinding(bind(mountPoint.current)); } @@ -216,7 +209,7 @@ function useImportSource(model: ReactPyVdom): MutableRefObject { binding.unmount(); } }; - }, [server, vdomImportSource, setBinding, mountPoint.current]); + }, [client, vdomImportSource, setBinding, mountPoint.current]); // this effect must run every time in case the model has changed useEffect(() => { diff --git a/src/client/packages/client/src/logger.ts b/src/client/packages/client/src/logger.ts new file mode 100644 index 000000000..4c4cdd264 --- /dev/null +++ b/src/client/packages/client/src/logger.ts @@ -0,0 +1,5 @@ +export default { + log: (...args: any[]): void => console.log("[ReactPy]", ...args), + warn: (...args: any[]): void => console.warn("[ReactPy]", ...args), + error: (...args: any[]): void => console.error("[ReactPy]", ...args), +}; diff --git a/src/client/packages/client/src/reactpy-client.ts b/src/client/packages/client/src/reactpy-client.ts index bca724c93..6f2d70b36 100644 --- a/src/client/packages/client/src/reactpy-client.ts +++ b/src/client/packages/client/src/reactpy-client.ts @@ -1,9 +1,15 @@ import { OutgoingMessage, IncomingMessage } from "./messages"; import { ReactPyModule } from "./reactpy-vdom"; +import logger from "./logger"; export interface ReactPyClient { - receiveMessage: (type: M["type"]) => Promise; - sendMessage: (message: OutgoingMessage) => Promise; + start: () => void; + stop: () => void; + onMessage: ( + type: M["type"], + handler: (message: M) => void, + ) => void; + sendMessage: (message: OutgoingMessage) => void; loadModule: (moduleName: string) => Promise; } @@ -21,42 +27,66 @@ type ReconnectProps = { }; export class SimpleReactPyClient implements ReactPyClient { + private resolveShouldOpen?: (value: unknown) => void; + private resolveShouldClose?: (value: unknown) => void; private readonly urls: ServerUrls; private readonly handlers: { - [key in IncomingMessage["type"]]?: ((message: any) => void)[]; + [key in IncomingMessage["type"]]: ((message: any) => void)[]; }; private readonly socket: { current?: WebSocket }; constructor(props: { - serverApi: UrlProps; + serverLocation: UrlProps; reconnectOptions?: ReconnectProps; }) { - this.handlers = {}; + this.handlers = { + "connection-open": [], + "connection-close": [], + "layout-update": [], + }; + + this.urls = getServerUrls(props.serverLocation); - this.urls = getServerUrls(props.serverApi); + const shouldOpen = new Promise((r) => (this.resolveShouldOpen = r)); + const shouldClose = new Promise((r) => (this.resolveShouldClose = r)); this.socket = startReconnectingWebSocket({ + shouldOpen, + shouldClose, url: this.urls.stream, onOpen: () => this.handleIncoming({ type: "connection-open" }), - onMessage: ({ data }) => this.handleIncoming(JSON.parse(data)), + onMessage: async ({ data }) => this.handleIncoming(JSON.parse(data)), onClose: () => this.handleIncoming({ type: "connection-close" }), - ...props.reconnectOptions, }); } - async receiveMessage(type: M["type"]): Promise { - let handlers: ((message: any) => void)[]; - if (type in this.handlers) { - handlers = this.handlers[type] as any; + start(): void { + if (this.resolveShouldOpen) { + logger.log("Starting ReactPy client..."); + this.resolveShouldOpen(undefined); } else { - handlers = []; - this.handlers[type] = handlers; + throw "Did not start client"; } - return new Promise((r) => handlers.push(r)); } - async sendMessage(message: OutgoingMessage): Promise { + stop(): void { + if (this.resolveShouldClose) { + logger.log("Stopping ReactPy client..."); + this.resolveShouldClose(undefined); + } else { + throw "Did not stop client"; + } + } + + onMessage( + type: M["type"], + handler: (message: M) => void, + ): void { + this.handlers[type].push(handler); + } + + sendMessage(message: OutgoingMessage): void { this.socket.current?.send(JSON.stringify(message)); } @@ -66,19 +96,18 @@ export class SimpleReactPyClient implements ReactPyClient { private handleIncoming(message: IncomingMessage): void { if (!message.type) { - console.warn("Received message without type", message); + logger.warn("Received message without type", message); return; } const messageHandlers: ((m: any) => void)[] | undefined = this.handlers[message.type]; if (!messageHandlers) { - console.warn("Received message without handler", message); + logger.warn("Received message without handler", message); return; } messageHandlers.forEach((h) => h(message)); - delete this.handlers[message.type]; } } @@ -95,14 +124,19 @@ function getServerUrls(props: UrlProps): ServerUrls { const assets = `${base}/assets`; const streamProtocol = `ws${base.protocol === "https:" ? "s" : ""}`; - const streamPath = `${base.pathname}/stream${props.routePath || ""}`; - const stream = `${streamProtocol}://${base.host}${streamPath}`; + const streamPath = rtrim( + `${base.pathname}/stream${props.routePath || ""}`, + "/", + ); + const stream = `${streamProtocol}://${base.host}${streamPath}${props.queryString}`; return { base, modules, assets, stream }; } function startReconnectingWebSocket( props: { + shouldOpen: Promise; + shouldClose: Promise; url: string; onOpen: () => void; onMessage: (message: MessageEvent) => void; @@ -116,35 +150,52 @@ function startReconnectingWebSocket( intervalJitter = 0.1, } = props; + const startInterval = 750; let retries = 0; - let interval = 200; + let interval = startInterval; + let closed = false; + let everConnected = false; const socket: { current?: WebSocket } = {}; const connect = () => { + if (closed) { + return; + } socket.current = new WebSocket(props.url); socket.current.onopen = () => { - interval = 0; + everConnected = true; + logger.log("client connected"); + interval = startInterval; retries = 0; props.onOpen(); }; socket.current.onmessage = props.onMessage; socket.current.onclose = () => { + if (!everConnected) { + logger.log("failed to connect"); + return; + } + + logger.log("disconnected"); props.onClose(); + if (retries >= maxRetries) { return; } - setTimeout(connect, interval); - interval = nextInterval( - interval, - backoffRate, - maxInterval, - intervalJitter, - ); + + const thisInterval = addJitter(interval, intervalJitter); + logger.log(`reconnecting in ${thisInterval / 1000} seconds...`); + setTimeout(connect, thisInterval); + interval = nextInterval(interval, backoffRate, maxInterval); retries++; }; }; - connect(); + props.shouldOpen.then(connect); + props.shouldClose.then(() => { + closed = true; + socket.current?.close(); + }); return socket; } @@ -153,15 +204,20 @@ function nextInterval( currentInterval: number, backoffRate: number, maxInterval: number, - intervalJitter: number, -) { +): number { return Math.min( currentInterval * // increase interval by backoff rate - backoffRate + - // add random jitter - (Math.random() * intervalJitter * 2 - intervalJitter), + backoffRate, // don't exceed max interval maxInterval, ); } + +function addJitter(interval: number, jitter: number): number { + return interval + (Math.random() * jitter * interval * 2 - jitter * interval); +} + +function rtrim(text: string, trim: string): string { + return text.replace(new RegExp(`${trim}+$`), ""); +} diff --git a/src/client/packages/client/src/reactpy-vdom.tsx b/src/client/packages/client/src/reactpy-vdom.tsx index 44f7ef3d3..db198e0b6 100644 --- a/src/client/packages/client/src/reactpy-vdom.tsx +++ b/src/client/packages/client/src/reactpy-vdom.tsx @@ -6,13 +6,13 @@ import { IncomingMessage, Message, OutgoingMessage } from "./messages"; export async function loadImportSource( vdomImportSource: ReactPyVdomImportSource, - server: ReactPyClient, + client: ReactPyClient, ): Promise { let module: ReactPyModule; if (vdomImportSource.sourceType === "URL") { module = await import(vdomImportSource.source); } else { - module = await server.loadModule(vdomImportSource.source); + module = await client.loadModule(vdomImportSource.source); } if (typeof module.bind !== "function") { throw `${vdomImportSource.source} did not export a function 'bind'`; @@ -20,8 +20,8 @@ export async function loadImportSource( return (node: HTMLElement) => { const binding = module.bind(node, { - sendMessage: server.sendMessage, - receiveMessage: server.receiveMessage, + sendMessage: client.sendMessage, + onMessage: client.onMessage, }); if ( !( @@ -38,7 +38,7 @@ export async function loadImportSource( render: (model) => binding.render( createImportSourceElement({ - server, + client, module, binding, model, @@ -51,7 +51,7 @@ export async function loadImportSource( } function createImportSourceElement(props: { - server: ReactPyClient; + client: ReactPyClient; module: ReactPyModule; binding: ReactPyModuleBinding; model: ReactPyVdom; @@ -84,7 +84,7 @@ function createImportSourceElement(props: { } return props.binding.create( type, - createAttributes(props.model, props.server), + createAttributes(props.model, props.client), createChildren(props.model, (child) => createImportSourceElement({ ...props, @@ -113,27 +113,25 @@ function stringifyImportSource(importSource: ReactPyVdomImportSource) { export function createChildren( model: ReactPyVdom, - createChild: (model: ReactPyVdom) => Child, + createChild: (child: ReactPyVdom) => Child, ): (Child | string)[] { if (!model.children) { return []; } else { - return model.children - .filter((x) => x) // filter nulls - .map((child) => { - switch (typeof child) { - case "object": - return createChild(child); - case "string": - return child; - } - }); + return model.children.map((child) => { + switch (typeof child) { + case "object": + return createChild(child); + case "string": + return child; + } + }); } } export function createAttributes( model: ReactPyVdom, - server: ReactPyClient, + client: ReactPyClient, ): { [key: string]: any } { return Object.fromEntries( Object.entries({ @@ -142,7 +140,7 @@ export function createAttributes( // Construct event handlers ...Object.fromEntries( Object.entries(model.eventHandlers || {}).map(([name, handler]) => - createEventHandler(server, name, handler), + createEventHandler(client, name, handler), ), ), // Convert snake_case to camelCase names @@ -151,7 +149,7 @@ export function createAttributes( } function createEventHandler( - { sendMessage }: ReactPyClient, + client: ReactPyClient, name: string, { target, preventDefault, stopPropagation }: ReactPyVdomEventHandler, ): [string, () => void] { @@ -171,7 +169,7 @@ function createEventHandler( return value; } }); - sendMessage({ type: "layout-event", data, target }); + client.sendMessage({ type: "layout-event", data, target }); }, ]; } @@ -225,8 +223,8 @@ export type ReactPyVdomEventHandler = { }; export type ReactPyVdomImportSource = { - sourceType: "URL" | "NAME"; source: string; + sourceType?: "URL" | "NAME"; fallback?: string | ReactPyVdom; unmountBeforeUpdate?: boolean; }; @@ -239,8 +237,8 @@ export type ReactPyModule = { } & { [key: string]: any }; export type ReactPyModuleBindingContext = { - sendMessage: (message: OutgoingMessage) => void; - receiveMessage: (type: M["type"]) => Promise; + sendMessage: ReactPyClient["sendMessage"]; + onMessage: ReactPyClient["onMessage"]; }; export type ReactPyModuleBinding = { diff --git a/src/reactpy/core/vdom.py b/src/reactpy/core/vdom.py index 7daaae6c0..9579560b9 100644 --- a/src/reactpy/core/vdom.py +++ b/src/reactpy/core/vdom.py @@ -84,7 +84,7 @@ }, "unmountBeforeUpdate": {"type": "boolean"}, }, - "required": ["source", "sourceType"], + "required": ["source"], }, "elementOrString": { "type": ["object", "string"], diff --git a/tests/test_backend/test_utils.py b/tests/test_backend/test_utils.py index 34f7e4024..2b36966f5 100644 --- a/tests/test_backend/test_utils.py +++ b/tests/test_backend/test_utils.py @@ -24,7 +24,7 @@ def test_find_available_port(): find_available_port("localhost", port_min=0, port_max=0) -async def test_run(page: Page, exit_stack: ExitStack): +async def test_run(page: Page): host = "127.0.0.1" port = find_available_port(host) url = f"http://{host}:{port}" diff --git a/tests/test_sample.py b/tests/test_sample.py index 0044aa6ff..b92e89789 100644 --- a/tests/test_sample.py +++ b/tests/test_sample.py @@ -4,6 +4,5 @@ async def test_sample_app(display: DisplayFixture): await display.show(SampleApp) - breakpoint() h1 = await display.page.wait_for_selector("h1") assert (await h1.text_content()) == "Sample Application" From 26fa732ce2fdebfed956a570c8bdb56f997e30f4 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Wed, 8 Mar 2023 19:00:26 -0800 Subject: [PATCH 05/16] rewrite event-to-object --- src/client/package-lock.json | 426 ++++++++++-------- src/client/packages/app/tsconfig.json | 17 +- .../packages/client/src/reactpy-vdom.tsx | 22 +- src/client/packages/client/tsconfig.json | 17 +- .../packages/event-to-object/package.json | 6 +- .../packages/event-to-object/src/events.ts | 283 ++++++++++++ .../packages/event-to-object/src/index.js | 240 ---------- .../packages/event-to-object/src/index.ts | 422 +++++++++++++++++ .../tests/event-to-object.test.js | 2 +- .../packages/event-to-object/tsconfig.json | 4 + src/client/tsconfig.json | 18 + 11 files changed, 971 insertions(+), 486 deletions(-) create mode 100644 src/client/packages/event-to-object/src/events.ts delete mode 100644 src/client/packages/event-to-object/src/index.js create mode 100644 src/client/packages/event-to-object/src/index.ts create mode 100644 src/client/packages/event-to-object/tsconfig.json create mode 100644 src/client/tsconfig.json diff --git a/src/client/package-lock.json b/src/client/package-lock.json index 0e440f3a3..24f6f31ea 100644 --- a/src/client/package-lock.json +++ b/src/client/package-lock.json @@ -15,9 +15,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.12.tgz", - "integrity": "sha512-IC7TqIqiyE0MmvAhWkl/8AEzpOtbhRNDo7aph47We1NbE5w2bt/Q+giAhe0YYeVpYnIhGMcuZY92qDK6dQauvA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", "cpu": [ "arm" ], @@ -31,9 +31,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.12.tgz", - "integrity": "sha512-tZEowDjvU7O7I04GYvWQOS4yyP9E/7YlsB0jjw1Ycukgr2ycEzKyIk5tms5WnLBymaewc6VmRKnn5IJWgK4eFw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", "cpu": [ "loong64" ], @@ -364,10 +364,47 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/esbuild": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" + } + }, "node_modules/esbuild-android-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.12.tgz", - "integrity": "sha512-MJKXwvPY9g0rGps0+U65HlTsM1wUs9lbjt5CU19RESqycGFDRijMDQsh68MtbzkqWSRdEtiKS1mtPzKneaAI0Q==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", "cpu": [ "x64" ], @@ -381,9 +418,9 @@ } }, "node_modules/esbuild-android-arm64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.12.tgz", - "integrity": "sha512-Hc9SEcZbIMhhLcvhr1DH+lrrec9SFTiRzfJ7EGSBZiiw994gfkVV6vG0sLWqQQ6DD7V4+OggB+Hn0IRUdDUqvA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", "cpu": [ "arm64" ], @@ -397,9 +434,9 @@ } }, "node_modules/esbuild-darwin-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.12.tgz", - "integrity": "sha512-qkmqrTVYPFiePt5qFjP8w/S+GIUMbt6k8qmiPraECUWfPptaPJUGkCKrWEfYFRWB7bY23FV95rhvPyh/KARP8Q==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", "cpu": [ "x64" ], @@ -413,9 +450,9 @@ } }, "node_modules/esbuild-darwin-arm64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.12.tgz", - "integrity": "sha512-z4zPX02tQ41kcXMyN3c/GfZpIjKoI/BzHrdKUwhC/Ki5BAhWv59A9M8H+iqaRbwpzYrYidTybBwiZAIWCLJAkw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", "cpu": [ "arm64" ], @@ -429,9 +466,9 @@ } }, "node_modules/esbuild-freebsd-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.12.tgz", - "integrity": "sha512-XFL7gKMCKXLDiAiBjhLG0XECliXaRLTZh6hsyzqUqPUf/PY4C6EJDTKIeqqPKXaVJ8+fzNek88285krSz1QECw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", "cpu": [ "x64" ], @@ -445,9 +482,9 @@ } }, "node_modules/esbuild-freebsd-arm64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.12.tgz", - "integrity": "sha512-jwEIu5UCUk6TjiG1X+KQnCGISI+ILnXzIzt9yDVrhjug2fkYzlLbl0K43q96Q3KB66v6N1UFF0r5Ks4Xo7i72g==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", "cpu": [ "arm64" ], @@ -461,9 +498,9 @@ } }, "node_modules/esbuild-linux-32": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.12.tgz", - "integrity": "sha512-uSQuSEyF1kVzGzuIr4XM+v7TPKxHjBnLcwv2yPyCz8riV8VUCnO/C4BF3w5dHiVpCd5Z1cebBtZJNlC4anWpwA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", "cpu": [ "ia32" ], @@ -477,9 +514,9 @@ } }, "node_modules/esbuild-linux-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.12.tgz", - "integrity": "sha512-QcgCKb7zfJxqT9o5z9ZUeGH1k8N6iX1Y7VNsEi5F9+HzN1OIx7ESxtQXDN9jbeUSPiRH1n9cw6gFT3H4qbdvcA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", "cpu": [ "x64" ], @@ -493,9 +530,9 @@ } }, "node_modules/esbuild-linux-arm": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.12.tgz", - "integrity": "sha512-Wf7T0aNylGcLu7hBnzMvsTfEXdEdJY/hY3u36Vla21aY66xR0MS5I1Hw8nVquXjTN0A6fk/vnr32tkC/C2lb0A==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", "cpu": [ "arm" ], @@ -509,9 +546,9 @@ } }, "node_modules/esbuild-linux-arm64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.12.tgz", - "integrity": "sha512-HtNq5xm8fUpZKwWKS2/YGwSfTF+339L4aIA8yphNKYJckd5hVdhfdl6GM2P3HwLSCORS++++7++//ApEwXEuAQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", "cpu": [ "arm64" ], @@ -525,9 +562,9 @@ } }, "node_modules/esbuild-linux-mips64le": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.12.tgz", - "integrity": "sha512-Qol3+AvivngUZkTVFgLpb0H6DT+N5/zM3V1YgTkryPYFeUvuT5JFNDR3ZiS6LxhyF8EE+fiNtzwlPqMDqVcc6A==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", "cpu": [ "mips64el" ], @@ -541,9 +578,9 @@ } }, "node_modules/esbuild-linux-ppc64le": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.12.tgz", - "integrity": "sha512-4D8qUCo+CFKaR0cGXtGyVsOI7w7k93Qxb3KFXWr75An0DHamYzq8lt7TNZKoOq/Gh8c40/aKaxvcZnTgQ0TJNg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", "cpu": [ "ppc64" ], @@ -557,9 +594,9 @@ } }, "node_modules/esbuild-linux-riscv64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.12.tgz", - "integrity": "sha512-G9w6NcuuCI6TUUxe6ka0enjZHDnSVK8bO+1qDhMOCtl7Tr78CcZilJj8SGLN00zO5iIlwNRZKHjdMpfFgNn1VA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", "cpu": [ "riscv64" ], @@ -573,9 +610,9 @@ } }, "node_modules/esbuild-linux-s390x": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.12.tgz", - "integrity": "sha512-Lt6BDnuXbXeqSlVuuUM5z18GkJAZf3ERskGZbAWjrQoi9xbEIsj/hEzVnSAFLtkfLuy2DE4RwTcX02tZFunXww==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", "cpu": [ "s390x" ], @@ -589,9 +626,9 @@ } }, "node_modules/esbuild-netbsd-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.12.tgz", - "integrity": "sha512-jlUxCiHO1dsqoURZDQts+HK100o0hXfi4t54MNRMCAqKGAV33JCVvMplLAa2FwviSojT/5ZG5HUfG3gstwAG8w==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", "cpu": [ "x64" ], @@ -605,9 +642,9 @@ } }, "node_modules/esbuild-openbsd-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.12.tgz", - "integrity": "sha512-1o1uAfRTMIWNOmpf8v7iudND0L6zRBYSH45sofCZywrcf7NcZA+c7aFsS1YryU+yN7aRppTqdUK1PgbZVaB1Dw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", "cpu": [ "x64" ], @@ -621,9 +658,9 @@ } }, "node_modules/esbuild-sunos-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.12.tgz", - "integrity": "sha512-nkl251DpoWoBO9Eq9aFdoIt2yYmp4I3kvQjba3jFKlMXuqQ9A4q+JaqdkCouG3DHgAGnzshzaGu6xofGcXyPXg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", "cpu": [ "x64" ], @@ -637,9 +674,9 @@ } }, "node_modules/esbuild-windows-32": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.12.tgz", - "integrity": "sha512-WlGeBZHgPC00O08luIp5B2SP4cNCp/PcS+3Pcg31kdcJPopHxLkdCXtadLU9J82LCfw4TVls21A6lilQ9mzHrw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", "cpu": [ "ia32" ], @@ -653,9 +690,9 @@ } }, "node_modules/esbuild-windows-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.12.tgz", - "integrity": "sha512-VActO3WnWZSN//xjSfbiGOSyC+wkZtI8I4KlgrTo5oHJM6z3MZZBCuFaZHd8hzf/W9KPhF0lY8OqlmWC9HO5AA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", "cpu": [ "x64" ], @@ -669,9 +706,9 @@ } }, "node_modules/esbuild-windows-arm64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.12.tgz", - "integrity": "sha512-Of3MIacva1OK/m4zCNIvBfz8VVROBmQT+gRX6pFTLPngFYcj6TFH/12VveAqq1k9VB2l28EoVMNMUCcmsfwyuA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", "cpu": [ "arm64" ], @@ -1582,6 +1619,21 @@ "node": ">=8" } }, + "node_modules/tsm": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tsm/-/tsm-2.3.0.tgz", + "integrity": "sha512-++0HFnmmR+gMpDtKTnW3XJ4yv9kVGi20n+NfyQWB9qwJvTaIWY9kBmzek2YUQK5APTQ/1DTrXmm4QtFPmW9Rzw==", + "dev": true, + "dependencies": { + "esbuild": "^0.15.16" + }, + "bin": { + "tsm": "bin.js" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -1750,43 +1802,6 @@ } } }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.12.tgz", - "integrity": "sha512-PcT+/wyDqJQsRVhaE9uX/Oq4XLrFh0ce/bs2TJh4CSaw9xuvI+xFrH2nAYOADbhQjUgAhNWC5LKoUsakm4dxng==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.15.12", - "@esbuild/linux-loong64": "0.15.12", - "esbuild-android-64": "0.15.12", - "esbuild-android-arm64": "0.15.12", - "esbuild-darwin-64": "0.15.12", - "esbuild-darwin-arm64": "0.15.12", - "esbuild-freebsd-64": "0.15.12", - "esbuild-freebsd-arm64": "0.15.12", - "esbuild-linux-32": "0.15.12", - "esbuild-linux-64": "0.15.12", - "esbuild-linux-arm": "0.15.12", - "esbuild-linux-arm64": "0.15.12", - "esbuild-linux-mips64le": "0.15.12", - "esbuild-linux-ppc64le": "0.15.12", - "esbuild-linux-riscv64": "0.15.12", - "esbuild-linux-s390x": "0.15.12", - "esbuild-netbsd-64": "0.15.12", - "esbuild-openbsd-64": "0.15.12", - "esbuild-sunos-64": "0.15.12", - "esbuild-windows-32": "0.15.12", - "esbuild-windows-64": "0.15.12", - "esbuild-windows-arm64": "0.15.12" - } - }, "node_modules/vite/node_modules/rollup": { "version": "2.78.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", @@ -2031,6 +2046,8 @@ "jsdom": "16.5.0", "lodash": "^4.17.21", "prettier": "^3.0.0-alpha.6", + "tsm": "^2.0.0", + "typescript": "^4.9.5", "uvu": "^0.5.1" } }, @@ -2093,16 +2110,16 @@ }, "dependencies": { "@esbuild/android-arm": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.12.tgz", - "integrity": "sha512-IC7TqIqiyE0MmvAhWkl/8AEzpOtbhRNDo7aph47We1NbE5w2bt/Q+giAhe0YYeVpYnIhGMcuZY92qDK6dQauvA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", "dev": true, "optional": true }, "@esbuild/linux-loong64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.12.tgz", - "integrity": "sha512-tZEowDjvU7O7I04GYvWQOS4yyP9E/7YlsB0jjw1Ycukgr2ycEzKyIk5tms5WnLBymaewc6VmRKnn5IJWgK4eFw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", "dev": true, "optional": true }, @@ -2414,143 +2431,173 @@ "safer-buffer": "^2.1.0" } }, + "esbuild": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" + } + }, "esbuild-android-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.12.tgz", - "integrity": "sha512-MJKXwvPY9g0rGps0+U65HlTsM1wUs9lbjt5CU19RESqycGFDRijMDQsh68MtbzkqWSRdEtiKS1mtPzKneaAI0Q==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", "dev": true, "optional": true }, "esbuild-android-arm64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.12.tgz", - "integrity": "sha512-Hc9SEcZbIMhhLcvhr1DH+lrrec9SFTiRzfJ7EGSBZiiw994gfkVV6vG0sLWqQQ6DD7V4+OggB+Hn0IRUdDUqvA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", "dev": true, "optional": true }, "esbuild-darwin-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.12.tgz", - "integrity": "sha512-qkmqrTVYPFiePt5qFjP8w/S+GIUMbt6k8qmiPraECUWfPptaPJUGkCKrWEfYFRWB7bY23FV95rhvPyh/KARP8Q==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", "dev": true, "optional": true }, "esbuild-darwin-arm64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.12.tgz", - "integrity": "sha512-z4zPX02tQ41kcXMyN3c/GfZpIjKoI/BzHrdKUwhC/Ki5BAhWv59A9M8H+iqaRbwpzYrYidTybBwiZAIWCLJAkw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", "dev": true, "optional": true }, "esbuild-freebsd-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.12.tgz", - "integrity": "sha512-XFL7gKMCKXLDiAiBjhLG0XECliXaRLTZh6hsyzqUqPUf/PY4C6EJDTKIeqqPKXaVJ8+fzNek88285krSz1QECw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", "dev": true, "optional": true }, "esbuild-freebsd-arm64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.12.tgz", - "integrity": "sha512-jwEIu5UCUk6TjiG1X+KQnCGISI+ILnXzIzt9yDVrhjug2fkYzlLbl0K43q96Q3KB66v6N1UFF0r5Ks4Xo7i72g==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", "dev": true, "optional": true }, "esbuild-linux-32": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.12.tgz", - "integrity": "sha512-uSQuSEyF1kVzGzuIr4XM+v7TPKxHjBnLcwv2yPyCz8riV8VUCnO/C4BF3w5dHiVpCd5Z1cebBtZJNlC4anWpwA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", "dev": true, "optional": true }, "esbuild-linux-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.12.tgz", - "integrity": "sha512-QcgCKb7zfJxqT9o5z9ZUeGH1k8N6iX1Y7VNsEi5F9+HzN1OIx7ESxtQXDN9jbeUSPiRH1n9cw6gFT3H4qbdvcA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", "dev": true, "optional": true }, "esbuild-linux-arm": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.12.tgz", - "integrity": "sha512-Wf7T0aNylGcLu7hBnzMvsTfEXdEdJY/hY3u36Vla21aY66xR0MS5I1Hw8nVquXjTN0A6fk/vnr32tkC/C2lb0A==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", "dev": true, "optional": true }, "esbuild-linux-arm64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.12.tgz", - "integrity": "sha512-HtNq5xm8fUpZKwWKS2/YGwSfTF+339L4aIA8yphNKYJckd5hVdhfdl6GM2P3HwLSCORS++++7++//ApEwXEuAQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", "dev": true, "optional": true }, "esbuild-linux-mips64le": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.12.tgz", - "integrity": "sha512-Qol3+AvivngUZkTVFgLpb0H6DT+N5/zM3V1YgTkryPYFeUvuT5JFNDR3ZiS6LxhyF8EE+fiNtzwlPqMDqVcc6A==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", "dev": true, "optional": true }, "esbuild-linux-ppc64le": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.12.tgz", - "integrity": "sha512-4D8qUCo+CFKaR0cGXtGyVsOI7w7k93Qxb3KFXWr75An0DHamYzq8lt7TNZKoOq/Gh8c40/aKaxvcZnTgQ0TJNg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", "dev": true, "optional": true }, "esbuild-linux-riscv64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.12.tgz", - "integrity": "sha512-G9w6NcuuCI6TUUxe6ka0enjZHDnSVK8bO+1qDhMOCtl7Tr78CcZilJj8SGLN00zO5iIlwNRZKHjdMpfFgNn1VA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", "dev": true, "optional": true }, "esbuild-linux-s390x": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.12.tgz", - "integrity": "sha512-Lt6BDnuXbXeqSlVuuUM5z18GkJAZf3ERskGZbAWjrQoi9xbEIsj/hEzVnSAFLtkfLuy2DE4RwTcX02tZFunXww==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", "dev": true, "optional": true }, "esbuild-netbsd-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.12.tgz", - "integrity": "sha512-jlUxCiHO1dsqoURZDQts+HK100o0hXfi4t54MNRMCAqKGAV33JCVvMplLAa2FwviSojT/5ZG5HUfG3gstwAG8w==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", "dev": true, "optional": true }, "esbuild-openbsd-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.12.tgz", - "integrity": "sha512-1o1uAfRTMIWNOmpf8v7iudND0L6zRBYSH45sofCZywrcf7NcZA+c7aFsS1YryU+yN7aRppTqdUK1PgbZVaB1Dw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", "dev": true, "optional": true }, "esbuild-sunos-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.12.tgz", - "integrity": "sha512-nkl251DpoWoBO9Eq9aFdoIt2yYmp4I3kvQjba3jFKlMXuqQ9A4q+JaqdkCouG3DHgAGnzshzaGu6xofGcXyPXg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", "dev": true, "optional": true }, "esbuild-windows-32": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.12.tgz", - "integrity": "sha512-WlGeBZHgPC00O08luIp5B2SP4cNCp/PcS+3Pcg31kdcJPopHxLkdCXtadLU9J82LCfw4TVls21A6lilQ9mzHrw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", "dev": true, "optional": true }, "esbuild-windows-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.12.tgz", - "integrity": "sha512-VActO3WnWZSN//xjSfbiGOSyC+wkZtI8I4KlgrTo5oHJM6z3MZZBCuFaZHd8hzf/W9KPhF0lY8OqlmWC9HO5AA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", "dev": true, "optional": true }, "esbuild-windows-arm64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.12.tgz", - "integrity": "sha512-Of3MIacva1OK/m4zCNIvBfz8VVROBmQT+gRX6pFTLPngFYcj6TFH/12VveAqq1k9VB2l28EoVMNMUCcmsfwyuA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", "dev": true, "optional": true }, @@ -2602,6 +2649,8 @@ "json-pointer": "^0.6.2", "lodash": "^4.17.21", "prettier": "^3.0.0-alpha.6", + "tsm": "^2.0.0", + "typescript": "^4.9.5", "uvu": "^0.5.1" }, "dependencies": { @@ -3246,6 +3295,15 @@ "punycode": "^2.1.1" } }, + "tsm": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tsm/-/tsm-2.3.0.tgz", + "integrity": "sha512-++0HFnmmR+gMpDtKTnW3XJ4yv9kVGi20n+NfyQWB9qwJvTaIWY9kBmzek2YUQK5APTQ/1DTrXmm4QtFPmW9Rzw==", + "dev": true, + "requires": { + "esbuild": "^0.15.16" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -3354,36 +3412,6 @@ "rollup": "~2.78.0" }, "dependencies": { - "esbuild": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.12.tgz", - "integrity": "sha512-PcT+/wyDqJQsRVhaE9uX/Oq4XLrFh0ce/bs2TJh4CSaw9xuvI+xFrH2nAYOADbhQjUgAhNWC5LKoUsakm4dxng==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.15.12", - "@esbuild/linux-loong64": "0.15.12", - "esbuild-android-64": "0.15.12", - "esbuild-android-arm64": "0.15.12", - "esbuild-darwin-64": "0.15.12", - "esbuild-darwin-arm64": "0.15.12", - "esbuild-freebsd-64": "0.15.12", - "esbuild-freebsd-arm64": "0.15.12", - "esbuild-linux-32": "0.15.12", - "esbuild-linux-64": "0.15.12", - "esbuild-linux-arm": "0.15.12", - "esbuild-linux-arm64": "0.15.12", - "esbuild-linux-mips64le": "0.15.12", - "esbuild-linux-ppc64le": "0.15.12", - "esbuild-linux-riscv64": "0.15.12", - "esbuild-linux-s390x": "0.15.12", - "esbuild-netbsd-64": "0.15.12", - "esbuild-openbsd-64": "0.15.12", - "esbuild-sunos-64": "0.15.12", - "esbuild-windows-32": "0.15.12", - "esbuild-windows-64": "0.15.12", - "esbuild-windows-arm64": "0.15.12" - } - }, "rollup": { "version": "2.78.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", diff --git a/src/client/packages/app/tsconfig.json b/src/client/packages/app/tsconfig.json index ef9ae16f9..596e2cf72 100644 --- a/src/client/packages/app/tsconfig.json +++ b/src/client/packages/app/tsconfig.json @@ -1,19 +1,4 @@ { - "compilerOptions": { - "target": "esnext", - "lib": ["DOM", "DOM.Iterable", "esnext"], - "allowJs": false, - "skipLibCheck": false, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react" - }, + "extends": "../../tsconfig.json", "include": ["src"] } diff --git a/src/client/packages/client/src/reactpy-vdom.tsx b/src/client/packages/client/src/reactpy-vdom.tsx index db198e0b6..97ba79479 100644 --- a/src/client/packages/client/src/reactpy-vdom.tsx +++ b/src/client/packages/client/src/reactpy-vdom.tsx @@ -1,8 +1,6 @@ -import React, { ComponentType, ReactNode } from "react"; +import React, { ComponentType } from "react"; import { ReactPyClient } from "./reactpy-client"; -// @ts-ignore import serializeEvent from "event-to-object"; -import { IncomingMessage, Message, OutgoingMessage } from "./messages"; export async function loadImportSource( vdomImportSource: ReactPyVdomImportSource, @@ -157,17 +155,17 @@ function createEventHandler( name, function () { const data = Array.from(arguments).map((value) => { - if (typeof value === "object" && value.nativeEvent) { - if (preventDefault) { - value.preventDefault(); - } - if (stopPropagation) { - value.stopPropagation(); - } - return serializeEvent(value); - } else { + if (!(typeof value === "object" && value.nativeEvent)) { return value; } + const event = value as React.SyntheticEvent; + if (preventDefault) { + event.preventDefault(); + } + if (stopPropagation) { + event.stopPropagation(); + } + return serializeEvent(event.nativeEvent); }); client.sendMessage({ type: "layout-event", data, target }); }, diff --git a/src/client/packages/client/tsconfig.json b/src/client/packages/client/tsconfig.json index ef9ae16f9..596e2cf72 100644 --- a/src/client/packages/client/tsconfig.json +++ b/src/client/packages/client/tsconfig.json @@ -1,19 +1,4 @@ { - "compilerOptions": { - "target": "esnext", - "lib": ["DOM", "DOM.Iterable", "esnext"], - "allowJs": false, - "skipLibCheck": false, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react" - }, + "extends": "../../tsconfig.json", "include": ["src"] } diff --git a/src/client/packages/event-to-object/package.json b/src/client/packages/event-to-object/package.json index 946aa21a2..8166ec4d7 100644 --- a/src/client/packages/event-to-object/package.json +++ b/src/client/packages/event-to-object/package.json @@ -9,7 +9,9 @@ "jsdom": "16.5.0", "lodash": "^4.17.21", "prettier": "^3.0.0-alpha.6", - "uvu": "^0.5.1" + "uvu": "^0.5.1", + "tsm": "^2.0.0", + "typescript": "^4.9.5" }, "license": "MIT", "main": "src/index.js", @@ -21,7 +23,7 @@ "scripts": { "format": "prettier --write ./src ./tests", "check:format": "prettier --check ./src ./tests", - "check:tests": "uvu tests", + "check:tests": "uvu -r tsm tests", "check:types": "tsc" }, "type": "module", diff --git a/src/client/packages/event-to-object/src/events.ts b/src/client/packages/event-to-object/src/events.ts new file mode 100644 index 000000000..455283b01 --- /dev/null +++ b/src/client/packages/event-to-object/src/events.ts @@ -0,0 +1,283 @@ +// TODO +type FileListObject = any; +type DataTransferItemListObject = any; + +export type EventToObjectMap = { + event: [Event, EventObject]; + animation: [AnimationEvent, AnimationEventObject]; + clipboard: [ClipboardEvent, ClipboardEventObject]; + composition: [CompositionEvent, CompositionEventObject]; + devicemotion: [DeviceMotionEvent, DeviceMotionEventObject]; + deviceorientation: [DeviceOrientationEvent, DeviceOrientationEventObject]; + drag: [DragEvent, DragEventObject]; + focus: [FocusEvent, FocusEventObject]; + formdata: [FormDataEvent, FormDataEventObject]; + gamepad: [GamepadEvent, GamepadEventObject]; + input: [InputEvent, InputEventObject]; + keyboard: [KeyboardEvent, KeyboardEventObject]; + mouse: [MouseEvent, MouseEventObject]; + pointer: [PointerEvent, PointerEventObject]; + submit: [SubmitEvent, SubmitEventObject]; + touch: [TouchEvent, TouchEventObject]; + transition: [TransitionEvent, TransitionEventObject]; + ui: [UIEvent, UIEventObject]; + wheel: [WheelEvent, WheelEventObject]; +}; + +export interface EventObject { + bubbles: boolean; + composed: boolean; + currentTarget: EventTargetObject | null; + defaultPrevented: boolean; + eventPhase: number; + isTrusted: boolean; + target: EventTargetObject | null; + timeStamp: DOMHighResTimeStamp; + type: string; + selection: SelectionObject | null; +} + +export interface SubmitEventObject extends EventObject { + submitter: EventTargetObject; +} + +export interface InputEventObject extends UIEventObject { + data: string | null; + dataTransfer: DataTransferObject | null; + isComposing: boolean; + inputType: string; +} + +export interface GamepadEventObject extends EventObject { + gamepad: GamepadObject; +} + +export interface GamepadObject { + axes: number[]; + buttons: GamepadButtonObject[]; + connected: boolean; + hapticActuators: GamepadHapticActuatorObject[]; + id: string; + index: number; + mapping: GamepadMappingType; + timestamp: DOMHighResTimeStamp; +} + +export interface GamepadButtonObject { + pressed: boolean; + touched: boolean; + value: number; +} +export interface GamepadHapticActuatorObject { + type: string; +} + +export interface DragEventObject extends MouseEventObject { + /** Returns the DataTransfer object for the event. */ + readonly dataTransfer: DataTransferObject | null; +} + +export interface DeviceMotionEventObject extends EventObject { + acceleration: DeviceAccelerationObject | null; + accelerationIncludingGravity: DeviceAccelerationObject | null; + interval: number; + rotationRate: DeviceRotationRateObject | null; +} + +export interface DeviceAccelerationObject { + x: number | null; + y: number | null; + z: number | null; +} + +export interface DeviceRotationRateObject { + alpha: number | null; + beta: number | null; + gamma: number | null; +} + +export interface DeviceOrientationEventObject extends EventObject { + absolute: boolean; + alpha: number | null; + beta: number | null; + gamma: number | null; +} + +export interface MouseEventObject extends EventObject { + altKey: boolean; + button: number; + buttons: number; + clientX: number; + clientY: number; + ctrlKey: boolean; + metaKey: boolean; + movementX: number; + movementY: number; + offsetX: number; + offsetY: number; + pageX: number; + pageY: number; + relatedTarget: EventTargetObject | null; + screenX: number; + screenY: number; + shiftKey: boolean; + x: number; + y: number; +} + +export interface FormDataEventObject extends EventObject { + formData: FormDataObject; +} + +export type FormDataObject = [string, string | FileObject][]; + +export interface AnimationEventObject extends EventObject { + animationName: string; + elapsedTime: number; + pseudoElement: string; +} + +export interface ClipboardEventObject extends EventObject { + clipboardData: DataTransferObject | null; +} + +export interface UIEventObject extends EventObject { + detail: number; +} + +/** The DOM CompositionEvent represents events that occur due to the user indirectly + * entering text. */ +export interface CompositionEventObject extends UIEventObject { + data: string; +} + +export interface KeyboardEventObject extends UIEventObject { + altKey: boolean; + code: string; + ctrlKey: boolean; + isComposing: boolean; + key: string; + location: number; + metaKey: boolean; + repeat: boolean; + shiftKey: boolean; +} + +export interface FocusEventObject extends UIEventObject { + relatedTarget: EventTargetObject | null; +} + +export interface TouchEventObject extends UIEventObject { + altKey: boolean; + changedTouches: TouchObject[]; + ctrlKey: boolean; + metaKey: boolean; + shiftKey: boolean; + targetTouches: TouchObject[]; + touches: TouchObject[]; +} + +export interface PointerEventObject extends MouseEventObject { + height: number; + isPrimary: boolean; + pointerId: number; + pointerType: string; + pressure: number; + tangentialPressure: number; + tiltX: number; + tiltY: number; + twist: number; + width: number; +} + +export interface TransitionEventObject extends EventObject { + elapsedTime: number; + propertyName: string; + pseudoElement: string; +} + +export interface WheelEventObject extends MouseEventObject { + readonly deltaMode: number; + readonly deltaX: number; + readonly deltaY: number; + readonly deltaZ: number; +} + +export interface TouchObject { + clientX: number; + clientY: number; + force: number; + identifier: number; + pageX: number; + pageY: number; + radiusX: number; + radiusY: number; + rotationAngle: number; + screenX: number; + screenY: number; + target: EventTargetObject; +} + +export interface DataTransferObject { + /** + * Returns the kind of operation that is currently selected. If the kind of operation + * isn't one of those that is allowed by the effectAllowed attribute, then the + * operation will fail. + * + * Can be set, to change the selected operation. + * + * The possible values are "none", "copy", "link", and "move". + */ + dropEffect: "none" | "copy" | "link" | "move"; + /** + * Returns the kinds of operations that are to be allowed. + * + * Can be set (during the dragstart event), to change the allowed operations. + * + * The possible values are "none", "copy", "copyLink", "copyMove", "link", "linkMove", + * "move", "all", and "uninitialized", + */ + effectAllowed: + | "none" + | "copy" + | "copyLink" + | "copyMove" + | "link" + | "linkMove" + | "move" + | "all" + | "uninitialized"; + /** Returns a FileList of the files being dragged, if any. */ + files: FileListObject; + /** Returns a DataTransferItemList object, with the drag data. */ + items: DataTransferItemListObject; + /** Lists the formats that were set in the dragstart event. In addition, if any files + * are being dragged, then one of the types will be the string "Files". */ + types: string[]; +} + +export interface SelectionObject { + anchorNode: EventTargetObject | null; + anchorOffset: number; + focusNode: EventTargetObject | null; + focusOffset: number; + isCollapsed: boolean; + rangeCount: number; + type: string; +} + +export interface EventTargetObject { + value?: string; + textContent?: string; +} + +export interface FileObject { + name: string; + size: number; + type: string; +} + +type NONE = 0; +type CAPTURING_PHASE = 1; +type AT_TARGET = 2; +type BUBBLING_PHASE = 3; diff --git a/src/client/packages/event-to-object/src/index.js b/src/client/packages/event-to-object/src/index.js deleted file mode 100644 index 2d2eba981..000000000 --- a/src/client/packages/event-to-object/src/index.js +++ /dev/null @@ -1,240 +0,0 @@ -export default function convert(event) { - const data = {}; - - if (event.type in eventTransforms) { - Object.assign(data, eventTransforms[event.type](event)); - } - - data.target = serializeDomElement(event.target); - data.currentTarget = - event.target === event.currentTarget - ? data.target - : serializeDomElement(event.currentTarget); - data.relatedTarget = serializeDomElement(event.relatedTarget); - - return data; -} - -function serializeDomElement(element) { - let elementData = null; - if (element) { - elementData = defaultElementTransform(element); - if (element.tagName in elementTransforms) { - elementTransforms[element.tagName].forEach((trans) => - Object.assign(elementData, trans(element)), - ); - } - } - return elementData; -} - -const elementTransformCategories = { - hasValue: (element) => ({ - value: element.value, - }), - hasCurrentTime: (element) => ({ - currentTime: element.currentTime, - }), - hasFiles: (element) => { - if (element?.type === "file") { - return { - files: Array.from(element.files).map((file) => ({ - lastModified: file.lastModified, - name: file.name, - size: file.size, - type: file.type, - })), - }; - } else { - return {}; - } - }, - hasElements: (element) => { - const { elements } = element; - const indices = [...Array(elements.length).keys()]; - return { - elements: indices.map((index) => serializeDomElement(elements[index])), - }; - }, - hasName: (element) => { - const { name } = element; - // In some edge cases, "name" may not be a string. For example, in the case of - // `
`, the "name" attribute of the `
` will - // be the `` element. - return typeof name === "string" ? { name } : {}; - }, -}; - -function defaultElementTransform(element) { - return { boundingClientRect: element.getBoundingClientRect() }; -} - -const elementTagCategories = { - hasValue: [ - "BUTTON", - "INPUT", - "OPTION", - "LI", - "METER", - "PROGRESS", - "PARAM", - "SELECT", - "TEXTAREA", - ], - hasCurrentTime: ["AUDIO", "VIDEO"], - hasFiles: ["INPUT"], - hasElements: ["FORM"], - hasName: [ - "BUTTON", - "FORM", - "FIELDSET", - "IFRAME", - "INPUT", - "KEYGEN", - "OBJECT", - "OUTPUT", - "SELECT", - "TEXTAREA", - "MAP", - "META", - "PARAM", - ], -}; - -const elementTransforms = {}; - -Object.keys(elementTagCategories).forEach((category) => { - elementTagCategories[category].forEach((type) => { - const transforms = - elementTransforms[type] || (elementTransforms[type] = []); - transforms.push(elementTransformCategories[category]); - }); -}); - -function EventTransformCategories() { - this.clipboard = (event) => ({ - clipboardData: event.clipboardData, - }); - this.composition = (event) => ({ - data: event.data, - }); - this.keyboard = (event) => ({ - altKey: event.altKey, - charCode: event.charCode, - ctrlKey: event.ctrlKey, - key: event.key, - keyCode: event.keyCode, - locale: event.locale, - location: event.location, - metaKey: event.metaKey, - repeat: event.repeat, - shiftKey: event.shiftKey, - which: event.which, - }); - this.mouse = (event) => ({ - altKey: event.altKey, - button: event.button, - buttons: event.buttons, - clientX: event.clientX, - clientY: event.clientY, - ctrlKey: event.ctrlKey, - metaKey: event.metaKey, - pageX: event.pageX, - pageY: event.pageY, - screenX: event.screenX, - screenY: event.screenY, - shiftKey: event.shiftKey, - }); - this.pointer = (event) => ({ - ...this.mouse(event), - pointerId: event.pointerId, - width: event.width, - height: event.height, - pressure: event.pressure, - tiltX: event.tiltX, - tiltY: event.tiltY, - pointerType: event.pointerType, - isPrimary: event.isPrimary, - }); - this.selection = () => { - return { selectedText: window.getSelection().toString() }; - }; - this.touch = (event) => ({ - altKey: event.altKey, - ctrlKey: event.ctrlKey, - metaKey: event.metaKey, - shiftKey: event.shiftKey, - }); - this.ui = (event) => ({ - detail: event.detail, - }); - this.wheel = (event) => ({ - deltaMode: event.deltaMode, - deltaX: event.deltaX, - deltaY: event.deltaY, - deltaZ: event.deltaZ, - }); - this.animation = (event) => ({ - animationName: event.animationName, - pseudoElement: event.pseudoElement, - elapsedTime: event.elapsedTime, - }); - this.transition = (event) => ({ - propertyName: event.propertyName, - pseudoElement: event.pseudoElement, - elapsedTime: event.elapsedTime, - }); -} - -const eventTypeCategories = { - clipboard: ["copy", "cut", "paste"], - composition: ["compositionend", "compositionstart", "compositionupdate"], - keyboard: ["keydown", "keypress", "keyup"], - mouse: [ - "click", - "contextmenu", - "doubleclick", - "drag", - "dragend", - "dragenter", - "dragexit", - "dragleave", - "dragover", - "dragstart", - "drop", - "mousedown", - "mouseenter", - "mouseleave", - "mousemove", - "mouseout", - "mouseover", - "mouseup", - ], - pointer: [ - "pointerdown", - "pointermove", - "pointerup", - "pointercancel", - "gotpointercapture", - "lostpointercapture", - "pointerenter", - "pointerleave", - "pointerover", - "pointerout", - ], - selection: ["select"], - touch: ["touchcancel", "touchend", "touchmove", "touchstart"], - ui: ["scroll"], - wheel: ["wheel"], - animation: ["animationstart", "animationend", "animationiteration"], - transition: ["transitionend"], -}; - -const eventTransforms = {}; - -const eventTransformCategories = new EventTransformCategories(); -Object.keys(eventTypeCategories).forEach((category) => { - eventTypeCategories[category].forEach((type) => { - eventTransforms[type] = eventTransformCategories[category]; - }); -}); diff --git a/src/client/packages/event-to-object/src/index.ts b/src/client/packages/event-to-object/src/index.ts new file mode 100644 index 000000000..fb171c35f --- /dev/null +++ b/src/client/packages/event-to-object/src/index.ts @@ -0,0 +1,422 @@ +import * as e from "./events"; + +export default function convert( + event: E, +): + | { + [K in keyof e.EventToObjectMap]: e.EventToObjectMap[K] extends [ + E, + infer P, + ] + ? P + : never; + }[keyof e.EventToObjectMap] + | null { + return event.type in eventConverters + ? eventConverters[event.type](event) + : convertEvent(event); +} + +const convertEvent = (event: Event): e.EventObject => ({ + /** Returns true or false depending on how event was initialized. True if event goes + * through its target's ancestors in reverse tree order, and false otherwise. */ + bubbles: event.bubbles, + composed: event.composed, + currentTarget: convertElement(event.currentTarget), + defaultPrevented: event.defaultPrevented, + eventPhase: event.eventPhase, + isTrusted: event.isTrusted, + target: convertElement(event.target), + timeStamp: event.timeStamp, + type: event.type, + selection: convertSelection(window.getSelection()), +}); + +const convertClipboardEvent = ( + event: ClipboardEvent, +): e.ClipboardEventObject => ({ + ...convertEvent(event), + clipboardData: convertDataTransferObject(event.clipboardData), +}); + +const convertCompositionEvent = ( + event: CompositionEvent, +): e.CompositionEventObject => ({ + ...convertUiEvent(event), + data: event.data, +}); + +const convertInputEvent = (event: InputEvent): e.InputEventObject => ({ + ...convertUiEvent(event), + data: event.data, + inputType: event.inputType, + dataTransfer: convertDataTransferObject(event.dataTransfer), + isComposing: event.isComposing, +}); + +const convertKeyboardEvent = (event: KeyboardEvent): e.KeyboardEventObject => ({ + ...convertUiEvent(event), + code: event.code, + isComposing: event.isComposing, + altKey: event.altKey, + ctrlKey: event.ctrlKey, + key: event.key, + location: event.location, + metaKey: event.metaKey, + repeat: event.repeat, + shiftKey: event.shiftKey, +}); + +const convertMouseEvent = (event: MouseEvent): e.MouseEventObject => ({ + ...convertEvent(event), + altKey: event.altKey, + button: event.button, + buttons: event.buttons, + clientX: event.clientX, + clientY: event.clientY, + ctrlKey: event.ctrlKey, + metaKey: event.metaKey, + pageX: event.pageX, + pageY: event.pageY, + screenX: event.screenX, + screenY: event.screenY, + shiftKey: event.shiftKey, + movementX: event.movementX, + movementY: event.movementY, + offsetX: event.offsetX, + offsetY: event.offsetY, + x: event.x, + y: event.y, + relatedTarget: convertElement(event.relatedTarget), +}); + +const convertTouchEvent = (event: TouchEvent): e.TouchEventObject => ({ + ...convertUiEvent(event), + altKey: event.altKey, + ctrlKey: event.ctrlKey, + metaKey: event.metaKey, + shiftKey: event.shiftKey, + touches: Array.from(event.touches).map(convertTouch), + changedTouches: Array.from(event.changedTouches).map(convertTouch), + targetTouches: Array.from(event.targetTouches).map(convertTouch), +}); + +const convertUiEvent = (event: UIEvent): e.UIEventObject => ({ + ...convertEvent(event), + detail: event.detail, +}); + +const convertAnimationEvent = ( + event: AnimationEvent, +): e.AnimationEventObject => ({ + ...convertEvent(event), + animationName: event.animationName, + pseudoElement: event.pseudoElement, + elapsedTime: event.elapsedTime, +}); + +const convertTransitionEvent = ( + event: TransitionEvent, +): e.TransitionEventObject => ({ + ...convertEvent(event), + propertyName: event.propertyName, + pseudoElement: event.pseudoElement, + elapsedTime: event.elapsedTime, +}); + +const convertFocusEvent = (event: FocusEvent): e.FocusEventObject => ({ + ...convertUiEvent(event), + relatedTarget: convertElement(event.relatedTarget), +}); + +const convertDeviceOrientationEvent = ( + event: DeviceOrientationEvent, +): e.DeviceOrientationEventObject => ({ + ...convertEvent(event), + absolute: event.absolute, + alpha: event.alpha, + beta: event.beta, + gamma: event.gamma, +}); + +const convertDragEvent = (event: DragEvent): e.DragEventObject => ({ + ...convertMouseEvent(event), + dataTransfer: convertDataTransferObject(event.dataTransfer), +}); + +const convertGamepadEvent = (event: GamepadEvent): e.GamepadEventObject => ({ + ...convertEvent(event), + gamepad: convertGamepad(event.gamepad), +}); + +const convertGamepad = (gamepad: Gamepad): e.GamepadObject => ({ + axes: Array.from(gamepad.axes), + buttons: Array.from(gamepad.buttons).map(convertGamepadButton), + connected: gamepad.connected, + id: gamepad.id, + index: gamepad.index, + mapping: gamepad.mapping, + timestamp: gamepad.timestamp, + hapticActuators: Array.from(gamepad.hapticActuators).map( + convertGamepadHapticActuator, + ), +}); + +const convertGamepadButton = ( + button: GamepadButton, +): e.GamepadButtonObject => ({ + pressed: button.pressed, + touched: button.touched, + value: button.value, +}); + +const convertGamepadHapticActuator = ( + actuator: GamepadHapticActuator, +): e.GamepadHapticActuatorObject => ({ + type: actuator.type, +}); + +const convertPointerEvent = (event: PointerEvent): e.PointerEventObject => ({ + ...convertMouseEvent(event), + pointerId: event.pointerId, + width: event.width, + height: event.height, + pressure: event.pressure, + tiltX: event.tiltX, + tiltY: event.tiltY, + pointerType: event.pointerType, + isPrimary: event.isPrimary, + tangentialPressure: event.tangentialPressure, + twist: event.twist, +}); + +const convertWheelEvent = (event: WheelEvent): e.WheelEventObject => ({ + ...convertMouseEvent(event), + deltaMode: event.deltaMode, + deltaX: event.deltaX, + deltaY: event.deltaY, + deltaZ: event.deltaZ, +}); + +const convertSubmitEvent = (event: SubmitEvent): e.SubmitEventObject => ({ + ...convertEvent(event), + submitter: convertElement(event.submitter), +}); + +const eventConverters: { [key: string]: (event: any) => any } = { + // animation events + animationcancel: convertAnimationEvent, + animationend: convertAnimationEvent, + animationiteration: convertAnimationEvent, + animationstart: convertAnimationEvent, + // + + beforeinput: convertInputEvent, + blur: convertFocusEvent, + // composition events + compositionend: convertCompositionEvent, + compositionstart: convertCompositionEvent, + compositionupdate: convertCompositionEvent, + // clipboard events + copy: convertClipboardEvent, + cut: convertClipboardEvent, + paste: convertClipboardEvent, + // device orientation events + deviceorientation: convertDeviceOrientationEvent, + // drag events + drag: convertDragEvent, + dragend: convertDragEvent, + dragenter: convertDragEvent, + dragleave: convertDragEvent, + dragover: convertDragEvent, + dragstart: convertDragEvent, + drop: convertDragEvent, + // ui events + error: convertUiEvent, + // focus events + focus: convertFocusEvent, + focusin: convertFocusEvent, + focusout: convertFocusEvent, + // gamepad events + gamepadconnected: convertGamepadEvent, + gamepaddisconnected: convertGamepadEvent, + gotpointercapture: convertPointerEvent, + // keyboard events + keydown: convertKeyboardEvent, + keypress: convertKeyboardEvent, + keyup: convertKeyboardEvent, + // mouse events + click: convertMouseEvent, + auxclick: convertMouseEvent, + dblclick: convertMouseEvent, + mousedown: convertMouseEvent, + mouseenter: convertMouseEvent, + mouseleave: convertMouseEvent, + mousemove: convertMouseEvent, + mouseout: convertMouseEvent, + mouseover: convertMouseEvent, + mouseup: convertMouseEvent, + scroll: convertMouseEvent, + // pointer events + lostpointercapture: convertPointerEvent, + pointercancel: convertPointerEvent, + pointerdown: convertPointerEvent, + pointerenter: convertPointerEvent, + pointerleave: convertPointerEvent, + pointerlockchange: convertPointerEvent, + pointerlockerror: convertPointerEvent, + pointermove: convertPointerEvent, + pointerout: convertPointerEvent, + pointerover: convertPointerEvent, + pointerup: convertPointerEvent, + // submit events + submit: convertSubmitEvent, + // touch events + touchcancel: convertTouchEvent, + touchend: convertTouchEvent, + touchmove: convertTouchEvent, + touchstart: convertTouchEvent, + // transition events + transitioncancel: convertTransitionEvent, + transitionend: convertTransitionEvent, + transitionrun: convertTransitionEvent, + transitionstart: convertTransitionEvent, + // wheel events + wheel: convertWheelEvent, +}; + +function convertElement(element: EventTarget | null): any { + if (!element) { + return null; + } + + // @ts-ignore + const tagName = element.tagName as string; + if (!tagName) { + return null; + } + + const converter = elementConverters[tagName]; + return converter + ? converter(element) + : "name" in element + ? { name: element.name } + : {}; +} + +const convertMediaElement = (element: HTMLMediaElement) => ({ + currentTime: element.currentTime, + duration: element.duration, + ended: element.ended, + error: element.error, + seeking: element.seeking, + volume: element.volume, +}); + +const elementConverters: { [key: string]: (element: any) => any } = { + AUDIO: convertMediaElement, + BUTTON: (element: HTMLButtonElement) => ({ value: element.value }), + DATA: (element: HTMLDataElement) => ({ value: element.value }), + DATALIST: (element: HTMLDataListElement) => ({ + options: Array.from(element.options).map(elementConverters["OPTION"]), + }), + DIALOG: (element: HTMLDialogElement) => ({ + returnValue: element.returnValue, + }), + FIELDSET: (element: HTMLFieldSetElement) => ({ + elements: Array.from(element.elements).map(convertElement), + }), + FORM: (element: HTMLFormElement) => ({ + elements: Array.from(element.elements).map(convertElement), + }), + INPUT: (element: HTMLInputElement) => ({ value: element.value }), + METER: (element: HTMLMeterElement) => ({ value: element.value }), + OPTION: (element: HTMLOptionElement) => ({ value: element.value }), + OUTPUT: (element: HTMLOutputElement) => ({ value: element.value }), + PROGRESS: (element: HTMLProgressElement) => ({ value: element.value }), + SELECT: (element: HTMLSelectElement) => ({ value: element.value }), + TEXTAREA: (element: HTMLTextAreaElement) => ({ value: element.value }), + VIDEO: convertMediaElement, +}; + +const convertFile = (file: File) => ({ + lastModified: file.lastModified, + name: file.name, + size: file.size, + type: file.type, +}); + +function convertDataTransferObject( + dataTransfer: DataTransfer | null, +): e.DataTransferObject | null { + if (!dataTransfer) { + return null; + } + const { dropEffect, effectAllowed, files, items, types } = dataTransfer; + return { + dropEffect, + effectAllowed, + files: Array.from(files).map(convertFile), + items: Array.from(items).map((item) => ({ + kind: item.kind, + type: item.type, + })), + types: Array.from(types), + }; +} + +function convertSelection( + selection: Selection | null, +): e.SelectionObject | null { + if (!selection) { + return null; + } + const { + type, + anchorNode, + anchorOffset, + focusNode, + focusOffset, + isCollapsed, + rangeCount, + } = selection; + return { + type, + anchorNode: convertElement(anchorNode), + anchorOffset, + focusNode: convertElement(focusNode), + focusOffset, + isCollapsed, + rangeCount, + }; +} + +function convertTouch({ + identifier, + pageX, + pageY, + screenX, + screenY, + clientX, + clientY, + force, + radiusX, + radiusY, + rotationAngle, + target, +}: Touch): e.TouchObject { + return { + identifier, + pageX, + pageY, + screenX, + screenY, + clientX, + clientY, + force, + radiusX, + radiusY, + rotationAngle, + target: convertElement(target), + }; +} diff --git a/src/client/packages/event-to-object/tests/event-to-object.test.js b/src/client/packages/event-to-object/tests/event-to-object.test.js index d73be18ec..205b19099 100644 --- a/src/client/packages/event-to-object/tests/event-to-object.test.js +++ b/src/client/packages/event-to-object/tests/event-to-object.test.js @@ -1,7 +1,7 @@ import { test } from "uvu"; import lodash from "lodash"; import * as assert from "uvu/assert"; -import serializeEvent from "../src/index.js"; +import serializeEvent from "../src/index.ts"; import "./tooling/setup.js"; const mockBoundingRect = { diff --git a/src/client/packages/event-to-object/tsconfig.json b/src/client/packages/event-to-object/tsconfig.json new file mode 100644 index 000000000..596e2cf72 --- /dev/null +++ b/src/client/packages/event-to-object/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src"] +} diff --git a/src/client/tsconfig.json b/src/client/tsconfig.json new file mode 100644 index 000000000..c4f7d98f5 --- /dev/null +++ b/src/client/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["DOM", "DOM.Iterable", "esnext"], + "allowJs": false, + "skipLibCheck": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + } +} From 5357a9a2f769c43e1fa31112b178792f0cd2b18a Mon Sep 17 00:00:00 2001 From: rmorshea Date: Thu, 9 Mar 2023 00:34:22 -0800 Subject: [PATCH 06/16] rewrite event-to-object tests --- src/client/package-lock.json | 208 +++++++++++ src/client/package.json | 1 + src/client/packages/app/package.json | 1 + src/client/packages/client/package.json | 1 + .../packages/event-to-object/package.json | 7 +- .../packages/event-to-object/src/events.ts | 1 + .../packages/event-to-object/src/index.ts | 92 ++--- .../tests/event-to-object.test.js | 353 ------------------ .../tests/event-to-object.test.ts | 125 +++++++ .../event-to-object/tests/tooling/check.ts | 49 +++ .../event-to-object/tests/tooling/dom.js | 16 - .../event-to-object/tests/tooling/mock.ts | 15 + .../event-to-object/tests/tooling/setup.js | 5 +- 13 files changed, 456 insertions(+), 418 deletions(-) delete mode 100644 src/client/packages/event-to-object/tests/event-to-object.test.js create mode 100644 src/client/packages/event-to-object/tests/event-to-object.test.ts create mode 100644 src/client/packages/event-to-object/tests/tooling/check.ts delete mode 100644 src/client/packages/event-to-object/tests/tooling/dom.js create mode 100644 src/client/packages/event-to-object/tests/tooling/mock.ts diff --git a/src/client/package-lock.json b/src/client/package-lock.json index 24f6f31ea..68253e9e8 100644 --- a/src/client/package-lock.json +++ b/src/client/package-lock.json @@ -238,6 +238,12 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, "node_modules/cssom": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", @@ -878,6 +884,63 @@ "assert-plus": "^1.0.0" } }, + "node_modules/happy-dom": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-8.9.0.tgz", + "integrity": "sha512-JZwJuGdR7ko8L61136YzmrLv7LgTh5b8XaEM3P709mLjyQuXJ3zHTDXvUtBBahRjGlcYW0zGjIiEWizoTUGKfA==", + "dev": true, + "dependencies": { + "css.escape": "^1.5.1", + "he": "^1.2.0", + "iconv-lite": "^0.6.3", + "node-fetch": "^2.x.x", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0" + } + }, + "node_modules/happy-dom/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/happy-dom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/happy-dom/node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/happy-dom/node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -913,6 +976,15 @@ "node": ">= 0.4.0" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, "node_modules/html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -1172,6 +1244,48 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-gyp-build": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", @@ -2043,6 +2157,7 @@ "json-pointer": "^0.6.2" }, "devDependencies": { + "happy-dom": "^8.9.0", "jsdom": "16.5.0", "lodash": "^4.17.21", "prettier": "^3.0.0-alpha.6", @@ -2325,6 +2440,12 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, "cssom": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", @@ -2645,6 +2766,7 @@ "version": "file:packages/event-to-object", "requires": { "event-to-object": "file:packages/event-to-object", + "happy-dom": "*", "jsdom": "16.5.0", "json-pointer": "^0.6.2", "lodash": "^4.17.21", @@ -2739,6 +2861,53 @@ "assert-plus": "^1.0.0" } }, + "happy-dom": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-8.9.0.tgz", + "integrity": "sha512-JZwJuGdR7ko8L61136YzmrLv7LgTh5b8XaEM3P709mLjyQuXJ3zHTDXvUtBBahRjGlcYW0zGjIiEWizoTUGKfA==", + "dev": true, + "requires": { + "css.escape": "^1.5.1", + "he": "^1.2.0", + "iconv-lite": "^0.6.3", + "node-fetch": "^2.x.x", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + } + }, + "whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true + } + } + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -2764,6 +2933,12 @@ "function-bind": "^1.1.1" } }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, "html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -2966,6 +3141,39 @@ "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "dev": true }, + "node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, "node-gyp-build": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", diff --git a/src/client/package.json b/src/client/package.json index ff6a2a8bd..f44a517fe 100644 --- a/src/client/package.json +++ b/src/client/package.json @@ -10,6 +10,7 @@ "scripts": { "build": "vite build", "publish": "npm --workspaces publish", + "test": "npm --workspaces test", "format": "npm --workspaces run format", "check:format": "npm --workspaces run check:format", "check:tests": "npm --workspaces run check:tests", diff --git a/src/client/packages/app/package.json b/src/client/packages/app/package.json index 9b886fffd..145d30230 100644 --- a/src/client/packages/app/package.json +++ b/src/client/packages/app/package.json @@ -19,6 +19,7 @@ }, "scripts": { "format": "prettier --write ./src", + "test": "npm run check:tests", "check:format": "prettier --check ./src", "check:tests": "echo 'no tests'", "check:types": "tsc" diff --git a/src/client/packages/client/package.json b/src/client/packages/client/package.json index dbd036c52..833fc4e28 100644 --- a/src/client/packages/client/package.json +++ b/src/client/packages/client/package.json @@ -25,6 +25,7 @@ }, "scripts": { "format": "prettier --write ./src", + "test": "npm run check:tests", "check:format": "prettier --check ./src", "check:tests": "echo 'no tests'", "check:types": "tsc" diff --git a/src/client/packages/event-to-object/package.json b/src/client/packages/event-to-object/package.json index 8166ec4d7..a0d7dd357 100644 --- a/src/client/packages/event-to-object/package.json +++ b/src/client/packages/event-to-object/package.json @@ -6,12 +6,12 @@ }, "description": "A client for ReactPy implemented in React", "devDependencies": { - "jsdom": "16.5.0", + "happy-dom": "^8.9.0", "lodash": "^4.17.21", "prettier": "^3.0.0-alpha.6", - "uvu": "^0.5.1", "tsm": "^2.0.0", - "typescript": "^4.9.5" + "typescript": "^4.9.5", + "uvu": "^0.5.1" }, "license": "MIT", "main": "src/index.js", @@ -22,6 +22,7 @@ }, "scripts": { "format": "prettier --write ./src ./tests", + "test": "npm run check:tests", "check:format": "prettier --check ./src ./tests", "check:tests": "uvu -r tsm tests", "check:types": "tsc" diff --git a/src/client/packages/event-to-object/src/events.ts b/src/client/packages/event-to-object/src/events.ts index 455283b01..24164027f 100644 --- a/src/client/packages/event-to-object/src/events.ts +++ b/src/client/packages/event-to-object/src/events.ts @@ -264,6 +264,7 @@ export interface SelectionObject { isCollapsed: boolean; rangeCount: number; type: string; + selectedText: string; } export interface EventTargetObject { diff --git a/src/client/packages/event-to-object/src/index.ts b/src/client/packages/event-to-object/src/index.ts index fb171c35f..0da436a7b 100644 --- a/src/client/packages/event-to-object/src/index.ts +++ b/src/client/packages/event-to-object/src/index.ts @@ -149,33 +149,6 @@ const convertGamepadEvent = (event: GamepadEvent): e.GamepadEventObject => ({ gamepad: convertGamepad(event.gamepad), }); -const convertGamepad = (gamepad: Gamepad): e.GamepadObject => ({ - axes: Array.from(gamepad.axes), - buttons: Array.from(gamepad.buttons).map(convertGamepadButton), - connected: gamepad.connected, - id: gamepad.id, - index: gamepad.index, - mapping: gamepad.mapping, - timestamp: gamepad.timestamp, - hapticActuators: Array.from(gamepad.hapticActuators).map( - convertGamepadHapticActuator, - ), -}); - -const convertGamepadButton = ( - button: GamepadButton, -): e.GamepadButtonObject => ({ - pressed: button.pressed, - touched: button.touched, - value: button.value, -}); - -const convertGamepadHapticActuator = ( - actuator: GamepadHapticActuator, -): e.GamepadHapticActuatorObject => ({ - type: actuator.type, -}); - const convertPointerEvent = (event: PointerEvent): e.PointerEventObject => ({ ...convertMouseEvent(event), pointerId: event.pointerId, @@ -209,10 +182,8 @@ const eventConverters: { [key: string]: (event: any) => any } = { animationend: convertAnimationEvent, animationiteration: convertAnimationEvent, animationstart: convertAnimationEvent, - // - + // input events beforeinput: convertInputEvent, - blur: convertFocusEvent, // composition events compositionend: convertCompositionEvent, compositionstart: convertCompositionEvent, @@ -234,6 +205,7 @@ const eventConverters: { [key: string]: (event: any) => any } = { // ui events error: convertUiEvent, // focus events + blur: convertFocusEvent, focus: convertFocusEvent, focusin: convertFocusEvent, focusout: convertFocusEvent, @@ -246,8 +218,8 @@ const eventConverters: { [key: string]: (event: any) => any } = { keypress: convertKeyboardEvent, keyup: convertKeyboardEvent, // mouse events - click: convertMouseEvent, auxclick: convertMouseEvent, + click: convertMouseEvent, dblclick: convertMouseEvent, mousedown: convertMouseEvent, mouseenter: convertMouseEvent, @@ -285,25 +257,26 @@ const eventConverters: { [key: string]: (event: any) => any } = { wheel: convertWheelEvent, }; -function convertElement(element: EventTarget | null): any { - if (!element) { +function convertElement(element: EventTarget | HTMLElement | null): any { + if (!element || !("tagName" in element)) { return null; } - // @ts-ignore - const tagName = element.tagName as string; - if (!tagName) { - return null; - } + const htmlElement = element as HTMLElement; - const converter = elementConverters[tagName]; - return converter - ? converter(element) - : "name" in element - ? { name: element.name } - : {}; + return { + ...convertGenericElement(htmlElement), + ...(htmlElement.tagName in elementConverters + ? elementConverters[htmlElement.tagName](htmlElement) + : {}), + }; } +const convertGenericElement = (element: HTMLElement) => ({ + tagName: element.tagName, + boundingClientRect: { ...element.getBoundingClientRect() }, +}); + const convertMediaElement = (element: HTMLMediaElement) => ({ currentTime: element.currentTime, duration: element.duration, @@ -339,6 +312,33 @@ const elementConverters: { [key: string]: (element: any) => any } = { VIDEO: convertMediaElement, }; +const convertGamepad = (gamepad: Gamepad): e.GamepadObject => ({ + axes: Array.from(gamepad.axes), + buttons: Array.from(gamepad.buttons).map(convertGamepadButton), + connected: gamepad.connected, + id: gamepad.id, + index: gamepad.index, + mapping: gamepad.mapping, + timestamp: gamepad.timestamp, + hapticActuators: Array.from(gamepad.hapticActuators).map( + convertGamepadHapticActuator, + ), +}); + +const convertGamepadButton = ( + button: GamepadButton, +): e.GamepadButtonObject => ({ + pressed: button.pressed, + touched: button.touched, + value: button.value, +}); + +const convertGamepadHapticActuator = ( + actuator: GamepadHapticActuator, +): e.GamepadHapticActuatorObject => ({ + type: actuator.type, +}); + const convertFile = (file: File) => ({ lastModified: file.lastModified, name: file.name, @@ -380,6 +380,9 @@ function convertSelection( isCollapsed, rangeCount, } = selection; + if (type === "None") { + return null; + } return { type, anchorNode: convertElement(anchorNode), @@ -388,6 +391,7 @@ function convertSelection( focusOffset, isCollapsed, rangeCount, + selectedText: selection.toString(), }; } diff --git a/src/client/packages/event-to-object/tests/event-to-object.test.js b/src/client/packages/event-to-object/tests/event-to-object.test.js deleted file mode 100644 index 205b19099..000000000 --- a/src/client/packages/event-to-object/tests/event-to-object.test.js +++ /dev/null @@ -1,353 +0,0 @@ -import { test } from "uvu"; -import lodash from "lodash"; -import * as assert from "uvu/assert"; -import serializeEvent from "../src/index.ts"; -import "./tooling/setup.js"; - -const mockBoundingRect = { - left: 0, - top: 0, - right: 0, - bottom: 0, - x: 0, - y: 0, - width: 0, -}; - -const mockElement = { - tagName: null, - getBoundingClientRect: () => mockBoundingRect, -}; - -const allTargetData = { - files: [ - { - lastModified: 0, - name: "something", - type: "some-type", - size: 0, - }, - ], - value: "something", - currentTime: 35, - tagName: null, // overwritten in tests - elements: [ - { ...mockElement, tagName: "INPUT", value: "first" }, - { ...mockElement, tagName: "INPUT", value: "second" }, - ], -}; - -function assertEqualSerializedEventData(eventData, expectedSerializedData) { - const commonEventData = { - target: mockElement, - currentTarget: mockElement, - relatedTarget: mockElement, - }; - - const commonSerializedEventData = { - target: { boundingClientRect: mockBoundingRect }, - currentTarget: { boundingClientRect: mockBoundingRect }, - relatedTarget: { boundingClientRect: mockBoundingRect }, - }; - - assert.equal( - serializeEvent(lodash.merge({}, commonEventData, eventData)), - lodash.merge({}, commonSerializedEventData, expectedSerializedData), - ); -} - -[ - { - case: "adds 'files' and 'value' attributes for INPUT if type=file", - tagName: "INPUT", - addTargetAttrs: { type: "file" }, - output: { - target: { - files: allTargetData.files, - value: allTargetData.value, - }, - }, - }, - ...["BUTTON", "INPUT", "OPTION", "LI", "METER", "PROGRESS", "PARAM"].map( - (tagName) => ({ - case: `adds 'value' attribute for ${tagName} element`, - tagName, - output: { target: { value: allTargetData.value } }, - }), - ), - ...["AUDIO", "VIDEO"].map((tagName) => ({ - case: `adds 'currentTime' attribute for ${tagName} element`, - tagName, - output: { target: { currentTime: allTargetData.currentTime } }, - })), - ...["FORM"].map((tagName) => ({ - case: `adds 'elements' attribute for ${tagName} element`, - tagName, - output: { - target: { - elements: [ - { - value: "first", - boundingClientRect: mockBoundingRect, - }, - { - value: "second", - boundingClientRect: mockBoundingRect, - }, - ], - }, - }, - })), -].forEach((expectation) => { - test(`serializeEvent() ${expectation.case}`, () => { - const eventData = { - target: { - ...allTargetData, - tagName: expectation.tagName, - }, - }; - if (expectation.addTargetAttrs) { - Object.assign(eventData.target, expectation.addTargetAttrs); - } - assertEqualSerializedEventData(eventData, expectation.output); - }); -}); - -const allEventData = { - type: null, // set in text - target: { tagName: null }, // avoid triggering target specific transformations - clipboardData: "clipboardData", - data: "data", - altKey: "altKey", - charCode: "charCode", - ctrlKey: "ctrlKey", - key: "key", - keyCode: "keyCode", - locale: "locale", - location: "location", - metaKey: "metaKey", - repeat: "repeat", - shiftKey: "shiftKey", - which: "which", - altKey: "altKey", - button: "button", - buttons: "buttons", - clientX: "clientX", - clientY: "clientY", - ctrlKey: "ctrlKey", - form: "form", - metaKey: "metaKey", - pageX: "pageX", - pageY: "pageY", - screenX: "screenX", - screenY: "screenY", - shiftKey: "shiftKey", - pointerId: "pointerId", - width: "width", - height: "height", - pressure: "pressure", - tiltX: "tiltX", - tiltY: "tiltY", - pointerType: "pointerType", - isPrimary: "isPrimary", - altKey: "altKey", - ctrlKey: "ctrlKey", - metaKey: "metaKey", - shiftKey: "shiftKey", - detail: "detail", - deltaMode: "deltaMode", - deltaX: "deltaX", - deltaY: "deltaY", - deltaZ: "deltaZ", - animationName: "animationName", - pseudoElement: "pseudoElement", - elapsedTime: "elapsedTime", - propertyName: "propertyName", - pseudoElement: "pseudoElement", - elapsedTime: "elapsedTime", -}; - -[ - ...["copy", "cut", "paste"].map((eventType) => ({ - eventType, - case: "clipboard", - output: { clipboardData: "clipboardData" }, - })), - ...["compositionend", "compositionstart", "compositionupdate"].map( - (eventType) => ({ - eventType, - case: "composition", - output: { data: "data" }, - }), - ), - ...["keydown", "keypress", "keyup"].map((eventType) => ({ - eventType, - case: "keyboard", - output: { - altKey: "altKey", - charCode: "charCode", - ctrlKey: "ctrlKey", - key: "key", - keyCode: "keyCode", - locale: "locale", - location: "location", - metaKey: "metaKey", - repeat: "repeat", - shiftKey: "shiftKey", - which: "which", - }, - })), - ...[ - "click", - "contextmenu", - "doubleclick", - "drag", - "dragend", - "dragenter", - "dragexit", - "dragleave", - "dragover", - "dragstart", - "drop", - "mousedown", - "mouseenter", - "mouseleave", - "mousemove", - "mouseout", - "mouseover", - "mouseup", - ].map((eventType) => ({ - eventType, - case: "mouse", - output: { - altKey: "altKey", - button: "button", - buttons: "buttons", - clientX: "clientX", - clientY: "clientY", - ctrlKey: "ctrlKey", - metaKey: "metaKey", - pageX: "pageX", - pageY: "pageY", - screenX: "screenX", - screenY: "screenY", - shiftKey: "shiftKey", - }, - })), - ...[ - "pointerdown", - "pointermove", - "pointerup", - "pointercancel", - "gotpointercapture", - "lostpointercapture", - "pointerenter", - "pointerleave", - "pointerover", - "pointerout", - ].map((eventType) => ({ - eventType, - case: "pointer", - output: { - pointerId: "pointerId", - width: "width", - height: "height", - pressure: "pressure", - tiltX: "tiltX", - tiltY: "tiltY", - pointerType: "pointerType", - altKey: "altKey", - button: "button", - buttons: "buttons", - clientX: "clientX", - clientY: "clientY", - ctrlKey: "ctrlKey", - metaKey: "metaKey", - pageX: "pageX", - pageY: "pageY", - screenX: "screenX", - screenY: "screenY", - shiftKey: "shiftKey", - isPrimary: "isPrimary", - }, - })), - ...["touchcancel", "touchend", "touchmove", "touchstart"].map( - (eventType) => ({ - eventType, - case: "touch", - output: { - altKey: "altKey", - ctrlKey: "ctrlKey", - metaKey: "metaKey", - shiftKey: "shiftKey", - }, - }), - ), - { - eventType: "scroll", - case: "ui", - output: { - detail: "detail", - }, - }, - { - eventType: "wheel", - case: "wheel", - output: { - deltaMode: "deltaMode", - deltaX: "deltaX", - deltaY: "deltaY", - deltaZ: "deltaZ", - }, - }, - ...["animationstart", "animationend", "animationiteration"].map( - (eventType) => ({ - eventType, - case: "animation", - output: { - animationName: "animationName", - pseudoElement: "pseudoElement", - elapsedTime: "elapsedTime", - }, - }), - ), - { - eventType: "transitionend", - case: "transition", - output: { - propertyName: "propertyName", - pseudoElement: "pseudoElement", - elapsedTime: "elapsedTime", - }, - }, -].forEach((expectation) => { - test(`serializeEvent() adds ${expectation.case} attributes`, () => { - assertEqualSerializedEventData( - { ...allEventData, type: expectation.eventType }, - expectation.output, - ); - }); -}); - -const mockElementsToSelect = ` -
-

START

-

MIDDLE

-

END

-
-`; - -test("serializeEvent() adds text of current selection", () => { - document.body.innerHTML = mockElementsToSelect; - const start = document.getElementById("start"); - const end = document.getElementById("end"); - window.getSelection().setBaseAndExtent(start, 0, end, 0); - assertEqualSerializedEventData( - { ...allEventData, type: "select" }, - { - selectedText: "START\nMIDDLE\n", - }, - ); -}); - -test.run(); diff --git a/src/client/packages/event-to-object/tests/event-to-object.test.ts b/src/client/packages/event-to-object/tests/event-to-object.test.ts new file mode 100644 index 000000000..f6ff4d185 --- /dev/null +++ b/src/client/packages/event-to-object/tests/event-to-object.test.ts @@ -0,0 +1,125 @@ +// @ts-ignore +import { window } from "./tooling/setup"; +import { test } from "uvu"; +import { checkEventConversion } from "./tooling/check"; +import { mockElement } from "./tooling/mock"; + +type SimpleTestCase = { + types: string[]; + description: string; + givenEventType: new (type: string) => Event; + expectedConversion: any; +}; + +const simpleTestCases: SimpleTestCase[] = [ + { + types: [ + "animationcancel", + "animationend", + "animationiteration", + "animationstart", + ], + description: "animation event", + givenEventType: window.AnimationEvent, + expectedConversion: { + animationName: "", + pseudoElement: "", + elapsedTime: 0, + }, + }, + { + types: ["copy", "cut", "paste"], + description: "clipboad event", + givenEventType: window.ClipboardEvent, + expectedConversion: { clipboardData: null }, + }, + { + types: ["beforeinput"], + description: "event", + givenEventType: window.InputEvent, + expectedConversion: { + detail: 0, + data: "", + inputType: "", + dataTransfer: null, + isComposing: false, + }, + }, + { + types: [ + "click", + "auxclick", + "dblclick", + "mousedown", + "mouseenter", + "mouseleave", + "mousemove", + "mouseout", + "mouseover", + "mouseup", + "scroll", + ], + description: "mouse event", + givenEventType: window.MouseEvent, + expectedConversion: { + altKey: false, + button: 0, + buttons: 0, + clientX: 0, + clientY: 0, + ctrlKey: false, + metaKey: false, + movementX: 0, + movementY: 0, + offsetX: 0, + offsetY: 0, + pageX: 0, + pageY: 0, + relatedTarget: null, + screenX: 0, + screenY: 0, + shiftKey: false, + x: undefined, + y: undefined, + }, + }, +]; + +simpleTestCases.forEach((testCase) => { + testCase.types.forEach((type) => { + test(`converts ${type} ${testCase.description}`, () => { + const event = new testCase.givenEventType(type); + checkEventConversion(event, testCase.expectedConversion); + }); + }); +}); + +test("adds text of current selection", () => { + document.body.innerHTML = ` +
+

START

+

MIDDLE

+

END

+
+ `; + const start = document.getElementById("start"); + const end = document.getElementById("end"); + window.getSelection()!.setBaseAndExtent(start!, 0, end!, 0); + checkEventConversion(new Event("fake"), { + type: "fake", + selection: { + type: "Range", + anchorNode: { ...mockElement, tagName: "P" }, + anchorOffset: 0, + focusNode: { ...mockElement, tagName: "P" }, + focusOffset: 0, + isCollapsed: false, + rangeCount: 1, + selectedText: "START\n MIDDLE\n ", + }, + eventPhase: 0, + isTrusted: false, + }); +}); + +test.run(); diff --git a/src/client/packages/event-to-object/tests/tooling/check.ts b/src/client/packages/event-to-object/tests/tooling/check.ts new file mode 100644 index 000000000..95e99111c --- /dev/null +++ b/src/client/packages/event-to-object/tests/tooling/check.ts @@ -0,0 +1,49 @@ +import * as assert from "uvu/assert"; +// @ts-ignore +import lodash from "lodash"; +import convert from "../../src/index"; +import { mockElement } from "./mock"; + +export function checkEventConversion( + givenEvent: Event, + expectedConversion: any, +): void { + const commonEventData = { + target: mockElement, + currentTarget: mockElement, + relatedTarget: mockElement, + }; + + const actualSerializedEvent = convert(givenEvent); + + if (!actualSerializedEvent) { + assert.equal(actualSerializedEvent, expectedConversion); + return; + } + + // too hard to compare + assert.equal(typeof actualSerializedEvent.timeStamp, "number"); + + assert.equal( + actualSerializedEvent, + lodash.merge( + { timeStamp: actualSerializedEvent.timeStamp, type: givenEvent.type }, + expectedConversionDefaults, + expectedConversion, + ), + ); + + // verify result is JSON serializable + JSON.stringify(actualSerializedEvent); +} + +const expectedConversionDefaults = { + target: null, + currentTarget: null, + bubbles: false, + composed: false, + defaultPrevented: false, + eventPhase: undefined, + isTrusted: undefined, + selection: null, +}; diff --git a/src/client/packages/event-to-object/tests/tooling/dom.js b/src/client/packages/event-to-object/tests/tooling/dom.js deleted file mode 100644 index e97eac844..000000000 --- a/src/client/packages/event-to-object/tests/tooling/dom.js +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from "react"; -import * as ReactTestUtils from "react-dom/test-utils.js"; - -export function render(Tag, props = {}) { - const container = window.document.querySelector("main"); - const component = React.h(Tag, props); - React.render(component, container); - return { container, component }; -} - -export async function fire(elem, event, details) { - await ReactTestUtils.act(() => { - let evt = new window.Event(event, details); - elem.dispatchEvent(evt); - }); -} diff --git a/src/client/packages/event-to-object/tests/tooling/mock.ts b/src/client/packages/event-to-object/tests/tooling/mock.ts new file mode 100644 index 000000000..de6ee79db --- /dev/null +++ b/src/client/packages/event-to-object/tests/tooling/mock.ts @@ -0,0 +1,15 @@ +export const mockBoundingRect = { + left: 0, + top: 0, + right: 0, + bottom: 0, + x: 0, + y: 0, + height: 0, + width: 0, +}; + +export const mockElement = { + tagName: null, + boundingClientRect: mockBoundingRect, +}; diff --git a/src/client/packages/event-to-object/tests/tooling/setup.js b/src/client/packages/event-to-object/tests/tooling/setup.js index 86f50ed14..213578046 100644 --- a/src/client/packages/event-to-object/tests/tooling/setup.js +++ b/src/client/packages/event-to-object/tests/tooling/setup.js @@ -1,7 +1,7 @@ import { test } from "uvu"; -import { JSDOM } from "jsdom"; +import { Window } from "happy-dom"; -const { window } = new JSDOM("
"); +export const window = new Window(); export function setup() { global.window = window; @@ -15,6 +15,7 @@ export function reset() { window.document.title = ""; window.document.head.innerHTML = ""; window.document.body.innerHTML = "
"; + window.getSelection().removeAllRanges(); } test.before(setup); From ea63d1ab5c503fcfc0ca54c3062e6887e52a34f4 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Sun, 12 Mar 2023 23:06:30 -0700 Subject: [PATCH 07/16] finish tests --- src/client/package-lock.json | 1957 +---------------- .../packages/event-to-object/src/events.ts | 39 +- .../packages/event-to-object/src/index.ts | 3 +- .../tests/event-to-object.test.ts | 284 ++- .../event-to-object/tests/tooling/check.ts | 14 +- .../event-to-object/tests/tooling/mock.ts | 48 +- .../event-to-object/tsconfig.tests.json | 16 + 7 files changed, 372 insertions(+), 1989 deletions(-) create mode 100644 src/client/packages/event-to-object/tsconfig.tests.json diff --git a/src/client/package-lock.json b/src/client/package-lock.json index 68253e9e8..860a41944 100644 --- a/src/client/package-lock.json +++ b/src/client/package-lock.json @@ -92,235 +92,18 @@ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", "dev": true }, - "node_modules/abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "node_modules/bufferutil": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz", - "integrity": "sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "peer": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", "dev": true }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, "node_modules/csstype": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", "dev": true }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/decimal.js": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", - "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", - "dev": true - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -339,37 +122,6 @@ "node": ">=0.3.1" } }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "node_modules/esbuild": { "version": "0.15.18", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", @@ -727,134 +479,15 @@ "node": ">=12" } }, - "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/event-to-object": { "resolved": "packages/event-to-object", "link": true }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, "node_modules/foreach": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==" }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -875,15 +508,6 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - } - }, "node_modules/happy-dom": { "version": "8.9.0", "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-8.9.0.tgz", @@ -941,29 +565,6 @@ "node": ">=12" } }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dev": true, - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -985,45 +586,6 @@ "he": "bin/he" } }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-core-module": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", @@ -1036,92 +598,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT", "peer": true }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "node_modules/jsdom": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.5.0.tgz", - "integrity": "sha512-QxZH0nmDTnTTVI0YDm4RUlaUPl5dcyn62G5TMDNfMmTW+J1u1v9gCR8WR+WZ6UghAa7nKJjDOFaI00eMMWvJFQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.0.5", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "html-encoding-sniffer": "^2.0.1", - "is-potential-custom-element-name": "^1.0.0", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "request": "^2.88.2", - "request-promise-native": "^1.0.9", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0", - "ws": "^7.4.4", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/json-pointer": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", @@ -1130,39 +611,6 @@ "foreach": "^2.0.4" } }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -1172,19 +620,6 @@ "node": ">=6" } }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -1202,27 +637,6 @@ "loose-envify": "cli.js" } }, - "node_modules/mime-db": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", - "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", - "dev": true, - "dependencies": { - "mime-db": "1.47.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -1286,34 +700,6 @@ "webidl-conversions": "^3.0.0" } }, - "node_modules/node-gyp-build": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", - "dev": true, - "optional": true, - "peer": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/object-assign": { "version": "4.1.1", "license": "MIT", @@ -1322,41 +708,12 @@ "node": ">=0.10.0" } }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -1396,15 +753,6 @@ "url": "https://opencollective.com/preact" } }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/prop-types": { "version": "15.7.2", "license": "MIT", @@ -1415,36 +763,6 @@ "react-is": "^16.8.1" } }, - "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, "node_modules/react": { "version": "16.14.0", "license": "MIT", @@ -1477,103 +795,6 @@ "license": "MIT", "peer": true }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dev": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "dev": true, - "dependencies": { - "lodash": "^4.17.19" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "request": "^2.34" - } - }, - "node_modules/request-promise-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", - "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", - "deprecated": "request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142", - "dev": true, - "dependencies": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "engines": { - "node": ">=0.12.0" - }, - "peerDependencies": { - "request": "^2.34" - } - }, - "node_modules/request-promise-native/node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/request/node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -1603,44 +824,12 @@ "node": ">=6" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/scheduler": { "version": "0.19.1", "license": "MIT", @@ -1659,35 +848,6 @@ "node": ">=0.10.0" } }, - "node_modules/sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -1700,39 +860,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "node_modules/tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", - "dev": true, - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tr46": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", - "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/tsm": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tsm/-/tsm-2.3.0.tgz", @@ -1748,36 +875,6 @@ "node": ">=12" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", @@ -1791,58 +888,6 @@ "node": ">=4.2.0" } }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/utf-8-validate": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", - "integrity": "sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "peer": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/uvu": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", @@ -1861,20 +906,6 @@ "node": ">=8" } }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "node_modules/vite": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.8.tgz", @@ -1931,107 +962,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true, - "engines": { - "node": ">=10.4" - } - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz", - "integrity": "sha512-fy+R77xWv0AiqfLl4nuGUlQ3/6b5uNfQ4WAbGQVMYshCTCCPK9psC1nWh3XHuxGVCtlcDDQPQW1csmmIQo+fwg==", - "dev": true, - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.0.2", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ws": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", - "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, "packages/@reactpy/app": { "version": "1.0.0", "extraneous": true, @@ -2158,7 +1088,6 @@ }, "devDependencies": { "happy-dom": "^8.9.0", - "jsdom": "16.5.0", "lodash": "^4.17.21", "prettier": "^3.0.0-alpha.6", "tsm": "^2.0.0", @@ -2320,242 +1249,34 @@ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", "dev": true }, - "abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", "dev": true }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", "dev": true }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } + "diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "bufferutil": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz", - "integrity": "sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "node-gyp-build": "^4.3.0" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true - }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, - "csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", - "dev": true - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - } - }, - "decimal.js": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", - "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", - "dev": true - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true - }, - "diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", - "dev": true - }, - "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true - } - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "esbuild": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", - "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", + "esbuild": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", "dev": true, "requires": { "@esbuild/android-arm": "0.15.18", @@ -2722,52 +1443,11 @@ "dev": true, "optional": true }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, "event-to-object": { "version": "file:packages/event-to-object", "requires": { "event-to-object": "file:packages/event-to-object", - "happy-dom": "*", - "jsdom": "16.5.0", + "happy-dom": "^8.9.0", "json-pointer": "^0.6.2", "lodash": "^4.17.21", "prettier": "^3.0.0-alpha.6", @@ -2787,58 +1467,11 @@ } } }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, "foreach": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==" }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, "fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -2852,15 +1485,6 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, "happy-dom": { "version": "8.9.0", "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-8.9.0.tgz", @@ -2908,22 +1532,6 @@ } } }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -2939,35 +1547,6 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.5" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, "is-core-module": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", @@ -2977,76 +1556,10 @@ "has": "^1.0.3" } }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, "js-tokens": { "version": "4.0.0", "peer": true }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "jsdom": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.5.0.tgz", - "integrity": "sha512-QxZH0nmDTnTTVI0YDm4RUlaUPl5dcyn62G5TMDNfMmTW+J1u1v9gCR8WR+WZ6UghAa7nKJjDOFaI00eMMWvJFQ==", - "dev": true, - "requires": { - "abab": "^2.0.5", - "acorn": "^8.0.5", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "html-encoding-sniffer": "^2.0.1", - "is-potential-custom-element-name": "^1.0.0", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "request": "^2.88.2", - "request-promise-native": "^1.0.9", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0", - "ws": "^7.4.4", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true - } - } - }, "json-pointer": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", @@ -3055,52 +1568,12 @@ "foreach": "^2.0.4" } }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, "kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", "dev": true }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -3114,21 +1587,6 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "mime-db": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", - "dev": true - }, - "mime-types": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", - "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", - "dev": true, - "requires": { - "mime-db": "1.47.0" - } - }, "mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -3174,62 +1632,16 @@ } } }, - "node-gyp-build": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", - "dev": true, - "optional": true, - "peer": true - }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, "object-assign": { "version": "4.1.1", "peer": true }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -3252,12 +1664,6 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.7.0.tgz", "integrity": "sha512-9MEURwzNMKpAil/t6+wabDIJI6oG6GnwypYxiJDvQnW+fHDTt51PYuLZ1QUM31hFr7sDaj9qTaShAF9VIxuxGQ==" }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true - }, "prop-types": { "version": "15.7.2", "peer": true, @@ -3267,30 +1673,6 @@ "react-is": "^16.8.1" } }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, "react": { "version": "16.14.0", "peer": true, @@ -3314,84 +1696,6 @@ "version": "16.13.1", "peer": true }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } - } - }, - "request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "dev": true, - "requires": { - "lodash": "^4.17.19" - } - }, - "request-promise-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", - "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", - "dev": true, - "requires": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } - } - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -3412,27 +1716,12 @@ "mri": "^1.1.0" } }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, "scheduler": { "version": "0.19.1", "peer": true, @@ -3447,62 +1736,12 @@ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "dev": true }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true - }, "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - } - }, - "tr46": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", - "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, "tsm": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tsm/-/tsm-2.3.0.tgz", @@ -3512,78 +1751,12 @@ "esbuild": "^0.15.16" } }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, "typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true }, - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "utf-8-validate": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", - "integrity": "sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "node-gyp-build": "^4.3.0" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - }, "uvu": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", @@ -3596,17 +1769,6 @@ "sade": "^1.7.3" } }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "vite": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.8.tgz", @@ -3630,81 +1792,6 @@ } } } - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "requires": { - "xml-name-validator": "^3.0.0" - } - }, - "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz", - "integrity": "sha512-fy+R77xWv0AiqfLl4nuGUlQ3/6b5uNfQ4WAbGQVMYshCTCCPK9psC1nWh3XHuxGVCtlcDDQPQW1csmmIQo+fwg==", - "dev": true, - "requires": { - "lodash": "^4.7.0", - "tr46": "^2.0.2", - "webidl-conversions": "^6.1.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "ws": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", - "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "dev": true, - "requires": {} - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true } } } diff --git a/src/client/packages/event-to-object/src/events.ts b/src/client/packages/event-to-object/src/events.ts index 24164027f..bdc353501 100644 --- a/src/client/packages/event-to-object/src/events.ts +++ b/src/client/packages/event-to-object/src/events.ts @@ -27,18 +27,18 @@ export type EventToObjectMap = { export interface EventObject { bubbles: boolean; composed: boolean; - currentTarget: EventTargetObject | null; + currentTarget: ElementObject | null; defaultPrevented: boolean; eventPhase: number; isTrusted: boolean; - target: EventTargetObject | null; + target: ElementObject | null; timeStamp: DOMHighResTimeStamp; type: string; selection: SelectionObject | null; } export interface SubmitEventObject extends EventObject { - submitter: EventTargetObject; + submitter: ElementObject; } export interface InputEventObject extends UIEventObject { @@ -117,7 +117,7 @@ export interface MouseEventObject extends EventObject { offsetY: number; pageX: number; pageY: number; - relatedTarget: EventTargetObject | null; + relatedTarget: ElementObject | null; screenX: number; screenY: number; shiftKey: boolean; @@ -164,7 +164,7 @@ export interface KeyboardEventObject extends UIEventObject { } export interface FocusEventObject extends UIEventObject { - relatedTarget: EventTargetObject | null; + relatedTarget: ElementObject | null; } export interface TouchEventObject extends UIEventObject { @@ -215,28 +215,11 @@ export interface TouchObject { rotationAngle: number; screenX: number; screenY: number; - target: EventTargetObject; + target: ElementObject; } export interface DataTransferObject { - /** - * Returns the kind of operation that is currently selected. If the kind of operation - * isn't one of those that is allowed by the effectAllowed attribute, then the - * operation will fail. - * - * Can be set, to change the selected operation. - * - * The possible values are "none", "copy", "link", and "move". - */ dropEffect: "none" | "copy" | "link" | "move"; - /** - * Returns the kinds of operations that are to be allowed. - * - * Can be set (during the dragstart event), to change the allowed operations. - * - * The possible values are "none", "copy", "copyLink", "copyMove", "link", "linkMove", - * "move", "all", and "uninitialized", - */ effectAllowed: | "none" | "copy" @@ -247,19 +230,15 @@ export interface DataTransferObject { | "move" | "all" | "uninitialized"; - /** Returns a FileList of the files being dragged, if any. */ files: FileListObject; - /** Returns a DataTransferItemList object, with the drag data. */ items: DataTransferItemListObject; - /** Lists the formats that were set in the dragstart event. In addition, if any files - * are being dragged, then one of the types will be the string "Files". */ types: string[]; } export interface SelectionObject { - anchorNode: EventTargetObject | null; + anchorNode: ElementObject | null; anchorOffset: number; - focusNode: EventTargetObject | null; + focusNode: ElementObject | null; focusOffset: number; isCollapsed: boolean; rangeCount: number; @@ -267,7 +246,7 @@ export interface SelectionObject { selectedText: string; } -export interface EventTargetObject { +export interface ElementObject { value?: string; textContent?: string; } diff --git a/src/client/packages/event-to-object/src/index.ts b/src/client/packages/event-to-object/src/index.ts index 0da436a7b..9a40a2128 100644 --- a/src/client/packages/event-to-object/src/index.ts +++ b/src/client/packages/event-to-object/src/index.ts @@ -212,7 +212,6 @@ const eventConverters: { [key: string]: (event: any) => any } = { // gamepad events gamepadconnected: convertGamepadEvent, gamepaddisconnected: convertGamepadEvent, - gotpointercapture: convertPointerEvent, // keyboard events keydown: convertKeyboardEvent, keypress: convertKeyboardEvent, @@ -221,6 +220,7 @@ const eventConverters: { [key: string]: (event: any) => any } = { auxclick: convertMouseEvent, click: convertMouseEvent, dblclick: convertMouseEvent, + contextmenu: convertMouseEvent, mousedown: convertMouseEvent, mouseenter: convertMouseEvent, mouseleave: convertMouseEvent, @@ -230,6 +230,7 @@ const eventConverters: { [key: string]: (event: any) => any } = { mouseup: convertMouseEvent, scroll: convertMouseEvent, // pointer events + gotpointercapture: convertPointerEvent, lostpointercapture: convertPointerEvent, pointercancel: convertPointerEvent, pointerdown: convertPointerEvent, diff --git a/src/client/packages/event-to-object/tests/event-to-object.test.ts b/src/client/packages/event-to-object/tests/event-to-object.test.ts index f6ff4d185..bad3c28aa 100644 --- a/src/client/packages/event-to-object/tests/event-to-object.test.ts +++ b/src/client/packages/event-to-object/tests/event-to-object.test.ts @@ -1,17 +1,24 @@ // @ts-ignore import { window } from "./tooling/setup"; import { test } from "uvu"; +import { Event } from "happy-dom"; import { checkEventConversion } from "./tooling/check"; -import { mockElement } from "./tooling/mock"; +import { + mockElementObject, + mockGamepad, + mockTouch, + mockTouchObject, +} from "./tooling/mock"; -type SimpleTestCase = { +type SimpleTestCase = { types: string[]; description: string; - givenEventType: new (type: string) => Event; + givenEventType: new (type: string) => E; expectedConversion: any; + initGivenEvent?: (event: E) => void; }; -const simpleTestCases: SimpleTestCase[] = [ +const simpleTestCases: SimpleTestCase[] = [ { types: [ "animationcancel", @@ -27,6 +34,27 @@ const simpleTestCases: SimpleTestCase[] = [ elapsedTime: 0, }, }, + { + types: ["beforeinput"], + description: "event", + givenEventType: window.InputEvent, + expectedConversion: { + detail: 0, + data: "", + inputType: "", + dataTransfer: null, + isComposing: false, + }, + }, + { + types: ["compositionend", "compositionstart", "compositionupdate"], + description: "composition event", + givenEventType: window.CompositionEvent, + expectedConversion: { + data: undefined, + detail: undefined, + }, + }, { types: ["copy", "cut", "paste"], description: "clipboad event", @@ -34,15 +62,79 @@ const simpleTestCases: SimpleTestCase[] = [ expectedConversion: { clipboardData: null }, }, { - types: ["beforeinput"], + types: [ + "drag", + "dragend", + "dragenter", + "dragleave", + "dragover", + "dragstart", + "drop", + ], + description: "drag event", + givenEventType: window.DragEvent, + expectedConversion: { + altKey: undefined, + button: undefined, + buttons: undefined, + clientX: undefined, + clientY: undefined, + ctrlKey: undefined, + dataTransfer: null, + metaKey: undefined, + movementX: undefined, + movementY: undefined, + offsetX: undefined, + offsetY: undefined, + pageX: undefined, + pageY: undefined, + relatedTarget: null, + screenX: undefined, + screenY: undefined, + shiftKey: undefined, + x: undefined, + y: undefined, + }, + }, + { + types: ["error"], description: "event", - givenEventType: window.InputEvent, + givenEventType: window.ErrorEvent, + expectedConversion: { detail: 0 }, + }, + { + types: ["blur", "focus", "focusin", "focusout"], + description: "focus event", + givenEventType: window.FocusEvent, expectedConversion: { + relatedTarget: null, detail: 0, - data: "", - inputType: "", - dataTransfer: null, + }, + }, + { + types: ["gamepadconnected", "gamepaddisconnected"], + description: "gamepad event", + givenEventType: window.GamepadEvent, + expectedConversion: { gamepad: mockGamepad }, + initGivenEvent: (event) => { + event.gamepad = mockGamepad; + }, + }, + { + types: ["keydown", "keypress", "keyup"], + description: "keyboard event", + givenEventType: window.KeyboardEvent, + expectedConversion: { + altKey: false, + code: "", + ctrlKey: false, isComposing: false, + key: "", + location: 0, + metaKey: false, + repeat: false, + shiftKey: false, + detail: 0, }, }, { @@ -83,12 +175,176 @@ const simpleTestCases: SimpleTestCase[] = [ y: undefined, }, }, + { + types: [ + "auxclick", + "click", + "contextmenu", + "dblclick", + "mousedown", + "mouseenter", + "mouseleave", + "mousemove", + "mouseout", + "mouseover", + "mouseup", + ], + description: "mouse event", + givenEventType: window.MouseEvent, + expectedConversion: { + altKey: false, + button: 0, + buttons: 0, + clientX: 0, + clientY: 0, + ctrlKey: false, + metaKey: false, + movementX: 0, + movementY: 0, + offsetX: 0, + offsetY: 0, + pageX: 0, + pageY: 0, + relatedTarget: null, + screenX: 0, + screenY: 0, + shiftKey: false, + x: undefined, + y: undefined, + }, + }, + { + types: [ + "gotpointercapture", + "lostpointercapture", + "pointercancel", + "pointerdown", + "pointerenter", + "pointerleave", + "pointerlockchange", + "pointerlockerror", + "pointermove", + "pointerout", + "pointerover", + "pointerup", + ], + description: "pointer event", + givenEventType: window.PointerEvent, + expectedConversion: { + altKey: false, + button: 0, + buttons: 0, + clientX: 0, + clientY: 0, + ctrlKey: false, + metaKey: false, + movementX: 0, + movementY: 0, + offsetX: 0, + offsetY: 0, + pageX: 0, + pageY: 0, + relatedTarget: null, + screenX: 0, + screenY: 0, + shiftKey: false, + x: undefined, + y: undefined, + pointerId: 0, + pointerType: "", + pressure: 0, + tiltX: 0, + tiltY: 0, + width: 0, + height: 0, + isPrimary: false, + twist: 0, + tangentialPressure: 0, + }, + }, + { + types: ["submit"], + description: "event", + givenEventType: window.Event, + expectedConversion: { submitter: null }, + initGivenEvent: (event) => { + event.submitter = null; + }, + }, + { + types: ["touchcancel", "touchend", "touchmove", "touchstart"], + description: "touch event", + givenEventType: window.TouchEvent, + expectedConversion: { + altKey: undefined, + changedTouches: [mockTouchObject], + ctrlKey: undefined, + metaKey: undefined, + targetTouches: [mockTouchObject], + touches: [mockTouchObject], + detail: undefined, + shiftKey: undefined, + }, + initGivenEvent: (event) => { + event.changedTouches = [mockTouch]; + event.targetTouches = [mockTouch]; + event.touches = [mockTouch]; + }, + }, + { + types: [ + "transitioncancel", + "transitionend", + "transitionrun", + "transitionstart", + ], + description: "transition event", + givenEventType: window.TransitionEvent, + expectedConversion: { + propertyName: undefined, + elapsedTime: undefined, + pseudoElement: undefined, + }, + }, + { + types: ["wheel"], + description: "wheel event", + givenEventType: window.WheelEvent, + expectedConversion: { + altKey: undefined, + button: undefined, + buttons: undefined, + clientX: undefined, + clientY: undefined, + ctrlKey: undefined, + deltaMode: 0, + deltaX: 0, + deltaY: 0, + deltaZ: 0, + metaKey: undefined, + movementX: undefined, + movementY: undefined, + offsetX: undefined, + offsetY: undefined, + pageX: 0, + pageY: 0, + relatedTarget: null, + screenX: undefined, + screenY: undefined, + shiftKey: undefined, + x: undefined, + y: undefined, + }, + }, ]; simpleTestCases.forEach((testCase) => { testCase.types.forEach((type) => { test(`converts ${type} ${testCase.description}`, () => { const event = new testCase.givenEventType(type); + if (testCase.initGivenEvent) { + testCase.initGivenEvent(event); + } checkEventConversion(event, testCase.expectedConversion); }); }); @@ -105,20 +361,20 @@ test("adds text of current selection", () => { const start = document.getElementById("start"); const end = document.getElementById("end"); window.getSelection()!.setBaseAndExtent(start!, 0, end!, 0); - checkEventConversion(new Event("fake"), { + checkEventConversion(new window.Event("fake"), { type: "fake", selection: { type: "Range", - anchorNode: { ...mockElement, tagName: "P" }, + anchorNode: { ...mockElementObject, tagName: "P" }, anchorOffset: 0, - focusNode: { ...mockElement, tagName: "P" }, + focusNode: { ...mockElementObject, tagName: "P" }, focusOffset: 0, isCollapsed: false, rangeCount: 1, selectedText: "START\n MIDDLE\n ", }, - eventPhase: 0, - isTrusted: false, + eventPhase: undefined, + isTrusted: undefined, }); }); diff --git a/src/client/packages/event-to-object/tests/tooling/check.ts b/src/client/packages/event-to-object/tests/tooling/check.ts index 95e99111c..42a77480b 100644 --- a/src/client/packages/event-to-object/tests/tooling/check.ts +++ b/src/client/packages/event-to-object/tests/tooling/check.ts @@ -1,20 +1,18 @@ import * as assert from "uvu/assert"; +import { Event } from "happy-dom"; // @ts-ignore import lodash from "lodash"; import convert from "../../src/index"; -import { mockElement } from "./mock"; +import { mockElementObject } from "./mock"; export function checkEventConversion( givenEvent: Event, expectedConversion: any, ): void { - const commonEventData = { - target: mockElement, - currentTarget: mockElement, - relatedTarget: mockElement, - }; - - const actualSerializedEvent = convert(givenEvent); + const actualSerializedEvent = convert( + // @ts-ignore + givenEvent, + ); if (!actualSerializedEvent) { assert.equal(actualSerializedEvent, expectedConversion); diff --git a/src/client/packages/event-to-object/tests/tooling/mock.ts b/src/client/packages/event-to-object/tests/tooling/mock.ts index de6ee79db..81e506500 100644 --- a/src/client/packages/event-to-object/tests/tooling/mock.ts +++ b/src/client/packages/event-to-object/tests/tooling/mock.ts @@ -9,7 +9,53 @@ export const mockBoundingRect = { width: 0, }; -export const mockElement = { +export const mockElementObject = { tagName: null, boundingClientRect: mockBoundingRect, }; + +export const mockElement = { + tagName: null, + getBoundingClientRect: () => mockBoundingRect, +}; + +export const mockGamepad = { + id: "test", + index: 0, + connected: true, + mapping: "standard", + axes: [], + buttons: [ + { + pressed: false, + touched: false, + value: 0, + }, + ], + hapticActuators: [ + { + type: "vibration", + }, + ], + timestamp: undefined, +}; + +export const mockTouch = { + identifier: 0, + pageX: 0, + pageY: 0, + screenX: 0, + screenY: 0, + clientX: 0, + clientY: 0, + force: 0, + radiusX: 0, + radiusY: 0, + rotationAngle: 0, + target: mockElement, +}; + +export const mockTouchObject = { + ...mockTouch, + target: mockElementObject, +}; diff --git a/src/client/packages/event-to-object/tsconfig.tests.json b/src/client/packages/event-to-object/tsconfig.tests.json new file mode 100644 index 000000000..33be69a56 --- /dev/null +++ b/src/client/packages/event-to-object/tsconfig.tests.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "esnext", + "allowJs": false, + "skipLibCheck": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true + } +} From a203386bdbf4973df539ae661d03c35effa7b062 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Mon, 13 Mar 2023 20:00:21 -0700 Subject: [PATCH 08/16] improve typescript configuration --- src/client/.gitignore | 1 + src/client/package-lock.json | 41 +++++++----------- src/client/package.json | 10 +---- src/client/{ => packages/app}/index.html | 4 +- src/client/packages/app/package.json | 20 +++++---- .../app}/public/assets/reactpy-logo.ico | Bin src/client/packages/app/tsconfig.json | 12 ++++- src/client/{ => packages/app}/vite.config.js | 2 +- src/client/packages/client/package.json | 20 +++++---- src/client/packages/client/tsconfig.json | 12 ++++- .../packages/event-to-object/package.json | 18 ++++---- .../packages/event-to-object/tsconfig.json | 5 +++ src/client/tsconfig.json | 17 ++++---- 13 files changed, 88 insertions(+), 74 deletions(-) create mode 100644 src/client/.gitignore rename src/client/{ => packages/app}/index.html (82%) rename src/client/{ => packages/app}/public/assets/reactpy-logo.ico (100%) rename src/client/{ => packages/app}/vite.config.js (74%) diff --git a/src/client/.gitignore b/src/client/.gitignore new file mode 100644 index 000000000..cc1b7f164 --- /dev/null +++ b/src/client/.gitignore @@ -0,0 +1 @@ +tsconfig.tsbuildinfo diff --git a/src/client/package-lock.json b/src/client/package-lock.json index 860a41944..f93a4919d 100644 --- a/src/client/package-lock.json +++ b/src/client/package-lock.json @@ -9,10 +9,7 @@ "license": "MIT", "workspaces": [ "./packages/*" - ], - "devDependencies": { - "vite": "^3.1.8" - } + ] }, "node_modules/@esbuild/android-arm": { "version": "0.15.18", @@ -1010,19 +1007,16 @@ "version": "0.45.0", "license": "MIT", "dependencies": { - "@reactpy/client": "file:packages/client", + "@reactpy/client": "^0.1.0", "preact": "^10.7.0" }, "devDependencies": { "@types/react-dom": "^18.0.11", "prettier": "^3.0.0-alpha.6", - "typescript": "^4.9.5" + "typescript": "^4.9.5", + "vite": "^3.1.8" } }, - "packages/app/node_modules/@reactpy/client": { - "resolved": "packages/app/packages/client", - "link": true - }, "packages/app/node_modules/prettier": { "version": "3.0.0-alpha.6", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", @@ -1038,13 +1032,15 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "packages/app/packages/client": {}, + "packages/app/packages/client": { + "extraneous": true + }, "packages/client": { "name": "@reactpy/client", "version": "0.1.0", "license": "MIT", "dependencies": { - "event-to-object": "file:packages/event-to-object", + "event-to-object": "^0.1.0", "json-pointer": "^0.6.2" }, "devDependencies": { @@ -1059,10 +1055,6 @@ "react-dom": ">=16" } }, - "packages/client/node_modules/event-to-object": { - "resolved": "packages/client/packages/event-to-object", - "link": true - }, "packages/client/node_modules/prettier": { "version": "3.0.0-alpha.6", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", @@ -1078,7 +1070,9 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "packages/client/packages/event-to-object": {}, + "packages/client/packages/event-to-object": { + "extraneous": true + }, "packages/event-to-object": { "version": "0.1.0", "license": "MIT", @@ -1170,16 +1164,14 @@ "@reactpy/app": { "version": "file:packages/app", "requires": { - "@reactpy/client": "file:packages/client", + "@reactpy/client": "^0.1.0", "@types/react-dom": "^18.0.11", "preact": "^10.7.0", "prettier": "^3.0.0-alpha.6", - "typescript": "^4.9.5" + "typescript": "^4.9.5", + "vite": "^3.1.8" }, "dependencies": { - "@reactpy/client": { - "version": "file:packages/app/packages/client" - }, "prettier": { "version": "3.0.0-alpha.6", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", @@ -1194,15 +1186,12 @@ "@types/json-pointer": "^1.0.31", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", - "event-to-object": "file:packages/event-to-object", + "event-to-object": "^0.1.0", "json-pointer": "^0.6.2", "prettier": "^3.0.0-alpha.6", "typescript": "^4.9.5" }, "dependencies": { - "event-to-object": { - "version": "file:packages/client/packages/event-to-object" - }, "prettier": { "version": "3.0.0-alpha.6", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", diff --git a/src/client/package.json b/src/client/package.json index f44a517fe..443553428 100644 --- a/src/client/package.json +++ b/src/client/package.json @@ -1,22 +1,14 @@ { - "devDependencies": { - "vite": "^3.1.8" - }, "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/reactive-python/reactpy" - }, "scripts": { - "build": "vite build", "publish": "npm --workspaces publish", "test": "npm --workspaces test", + "build": "npm --workspaces run build", "format": "npm --workspaces run format", "check:format": "npm --workspaces run check:format", "check:tests": "npm --workspaces run check:tests", "check:types": "npm --workspaces run check:types" }, - "version": "1.0.0", "workspaces": [ "./packages/*" ] diff --git a/src/client/index.html b/src/client/packages/app/index.html similarity index 82% rename from src/client/index.html rename to src/client/packages/app/index.html index a7a3cff4f..d734a1814 100644 --- a/src/client/index.html +++ b/src/client/packages/app/index.html @@ -1,4 +1,4 @@ - + @@ -8,7 +8,7 @@
diff --git a/src/client/packages/app/package.json b/src/client/packages/app/package.json index 145d30230..7ceb8268e 100644 --- a/src/client/packages/app/package.json +++ b/src/client/packages/app/package.json @@ -1,28 +1,30 @@ { "author": "Ryan Morshead", + "license": "MIT", + "main": "src/dist/index.js", + "types": "src/dist/index.d.ts", + "name": "@reactpy/app", + "description": "A client application for ReactPy implemented in React", "dependencies": { - "@reactpy/client": "file:packages/client", + "@reactpy/client": "^0.1.0", "preact": "^10.7.0" }, - "description": "A client application for ReactPy implemented in React", "devDependencies": { "prettier": "^3.0.0-alpha.6", "typescript": "^4.9.5", - "@types/react-dom": "^18.0.11" + "@types/react-dom": "^18.0.11", + "vite": "^3.1.8" }, - "license": "MIT", - "main": "src/index.tsx", - "name": "@reactpy/app", "repository": { "type": "git", "url": "https://github.com/reactive-python/reactpy" }, "scripts": { + "build": "vite build", "format": "prettier --write ./src", "test": "npm run check:tests", "check:format": "prettier --check ./src", "check:tests": "echo 'no tests'", - "check:types": "tsc" - }, - "version": "0.45.0" + "check:types": "tsc --noEmit" + } } diff --git a/src/client/public/assets/reactpy-logo.ico b/src/client/packages/app/public/assets/reactpy-logo.ico similarity index 100% rename from src/client/public/assets/reactpy-logo.ico rename to src/client/packages/app/public/assets/reactpy-logo.ico diff --git a/src/client/packages/app/tsconfig.json b/src/client/packages/app/tsconfig.json index 596e2cf72..8f31503be 100644 --- a/src/client/packages/app/tsconfig.json +++ b/src/client/packages/app/tsconfig.json @@ -1,4 +1,14 @@ { "extends": "../../tsconfig.json", - "include": ["src"] + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": true + }, + "include": ["src"], + "references": [ + { + "path": "../client" + } + ] } diff --git a/src/client/vite.config.js b/src/client/packages/app/vite.config.js similarity index 74% rename from src/client/vite.config.js rename to src/client/packages/app/vite.config.js index 7bfe66bca..df41ee669 100644 --- a/src/client/vite.config.js +++ b/src/client/packages/app/vite.config.js @@ -1,7 +1,7 @@ import { defineConfig } from "vite"; export default defineConfig({ - build: { outDir: "../reactpy/_client", emptyOutDir: true }, + build: { outDir: "../../../reactpy/_client", emptyOutDir: true }, resolve: { alias: { react: "preact/compat", diff --git a/src/client/packages/client/package.json b/src/client/packages/client/package.json index 833fc4e28..95bd9759a 100644 --- a/src/client/packages/client/package.json +++ b/src/client/packages/client/package.json @@ -1,11 +1,16 @@ { "author": "Ryan Morshead", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "description": "A client for ReactPy implemented in React", + "license": "MIT", + "name": "@reactpy/client", + "type": "module", + "version": "0.1.0", "dependencies": { - "event-to-object": "file:packages/event-to-object", + "event-to-object": "^0.1.0", "json-pointer": "^0.6.2" }, - "main": "src/index.ts", - "description": "A client for ReactPy implemented in React", "devDependencies": { "@types/json-pointer": "^1.0.31", "@types/react": "^18.0.28", @@ -13,8 +18,6 @@ "prettier": "^3.0.0-alpha.6", "typescript": "^4.9.5" }, - "license": "MIT", - "name": "@reactpy/client", "peerDependencies": { "react": ">=16", "react-dom": ">=16" @@ -24,12 +27,11 @@ "url": "https://github.com/reactive-python/reactpy" }, "scripts": { + "build": "tsc -b", "format": "prettier --write ./src", "test": "npm run check:tests", "check:format": "prettier --check ./src", "check:tests": "echo 'no tests'", - "check:types": "tsc" - }, - "type": "module", - "version": "0.1.0" + "check:types": "tsc --noEmit" + } } diff --git a/src/client/packages/client/tsconfig.json b/src/client/packages/client/tsconfig.json index 596e2cf72..23a34c78b 100644 --- a/src/client/packages/client/tsconfig.json +++ b/src/client/packages/client/tsconfig.json @@ -1,4 +1,14 @@ { "extends": "../../tsconfig.json", - "include": ["src"] + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": true + }, + "include": ["src"], + "references": [ + { + "path": "../event-to-object" + } + ] } diff --git a/src/client/packages/event-to-object/package.json b/src/client/packages/event-to-object/package.json index a0d7dd357..6c0d6b02d 100644 --- a/src/client/packages/event-to-object/package.json +++ b/src/client/packages/event-to-object/package.json @@ -1,10 +1,16 @@ { "author": "Ryan Morshead", + "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "name": "event-to-object", + "description": "A client for ReactPy implemented in React", + "type": "module", + "version": "0.1.0", "dependencies": { "event-to-object": "file:packages/event-to-object", "json-pointer": "^0.6.2" }, - "description": "A client for ReactPy implemented in React", "devDependencies": { "happy-dom": "^8.9.0", "lodash": "^4.17.21", @@ -13,20 +19,16 @@ "typescript": "^4.9.5", "uvu": "^0.5.1" }, - "license": "MIT", - "main": "src/index.js", - "name": "event-to-object", "repository": { "type": "git", "url": "https://github.com/reactive-python/reactpy" }, "scripts": { + "build": "tsc -b", "format": "prettier --write ./src ./tests", "test": "npm run check:tests", "check:format": "prettier --check ./src ./tests", "check:tests": "uvu -r tsm tests", - "check:types": "tsc" - }, - "type": "module", - "version": "0.1.0" + "check:types": "tsc --noEmit" + } } diff --git a/src/client/packages/event-to-object/tsconfig.json b/src/client/packages/event-to-object/tsconfig.json index 596e2cf72..9b0e0b6a5 100644 --- a/src/client/packages/event-to-object/tsconfig.json +++ b/src/client/packages/event-to-object/tsconfig.json @@ -1,4 +1,9 @@ { "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": true + }, "include": ["src"] } diff --git a/src/client/tsconfig.json b/src/client/tsconfig.json index c4f7d98f5..cc694eb6b 100644 --- a/src/client/tsconfig.json +++ b/src/client/tsconfig.json @@ -1,18 +1,19 @@ { "compilerOptions": { - "target": "esnext", - "lib": ["DOM", "DOM.Iterable", "esnext"], "allowJs": false, - "skipLibCheck": false, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, - "strict": true, + "declaration": true, + "esModuleInterop": false, "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "jsx": "react", + "lib": ["DOM", "DOM.Iterable", "esnext"], "module": "esnext", "moduleResolution": "node", + "noEmit": false, "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react" + "skipLibCheck": false, + "strict": true, + "target": "esnext" } } From ed9b929bee3f8d01cc09abd80a568b6787c01f98 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Mon, 13 Mar 2023 20:09:28 -0700 Subject: [PATCH 09/16] show npm version --- noxfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/noxfile.py b/noxfile.py index 37fa4db1a..394ff6643 100644 --- a/noxfile.py +++ b/noxfile.py @@ -60,6 +60,7 @@ def __call__(self, session: Session) -> Callable[[bool], None]: def setup_checks(session: Session) -> None: session.install("--upgrade", "pip") session.run("pip", "--version") + session.run("npm", "--version", external=True) @group.setup("check-javascript") From 136e8638d49952a11f2e18db9ae93d9000ed7860 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Mon, 13 Mar 2023 20:43:17 -0700 Subject: [PATCH 10/16] workspace order --- src/client/package.json | 4 +++- src/client/packages/app/vite.config.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client/package.json b/src/client/package.json index 443553428..d550bfa91 100644 --- a/src/client/package.json +++ b/src/client/package.json @@ -10,6 +10,8 @@ "check:types": "npm --workspaces run check:types" }, "workspaces": [ - "./packages/*" + "packages/event-to-object", + "packages/client", + "packages/app" ] } diff --git a/src/client/packages/app/vite.config.js b/src/client/packages/app/vite.config.js index df41ee669..aaa3e80b2 100644 --- a/src/client/packages/app/vite.config.js +++ b/src/client/packages/app/vite.config.js @@ -8,5 +8,5 @@ export default defineConfig({ "react-dom": "preact/compat", }, }, - base: "/_reactpy", + base: "/_reactpy/", }); From af3bf9095404181d98cd1141c57070322d102b8c Mon Sep 17 00:00:00 2001 From: rmorshea Date: Tue, 14 Mar 2023 23:47:43 -0700 Subject: [PATCH 11/16] minor fixes --- src/client/package-lock.json | 144 ++++++++++++++-------- src/client/packages/app/package-lock.json | 65 ---------- src/client/packages/app/package.json | 3 +- src/client/packages/client/package.json | 8 +- 4 files changed, 99 insertions(+), 121 deletions(-) delete mode 100644 src/client/packages/app/package-lock.json diff --git a/src/client/package-lock.json b/src/client/package-lock.json index f93a4919d..ba43f5e40 100644 --- a/src/client/package-lock.json +++ b/src/client/package-lock.json @@ -5,10 +5,11 @@ "requires": true, "packages": { "": { - "version": "1.0.0", "license": "MIT", "workspaces": [ - "./packages/*" + "packages/event-to-object", + "packages/client", + "packages/app" ] }, "node_modules/@esbuild/android-arm": { @@ -63,26 +64,6 @@ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", "dev": true }, - "node_modules/@types/react": { - "version": "18.0.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", - "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", - "dev": true, - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.0.11", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", - "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", @@ -1004,19 +985,39 @@ }, "packages/app": { "name": "@reactpy/app", - "version": "0.45.0", "license": "MIT", "dependencies": { "@reactpy/client": "^0.1.0", "preact": "^10.7.0" }, "devDependencies": { - "@types/react-dom": "^18.0.11", + "@types/react": "^17.0", + "@types/react-dom": "^17.0", "prettier": "^3.0.0-alpha.6", "typescript": "^4.9.5", "vite": "^3.1.8" } }, + "packages/app/node_modules/@types/react": { + "version": "17.0.53", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.53.tgz", + "integrity": "sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "packages/app/node_modules/@types/react-dom": { + "version": "17.0.19", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz", + "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==", + "dev": true, + "dependencies": { + "@types/react": "^17" + } + }, "packages/app/node_modules/prettier": { "version": "3.0.0-alpha.6", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", @@ -1045,14 +1046,34 @@ }, "devDependencies": { "@types/json-pointer": "^1.0.31", - "@types/react": "^18.0.28", - "@types/react-dom": "^18.0.11", + "@types/react": "^17.0", + "@types/react-dom": "^17.0", "prettier": "^3.0.0-alpha.6", "typescript": "^4.9.5" }, "peerDependencies": { - "react": ">=16", - "react-dom": ">=16" + "react": ">=16 <18", + "react-dom": ">=16 <18" + } + }, + "packages/client/node_modules/@types/react": { + "version": "17.0.53", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.53.tgz", + "integrity": "sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "packages/client/node_modules/@types/react-dom": { + "version": "17.0.19", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz", + "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==", + "dev": true, + "dependencies": { + "@types/react": "^17" } }, "packages/client/node_modules/prettier": { @@ -1165,13 +1186,34 @@ "version": "file:packages/app", "requires": { "@reactpy/client": "^0.1.0", - "@types/react-dom": "^18.0.11", + "@types/react": "^17.0", + "@types/react-dom": "^17.0", "preact": "^10.7.0", "prettier": "^3.0.0-alpha.6", "typescript": "^4.9.5", "vite": "^3.1.8" }, "dependencies": { + "@types/react": { + "version": "17.0.53", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.53.tgz", + "integrity": "sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "17.0.19", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz", + "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==", + "dev": true, + "requires": { + "@types/react": "^17" + } + }, "prettier": { "version": "3.0.0-alpha.6", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", @@ -1184,14 +1226,34 @@ "version": "file:packages/client", "requires": { "@types/json-pointer": "^1.0.31", - "@types/react": "^18.0.28", - "@types/react-dom": "^18.0.11", + "@types/react": "^17.0", + "@types/react-dom": "^17.0", "event-to-object": "^0.1.0", "json-pointer": "^0.6.2", "prettier": "^3.0.0-alpha.6", "typescript": "^4.9.5" }, "dependencies": { + "@types/react": { + "version": "17.0.53", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.53.tgz", + "integrity": "sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "17.0.19", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz", + "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==", + "dev": true, + "requires": { + "@types/react": "^17" + } + }, "prettier": { "version": "3.0.0-alpha.6", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", @@ -1212,26 +1274,6 @@ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", "dev": true }, - "@types/react": { - "version": "18.0.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", - "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", - "dev": true, - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "@types/react-dom": { - "version": "18.0.11", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", - "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, "@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", diff --git a/src/client/packages/app/package-lock.json b/src/client/packages/app/package-lock.json deleted file mode 100644 index 9d1075eb8..000000000 --- a/src/client/packages/app/package-lock.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "@reactpy/app", - "version": "0.38.0-a1", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "@reactpy/app", - "version": "0.38.0-a1", - "license": "MIT", - "dependencies": { - "@reactpy/client": "file:packages/@reactpy/client", - "preact": "^10.7.0" - }, - "devDependencies": { - "prettier": "^2.5.1" - } - }, - "node_modules/@reactpy/client": { - "resolved": "packages/@reactpy/client", - "link": true - }, - "node_modules/preact": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.7.0.tgz", - "integrity": "sha512-9MEURwzNMKpAil/t6+wabDIJI6oG6GnwypYxiJDvQnW+fHDTt51PYuLZ1QUM31hFr7sDaj9qTaShAF9VIxuxGQ==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/prettier": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.1.tgz", - "integrity": "sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "packages/@reactpy/client": {} - }, - "dependencies": { - "@reactpy/client": { - "version": "file:packages/@reactpy/client" - }, - "preact": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.7.0.tgz", - "integrity": "sha512-9MEURwzNMKpAil/t6+wabDIJI6oG6GnwypYxiJDvQnW+fHDTt51PYuLZ1QUM31hFr7sDaj9qTaShAF9VIxuxGQ==" - }, - "prettier": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.1.tgz", - "integrity": "sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==", - "dev": true - } - } -} diff --git a/src/client/packages/app/package.json b/src/client/packages/app/package.json index 7ceb8268e..82b6bfb7b 100644 --- a/src/client/packages/app/package.json +++ b/src/client/packages/app/package.json @@ -12,7 +12,8 @@ "devDependencies": { "prettier": "^3.0.0-alpha.6", "typescript": "^4.9.5", - "@types/react-dom": "^18.0.11", + "@types/react": "^17.0", + "@types/react-dom": "^17.0", "vite": "^3.1.8" }, "repository": { diff --git a/src/client/packages/client/package.json b/src/client/packages/client/package.json index 95bd9759a..dd39371b8 100644 --- a/src/client/packages/client/package.json +++ b/src/client/packages/client/package.json @@ -13,14 +13,14 @@ }, "devDependencies": { "@types/json-pointer": "^1.0.31", - "@types/react": "^18.0.28", - "@types/react-dom": "^18.0.11", + "@types/react": "^17.0", + "@types/react-dom": "^17.0", "prettier": "^3.0.0-alpha.6", "typescript": "^4.9.5" }, "peerDependencies": { - "react": ">=16", - "react-dom": ">=16" + "react": ">=16 <18", + "react-dom": ">=16 <18" }, "repository": { "type": "git", From 9f304b777ba7db52f24b7ac517bd1d0763303e14 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Wed, 22 Mar 2023 22:57:14 -0700 Subject: [PATCH 12/16] rework docs extension + misc changes --- docs/source/_custom_js/package-lock.json | 28 +- docs/source/_custom_js/src/index.js | 26 +- noxfile.py | 66 +- src/client/.gitignore | 1 + src/client/package-lock.json | 684 +++++++----------- src/client/packages/app/index.html | 8 +- src/client/packages/app/package.json | 10 +- src/client/packages/app/src/index.ts | 14 + src/client/packages/app/src/index.tsx | 14 - src/client/packages/app/tsconfig.json | 2 +- src/client/packages/client/package.json | 6 +- src/client/packages/client/src/components.tsx | 2 - src/client/packages/client/src/index.ts | 1 + src/client/packages/client/src/mount.tsx | 8 + .../packages/client/src/reactpy-client.ts | 57 +- .../packages/client/src/reactpy-vdom.tsx | 4 +- src/client/packages/client/tsconfig.json | 2 +- .../packages/event-to-object/package.json | 4 +- .../packages/event-to-object/tsconfig.json | 2 +- .../{tsconfig.json => tsconfig.package.json} | 1 + 20 files changed, 396 insertions(+), 544 deletions(-) create mode 100644 src/client/packages/app/src/index.ts delete mode 100644 src/client/packages/app/src/index.tsx create mode 100644 src/client/packages/client/src/mount.tsx rename src/client/{tsconfig.json => tsconfig.package.json} (94%) diff --git a/docs/source/_custom_js/package-lock.json b/docs/source/_custom_js/package-lock.json index 5e8cdba10..e0489b04e 100644 --- a/docs/source/_custom_js/package-lock.json +++ b/docs/source/_custom_js/package-lock.json @@ -40,21 +40,22 @@ }, "../../../src/client/packages/client": { "name": "@reactpy/client", - "version": "1.0.0-a6", + "version": "0.2.0", "license": "MIT", "dependencies": { - "htm": "^3.0.3", + "event-to-object": "^0.1.0", "json-pointer": "^0.6.2" }, "devDependencies": { - "jsdom": "16.5.0", - "lodash": "^4.17.21", - "prettier": "^2.5.1", - "uvu": "^0.5.1" + "@types/json-pointer": "^1.0.31", + "@types/react": "^17.0", + "@types/react-dom": "^17.0", + "prettier": "^3.0.0-alpha.6", + "typescript": "^4.9.5" }, "peerDependencies": { - "react": ">=16", - "react-dom": ">=16" + "react": ">=16 <18", + "react-dom": ">=16 <18" } }, "node_modules/@reactpy/client": { @@ -435,12 +436,13 @@ "@reactpy/client": { "version": "file:../../../src/client/packages/client", "requires": { - "htm": "^3.0.3", - "jsdom": "16.5.0", + "@types/json-pointer": "^1.0.31", + "@types/react": "^17.0", + "@types/react-dom": "^17.0", + "event-to-object": "^0.1.0", "json-pointer": "^0.6.2", - "lodash": "^4.17.21", - "prettier": "^2.5.1", - "uvu": "^0.5.1" + "prettier": "^3.0.0-alpha.6", + "typescript": "^4.9.5" } }, "@rollup/plugin-commonjs": { diff --git a/docs/source/_custom_js/src/index.js b/docs/source/_custom_js/src/index.js index 41a21334d..da04d98c7 100644 --- a/docs/source/_custom_js/src/index.js +++ b/docs/source/_custom_js/src/index.js @@ -1,4 +1,4 @@ -import { mountWithLayoutServer, LayoutServerInfo } from "@reactpy/client"; +import { SimpleReactPyClient, mount } from "@reactpy/client"; let didMountDebug = false; @@ -6,7 +6,7 @@ export function mountWidgetExample( mountID, viewID, reactpyServerHost, - useActivateButton + useActivateButton, ) { let reactpyHost, reactpyPort; if (reactpyServerHost) { @@ -16,27 +16,27 @@ export function mountWidgetExample( reactpyPort = window.location.port; } - const serverInfo = new LayoutServerInfo({ - host: reactpyHost, - port: reactpyPort, - path: "/_reactpy/", - query: `view_id=${viewID}`, - secure: window.location.protocol == "https:", + const client = new SimpleReactPyClient({ + serverLocation: { + url: `${window.location.protocol}//${reactpyHost}:${reactpyPort}`, + route: "/", + query: `?view_id=${viewID}`, + }, }); const mountEl = document.getElementById(mountID); let isMounted = false; triggerIfInViewport(mountEl, () => { if (!isMounted) { - activateView(mountEl, serverInfo, useActivateButton); + activateView(mountEl, client, useActivateButton); isMounted = true; } }); } -function activateView(mountEl, serverInfo, useActivateButton) { +function activateView(mountEl, client, useActivateButton) { if (!useActivateButton) { - mountWithLayoutServer(mountEl, serverInfo); + mount(mountEl, client); return; } @@ -51,7 +51,7 @@ function activateView(mountEl, serverInfo, useActivateButton) { mountEl.setAttribute("class", "interactive widget-container"); mountWithLayoutServer(mountEl, serverInfo); } - }) + }), ); function fadeOutElementThenCallback(element, callback) { @@ -87,7 +87,7 @@ function triggerIfInViewport(element, callback) { { root: null, threshold: 0.1, // set offset 0.1 means trigger if atleast 10% of element in viewport - } + }, ); observer.observe(element); diff --git a/noxfile.py b/noxfile.py index 394ff6643..d48d4044b 100644 --- a/noxfile.py +++ b/noxfile.py @@ -35,7 +35,6 @@ def __call__(self, session: Session) -> Callable[[bool], None]: SRC_DIR = ROOT_DIR / "src" CLIENT_DIR = SRC_DIR / "client" REACTPY_DIR = SRC_DIR / "reactpy" -LANGUAGE_TYPES: list[LanguageName] = ["py", "js"] TAG_PATTERN = re.compile( # start r"^" @@ -46,7 +45,6 @@ def __call__(self, session: Session) -> Callable[[bool], None]: # end r"$" ) -print(TAG_PATTERN.pattern) REMAINING_ARGS = Option(nargs=REMAINDER, type=str) @@ -89,6 +87,12 @@ def format(session: Session) -> None: session.run("npm", "run", "format", external=True) +@group.session +def tsc(session: Session) -> None: + session.chdir(CLIENT_DIR) + session.run("npx", "tsc", "-b", "-w", "packages/app", external=True) + + @group.session def example(session: Session) -> None: """Run an example""" @@ -269,7 +273,17 @@ def build_python(session: Session) -> None: @group.session -def publish(session: Session, dry_run: bool = False) -> None: +def publish( + session: Session, + publish_dry_run: Annotated[ + bool, + Option(help="whether to test the release process"), + ] = False, + publish_fake_tags: Annotated[ + Sequence[str], + Option(nargs="*", type=str, help="fake tags to use for a dry run release"), + ] = (), +) -> None: packages = get_packages(session) release_prep: dict[LanguageName, ReleasePrepFunc] = { @@ -277,8 +291,20 @@ def publish(session: Session, dry_run: bool = False) -> None: "py": prepare_python_release, } + if publish_fake_tags and not publish_dry_run: + session.error("Cannot specify --publish-fake-tags without --publish-dry-run") + + parsed_tags: list[TagInfo] = [] + for tag in publish_fake_tags or get_current_tags(session): + tag_info = parse_tag(tag) + if tag_info is None: + session.error( + f"Invalid tag {tag} - must be of the form --" + ) + parsed_tags.append(tag_info) + publishers: list[tuple[Path, Callable[[bool], None]]] = [] - for tag, tag_pkg, tag_ver in get_current_tags(session): + for tag, tag_pkg, tag_ver in parsed_tags: if tag_pkg not in packages: session.error(f"Tag {tag} references package {tag_pkg} that does not exist") @@ -296,7 +322,7 @@ def publish(session: Session, dry_run: bool = False) -> None: for pkg_path, publish in publishers: session.log(f"Publishing {pkg_path}...") session.chdir(pkg_path) - publish(dry_run) + publish(publish_dry_run) # --- Utilities ------------------------------------------------------------------------ @@ -420,7 +446,7 @@ class PackageInfo(NamedTuple): version: str -def get_current_tags(session: Session) -> list[TagInfo]: +def get_current_tags(session: Session) -> list[str]: """Get tags for the current commit""" # check if unstaged changes try: @@ -468,24 +494,20 @@ def get_current_tags(session: Session) -> list[TagInfo]: if not tags: session.error("No tags found for current commit") - parsed_tags: list[TagInfo] = [] - for tag in tags: - match = TAG_PATTERN.match(tag) - if not match: - session.error( - f"Invalid tag {tag} - must be of the form --" - ) - parsed_tags.append( - TagInfo( - tag, - match["name"], # type: ignore[index] - match["version"], # type: ignore[index] - ) - ) + session.log(f"Found tags: {tags}") - session.log(f"Found tags: {[info.tag for info in parsed_tags]}") + return tags - return parsed_tags + +def parse_tag(tag: str) -> TagInfo | None: + match = TAG_PATTERN.match(tag) + if not match: + return None + return TagInfo( + tag, + match["name"], # type: ignore[index] + match["version"], # type: ignore[index] + ) class TagInfo(NamedTuple): diff --git a/src/client/.gitignore b/src/client/.gitignore index cc1b7f164..52128d3e1 100644 --- a/src/client/.gitignore +++ b/src/client/.gitignore @@ -1 +1,2 @@ tsconfig.tsbuildinfo +packages/**/package-lock.json diff --git a/src/client/package-lock.json b/src/client/package-lock.json index ba43f5e40..006f1fc57 100644 --- a/src/client/package-lock.json +++ b/src/client/package-lock.json @@ -1,6 +1,5 @@ { "name": "client", - "version": "1.0.0", "lockfileVersion": 2, "requires": true, "packages": { @@ -64,6 +63,26 @@ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", "dev": true }, + "node_modules/@types/react": { + "version": "17.0.53", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.53.tgz", + "integrity": "sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "17.0.19", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz", + "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==", + "dev": true, + "dependencies": { + "@types/react": "^17" + } + }, "node_modules/@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", @@ -501,48 +520,6 @@ "whatwg-mimetype": "^3.0.0" } }, - "node_modules/happy-dom/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/happy-dom/node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/happy-dom/node_modules/whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "dev": true, - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/happy-dom/node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -564,6 +541,18 @@ "he": "bin/he" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-core-module": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", @@ -578,7 +567,8 @@ }, "node_modules/js-tokens": { "version": "4.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "peer": true }, "node_modules/json-pointer": { @@ -606,7 +596,8 @@ }, "node_modules/loose-envify": { "version": "1.4.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -656,31 +647,10 @@ } } }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/object-assign": { "version": "4.1.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "peer": true, "engines": { "node": ">=0.10.0" @@ -699,9 +669,9 @@ "dev": true }, "node_modules/postcss": { - "version": "8.4.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", - "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", "dev": true, "funding": [ { @@ -723,56 +693,56 @@ } }, "node_modules/preact": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.7.0.tgz", - "integrity": "sha512-9MEURwzNMKpAil/t6+wabDIJI6oG6GnwypYxiJDvQnW+fHDTt51PYuLZ1QUM31hFr7sDaj9qTaShAF9VIxuxGQ==", + "version": "10.13.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.1.tgz", + "integrity": "sha512-KyoXVDU5OqTpG9LXlB3+y639JAGzl8JSBXLn1J9HTSB3gbKcuInga7bZnXLlxmK94ntTs1EFeZp0lrja2AuBYQ==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" } }, - "node_modules/prop-types": { - "version": "15.7.2", - "license": "MIT", - "peer": true, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "node_modules/prettier": { + "version": "3.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", + "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, "node_modules/react": { - "version": "16.14.0", - "license": "MIT", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", "peer": true, "dependencies": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" + "object-assign": "^4.1.1" }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "16.14.0", - "license": "MIT", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" + "scheduler": "^0.20.2" }, "peerDependencies": { - "react": "^16.14.0" + "react": "17.0.2" } }, - "node_modules/react-is": { - "version": "16.13.1", - "license": "MIT", - "peer": true - }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -790,6 +760,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/rollup": { + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", @@ -809,8 +794,9 @@ "dev": true }, "node_modules/scheduler": { - "version": "0.19.1", - "license": "MIT", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", "peer": true, "dependencies": { "loose-envify": "^1.1.0", @@ -838,6 +824,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, "node_modules/tsm": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tsm/-/tsm-2.3.0.tgz", @@ -885,15 +877,15 @@ } }, "node_modules/vite": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.8.tgz", - "integrity": "sha512-m7jJe3nufUbuOfotkntGFupinL/fmuTNuQmiVE7cH2IZMuf4UbfbGYMUT3jVWgGYuRVLY9j8NnrRqgw5rr5QTg==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz", + "integrity": "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==", "dev": true, "dependencies": { "esbuild": "^0.15.9", - "postcss": "^8.4.16", + "postcss": "^8.4.18", "resolve": "^1.22.1", - "rollup": "~2.78.0" + "rollup": "^2.79.1" }, "bin": { "vite": "bin/vite.js" @@ -905,12 +897,17 @@ "fsevents": "~2.3.2" }, "peerDependencies": { + "@types/node": ">= 14", "less": "*", "sass": "*", "stylus": "*", + "sugarss": "*", "terser": "^5.4.0" }, "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, "less": { "optional": true }, @@ -920,69 +917,60 @@ "stylus": { "optional": true }, + "sugarss": { + "optional": true + }, "terser": { "optional": true } } }, - "node_modules/vite/node_modules/rollup": { - "version": "2.78.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", - "integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==", + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "node": ">=12" } }, - "packages/@reactpy/app": { - "version": "1.0.0", - "extraneous": true, - "license": "MIT", + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, "dependencies": { - "@reactpy/client": "file:packages/@reactpy/client", - "preact": "^10.7.0" + "iconv-lite": "0.6.3" }, - "devDependencies": { - "prettier": "^2.5.1" + "engines": { + "node": ">=12" } }, - "packages/@reactpy/app/packages/@reactpy/client": { - "version": "0.0.1", - "extraneous": true, - "dependencies": { - "fast-json-patch": "^3.0.0-1", - "htm": "^3.0.3", - "jsdom": "16.5.0", - "lodash": "^4.17.21", - "prettier": "^2.5.1", - "uvu": "^0.5.1" + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" } }, - "packages/@reactpy/client": { - "version": "1.0.0", - "extraneous": true, - "license": "MIT", + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, "dependencies": { - "htm": "^3.0.3", - "json-pointer": "^0.6.2" - }, - "devDependencies": { - "jsdom": "16.5.0", - "lodash": "^4.17.21", - "prettier": "^2.5.1", - "uvu": "^0.5.1" - }, - "peerDependencies": { - "react": ">=16", - "react-dom": ">=16" + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, + "node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, "packages/app": { "name": "@reactpy/app", "license": "MIT", @@ -998,47 +986,9 @@ "vite": "^3.1.8" } }, - "packages/app/node_modules/@types/react": { - "version": "17.0.53", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.53.tgz", - "integrity": "sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw==", - "dev": true, - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "packages/app/node_modules/@types/react-dom": { - "version": "17.0.19", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz", - "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==", - "dev": true, - "dependencies": { - "@types/react": "^17" - } - }, - "packages/app/node_modules/prettier": { - "version": "3.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", - "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "packages/app/packages/client": { - "extraneous": true - }, "packages/client": { "name": "@reactpy/client", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "dependencies": { "event-to-object": "^0.1.0", @@ -1056,44 +1006,6 @@ "react-dom": ">=16 <18" } }, - "packages/client/node_modules/@types/react": { - "version": "17.0.53", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.53.tgz", - "integrity": "sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw==", - "dev": true, - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "packages/client/node_modules/@types/react-dom": { - "version": "17.0.19", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz", - "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==", - "dev": true, - "dependencies": { - "@types/react": "^17" - } - }, - "packages/client/node_modules/prettier": { - "version": "3.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", - "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "packages/client/packages/event-to-object": { - "extraneous": true - }, "packages/event-to-object": { "version": "0.1.0", "license": "MIT", @@ -1114,58 +1026,7 @@ "resolved": "packages/event-to-object/packages/event-to-object", "link": true }, - "packages/event-to-object/node_modules/prettier": { - "version": "3.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", - "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "packages/event-to-object/packages/event-to-object": {}, - "packages/idom-app-react": { - "name": "@reactpy/app", - "version": "1.0.0-a5", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@reactpy/client": "file:packages/@reactpy/client", - "preact": "^10.7.0" - }, - "devDependencies": { - "prettier": "^2.5.1" - } - }, - "packages/idom-app-react/packages/@reactpy/client": { - "extraneous": true - }, - "packages/idom-client-react": { - "name": "@reactpy/client", - "version": "1.0.0-a5", - "extraneous": true, - "license": "MIT", - "dependencies": { - "htm": "^3.0.3", - "json-pointer": "^0.6.2" - }, - "devDependencies": { - "jsdom": "16.5.0", - "lodash": "^4.17.21", - "prettier": "^2.5.1", - "uvu": "^0.5.1" - }, - "peerDependencies": { - "react": ">=16", - "react-dom": ">=16" - } - } + "packages/event-to-object/packages/event-to-object": {} }, "dependencies": { "@esbuild/android-arm": { @@ -1192,34 +1053,6 @@ "prettier": "^3.0.0-alpha.6", "typescript": "^4.9.5", "vite": "^3.1.8" - }, - "dependencies": { - "@types/react": { - "version": "17.0.53", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.53.tgz", - "integrity": "sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw==", - "dev": true, - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "@types/react-dom": { - "version": "17.0.19", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz", - "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==", - "dev": true, - "requires": { - "@types/react": "^17" - } - }, - "prettier": { - "version": "3.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", - "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==", - "dev": true - } } }, "@reactpy/client": { @@ -1232,34 +1065,6 @@ "json-pointer": "^0.6.2", "prettier": "^3.0.0-alpha.6", "typescript": "^4.9.5" - }, - "dependencies": { - "@types/react": { - "version": "17.0.53", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.53.tgz", - "integrity": "sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw==", - "dev": true, - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "@types/react-dom": { - "version": "17.0.19", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz", - "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==", - "dev": true, - "requires": { - "@types/react": "^17" - } - }, - "prettier": { - "version": "3.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", - "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==", - "dev": true - } } }, "@types/json-pointer": { @@ -1274,6 +1079,26 @@ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", "dev": true }, + "@types/react": { + "version": "17.0.53", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.53.tgz", + "integrity": "sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "17.0.19", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz", + "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==", + "dev": true, + "requires": { + "@types/react": "^17" + } + }, "@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", @@ -1489,12 +1314,6 @@ "dependencies": { "event-to-object": { "version": "file:packages/event-to-object/packages/event-to-object" - }, - "prettier": { - "version": "3.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", - "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==", - "dev": true } } }, @@ -1529,38 +1348,6 @@ "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0" - }, - "dependencies": { - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - }, - "webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true - }, - "whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "dev": true, - "requires": { - "iconv-lite": "0.6.3" - } - }, - "whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "dev": true - } } }, "has": { @@ -1578,6 +1365,15 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, "is-core-module": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", @@ -1589,6 +1385,8 @@ }, "js-tokens": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "peer": true }, "json-pointer": { @@ -1613,6 +1411,8 @@ }, "loose-envify": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "peer": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -1637,34 +1437,12 @@ "dev": true, "requires": { "whatwg-url": "^5.0.0" - }, - "dependencies": { - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } } }, "object-assign": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "peer": true }, "path-parse": { @@ -1680,9 +1458,9 @@ "dev": true }, "postcss": { - "version": "8.4.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", - "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", "dev": true, "requires": { "nanoid": "^3.3.4", @@ -1691,42 +1469,37 @@ } }, "preact": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.7.0.tgz", - "integrity": "sha512-9MEURwzNMKpAil/t6+wabDIJI6oG6GnwypYxiJDvQnW+fHDTt51PYuLZ1QUM31hFr7sDaj9qTaShAF9VIxuxGQ==" + "version": "10.13.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.1.tgz", + "integrity": "sha512-KyoXVDU5OqTpG9LXlB3+y639JAGzl8JSBXLn1J9HTSB3gbKcuInga7bZnXLlxmK94ntTs1EFeZp0lrja2AuBYQ==" }, - "prop-types": { - "version": "15.7.2", - "peer": true, - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } + "prettier": { + "version": "3.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz", + "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==", + "dev": true }, "react": { - "version": "16.14.0", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", "peer": true, "requires": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" + "object-assign": "^4.1.1" } }, "react-dom": { - "version": "16.14.0", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", "peer": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" + "scheduler": "^0.20.2" } }, - "react-is": { - "version": "16.13.1", - "peer": true - }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -1738,6 +1511,15 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, + "rollup": { + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, "sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", @@ -1754,7 +1536,9 @@ "dev": true }, "scheduler": { - "version": "0.19.1", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", "peer": true, "requires": { "loose-envify": "^1.1.0", @@ -1773,6 +1557,12 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, "tsm": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tsm/-/tsm-2.3.0.tgz", @@ -1801,26 +1591,54 @@ } }, "vite": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.8.tgz", - "integrity": "sha512-m7jJe3nufUbuOfotkntGFupinL/fmuTNuQmiVE7cH2IZMuf4UbfbGYMUT3jVWgGYuRVLY9j8NnrRqgw5rr5QTg==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz", + "integrity": "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==", "dev": true, "requires": { "esbuild": "^0.15.9", "fsevents": "~2.3.2", - "postcss": "^8.4.16", + "postcss": "^8.4.18", "resolve": "^1.22.1", - "rollup": "~2.78.0" + "rollup": "^2.79.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + } + }, + "whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" }, "dependencies": { - "rollup": { - "version": "2.78.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", - "integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==", - "dev": true, - "requires": { - "fsevents": "~2.3.2" - } + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true } } } diff --git a/src/client/packages/app/index.html b/src/client/packages/app/index.html index d734a1814..e94280368 100644 --- a/src/client/packages/app/index.html +++ b/src/client/packages/app/index.html @@ -2,14 +2,14 @@ + {__head__}
- diff --git a/src/client/packages/app/package.json b/src/client/packages/app/package.json index 82b6bfb7b..33d0e740b 100644 --- a/src/client/packages/app/package.json +++ b/src/client/packages/app/package.json @@ -6,14 +6,14 @@ "name": "@reactpy/app", "description": "A client application for ReactPy implemented in React", "dependencies": { - "@reactpy/client": "^0.1.0", + "@reactpy/client": "^0.2.0", "preact": "^10.7.0" }, "devDependencies": { - "prettier": "^3.0.0-alpha.6", - "typescript": "^4.9.5", "@types/react": "^17.0", "@types/react-dom": "^17.0", + "prettier": "^3.0.0-alpha.6", + "typescript": "^4.9.5", "vite": "^3.1.8" }, "repository": { @@ -22,9 +22,9 @@ }, "scripts": { "build": "vite build", - "format": "prettier --write ./src", + "format": "prettier --write .", "test": "npm run check:tests", - "check:format": "prettier --check ./src", + "check:format": "prettier --check .", "check:tests": "echo 'no tests'", "check:types": "tsc --noEmit" } diff --git a/src/client/packages/app/src/index.ts b/src/client/packages/app/src/index.ts new file mode 100644 index 000000000..56a0f4c41 --- /dev/null +++ b/src/client/packages/app/src/index.ts @@ -0,0 +1,14 @@ +import { mount, SimpleReactPyClient } from "@reactpy/client"; + +export function app(element: HTMLElement) { + mount( + element, + new SimpleReactPyClient({ + serverLocation: { + url: document.location.host, + route: document.location.pathname, + query: document.location.search, + }, + }), + ); +} diff --git a/src/client/packages/app/src/index.tsx b/src/client/packages/app/src/index.tsx deleted file mode 100644 index ca6d93711..000000000 --- a/src/client/packages/app/src/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from "react"; -import { render } from "react-dom"; -import { Layout, SimpleReactPyClient } from "@reactpy/client"; - -export function mount(root: HTMLElement) { - const client = new SimpleReactPyClient({ - serverLocation: { - baseUrl: document.location.origin, - routePath: document.location.pathname, - queryString: document.location.search, - }, - }); - render(, root); -} diff --git a/src/client/packages/app/tsconfig.json b/src/client/packages/app/tsconfig.json index 8f31503be..23b2ff31d 100644 --- a/src/client/packages/app/tsconfig.json +++ b/src/client/packages/app/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.package.json", "compilerOptions": { "outDir": "dist", "rootDir": "src", diff --git a/src/client/packages/client/package.json b/src/client/packages/client/package.json index dd39371b8..c878a3069 100644 --- a/src/client/packages/client/package.json +++ b/src/client/packages/client/package.json @@ -6,7 +6,7 @@ "license": "MIT", "name": "@reactpy/client", "type": "module", - "version": "0.1.0", + "version": "0.2.0", "dependencies": { "event-to-object": "^0.1.0", "json-pointer": "^0.6.2" @@ -28,9 +28,9 @@ }, "scripts": { "build": "tsc -b", - "format": "prettier --write ./src", + "format": "prettier --write .", "test": "npm run check:tests", - "check:format": "prettier --check ./src", + "check:format": "prettier --check .", "check:tests": "echo 'no tests'", "check:types": "tsc --noEmit" } diff --git a/src/client/packages/client/src/components.tsx b/src/client/packages/client/src/components.tsx index b6c5f6122..02edae067 100644 --- a/src/client/packages/client/src/components.tsx +++ b/src/client/packages/client/src/components.tsx @@ -2,12 +2,10 @@ import React, { createElement, createContext, useState, - useCallback, useRef, useContext, useEffect, Fragment, - ComponentType, MutableRefObject, ChangeEvent, } from "react"; diff --git a/src/client/packages/client/src/index.ts b/src/client/packages/client/src/index.ts index a46dce8b7..548fcbfc7 100644 --- a/src/client/packages/client/src/index.ts +++ b/src/client/packages/client/src/index.ts @@ -1,4 +1,5 @@ export * from "./components"; export * from "./messages"; +export * from "./mount"; export * from "./reactpy-client"; export * from "./reactpy-vdom"; diff --git a/src/client/packages/client/src/mount.tsx b/src/client/packages/client/src/mount.tsx new file mode 100644 index 000000000..0b824a4ee --- /dev/null +++ b/src/client/packages/client/src/mount.tsx @@ -0,0 +1,8 @@ +import React from "react"; +import { render } from "react-dom"; +import { Layout } from "./components"; +import { ReactPyClient } from "./reactpy-client"; + +export function mount(element: HTMLElement, client: ReactPyClient): void { + render(, element); +} diff --git a/src/client/packages/client/src/reactpy-client.ts b/src/client/packages/client/src/reactpy-client.ts index 6f2d70b36..e41ff10ad 100644 --- a/src/client/packages/client/src/reactpy-client.ts +++ b/src/client/packages/client/src/reactpy-client.ts @@ -13,10 +13,15 @@ export interface ReactPyClient { loadModule: (moduleName: string) => Promise; } +export type SimpleReactPyClientProprs = { + serverLocation: UrlProps; + reconnectOptions?: ReconnectProps; +}; + type UrlProps = { - baseUrl: string; - routePath: string; - queryString: string; + url: string; + route: string; + query: string; }; type ReconnectProps = { @@ -27,18 +32,15 @@ type ReconnectProps = { }; export class SimpleReactPyClient implements ReactPyClient { - private resolveShouldOpen?: (value: unknown) => void; - private resolveShouldClose?: (value: unknown) => void; + private resolveShouldOpen: (value: unknown) => void; + private resolveShouldClose: (value: unknown) => void; private readonly urls: ServerUrls; private readonly handlers: { [key in IncomingMessage["type"]]: ((message: any) => void)[]; }; private readonly socket: { current?: WebSocket }; - constructor(props: { - serverLocation: UrlProps; - reconnectOptions?: ReconnectProps; - }) { + constructor(props: SimpleReactPyClientProprs) { this.handlers = { "connection-open": [], "connection-close": [], @@ -47,6 +49,12 @@ export class SimpleReactPyClient implements ReactPyClient { this.urls = getServerUrls(props.serverLocation); + this.resolveShouldOpen = () => { + throw new Error("Could not start client"); + }; + this.resolveShouldClose = () => { + throw new Error("Could not stop client"); + }; const shouldOpen = new Promise((r) => (this.resolveShouldOpen = r)); const shouldClose = new Promise((r) => (this.resolveShouldClose = r)); @@ -62,21 +70,13 @@ export class SimpleReactPyClient implements ReactPyClient { } start(): void { - if (this.resolveShouldOpen) { - logger.log("Starting ReactPy client..."); - this.resolveShouldOpen(undefined); - } else { - throw "Did not start client"; - } + logger.log("Starting client..."); + this.resolveShouldOpen(undefined); } stop(): void { - if (this.resolveShouldClose) { - logger.log("Stopping ReactPy client..."); - this.resolveShouldClose(undefined); - } else { - throw "Did not stop client"; - } + logger.log("stopping client..."); + this.resolveShouldClose(undefined); } onMessage( @@ -119,16 +119,13 @@ type ServerUrls = { }; function getServerUrls(props: UrlProps): ServerUrls { - const base = new URL(`${props.baseUrl || document.location.origin}/_reactpy`); + const base = new URL(`${props.url || document.location.origin}/_reactpy`); const modules = `${base}/modules`; const assets = `${base}/assets`; const streamProtocol = `ws${base.protocol === "https:" ? "s" : ""}`; - const streamPath = rtrim( - `${base.pathname}/stream${props.routePath || ""}`, - "/", - ); - const stream = `${streamProtocol}://${base.host}${streamPath}${props.queryString}`; + const streamPath = rtrim(`${base.pathname}/stream${props.route || ""}`, "/"); + const stream = `${streamProtocol}://${base.host}${streamPath}${props.query}`; return { base, modules, assets, stream }; } @@ -176,7 +173,7 @@ function startReconnectingWebSocket( return; } - logger.log("disconnected"); + logger.log("client disconnected"); props.onClose(); if (retries >= maxRetries) { @@ -184,7 +181,9 @@ function startReconnectingWebSocket( } const thisInterval = addJitter(interval, intervalJitter); - logger.log(`reconnecting in ${thisInterval / 1000} seconds...`); + logger.log( + `reconnecting in ${(thisInterval / 1000).toPrecision(4)} seconds...`, + ); setTimeout(connect, thisInterval); interval = nextInterval(interval, backoffRate, maxInterval); retries++; diff --git a/src/client/packages/client/src/reactpy-vdom.tsx b/src/client/packages/client/src/reactpy-vdom.tsx index 97ba79479..0d4f9c213 100644 --- a/src/client/packages/client/src/reactpy-vdom.tsx +++ b/src/client/packages/client/src/reactpy-vdom.tsx @@ -13,7 +13,9 @@ export async function loadImportSource( module = await client.loadModule(vdomImportSource.source); } if (typeof module.bind !== "function") { - throw `${vdomImportSource.source} did not export a function 'bind'`; + throw new Error( + `${vdomImportSource.source} did not export a function 'bind'`, + ); } return (node: HTMLElement) => { diff --git a/src/client/packages/client/tsconfig.json b/src/client/packages/client/tsconfig.json index 23a34c78b..27f751157 100644 --- a/src/client/packages/client/tsconfig.json +++ b/src/client/packages/client/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.package.json", "compilerOptions": { "outDir": "dist", "rootDir": "src", diff --git a/src/client/packages/event-to-object/package.json b/src/client/packages/event-to-object/package.json index 6c0d6b02d..caa871861 100644 --- a/src/client/packages/event-to-object/package.json +++ b/src/client/packages/event-to-object/package.json @@ -25,9 +25,9 @@ }, "scripts": { "build": "tsc -b", - "format": "prettier --write ./src ./tests", + "format": "prettier --write .", "test": "npm run check:tests", - "check:format": "prettier --check ./src ./tests", + "check:format": "prettier --check .", "check:tests": "uvu -r tsm tests", "check:types": "tsc --noEmit" } diff --git a/src/client/packages/event-to-object/tsconfig.json b/src/client/packages/event-to-object/tsconfig.json index 9b0e0b6a5..b9a031fa9 100644 --- a/src/client/packages/event-to-object/tsconfig.json +++ b/src/client/packages/event-to-object/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.package.json", "compilerOptions": { "outDir": "dist", "rootDir": "src", diff --git a/src/client/tsconfig.json b/src/client/tsconfig.package.json similarity index 94% rename from src/client/tsconfig.json rename to src/client/tsconfig.package.json index cc694eb6b..79454a834 100644 --- a/src/client/tsconfig.json +++ b/src/client/tsconfig.package.json @@ -3,6 +3,7 @@ "allowJs": false, "allowSyntheticDefaultImports": true, "declaration": true, + "declarationMap": true, "esModuleInterop": false, "forceConsistentCasingInFileNames": true, "isolatedModules": true, From da2d9b47237372ccb1ffdd0ff1952fbd6c7e15cb Mon Sep 17 00:00:00 2001 From: rmorshea Date: Wed, 22 Mar 2023 23:01:35 -0700 Subject: [PATCH 13/16] build before check types --- noxfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/noxfile.py b/noxfile.py index d48d4044b..905a228f8 100644 --- a/noxfile.py +++ b/noxfile.py @@ -248,6 +248,7 @@ def check_javascript_format(session: Session) -> None: @group.session def check_javascript_types(session: Session) -> None: + session.run("npm", "run", "build", external=True) session.run("npm", "run", "check:types", external=True) From 6d5420009a62852dd2a4765366d61fd47fe66760 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Wed, 22 Mar 2023 23:28:31 -0700 Subject: [PATCH 14/16] fix typos --- src/client/packages/app/src/index.ts | 2 +- .../packages/client/src/reactpy-client.ts | 43 ++++++++++++++++--- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/client/packages/app/src/index.ts b/src/client/packages/app/src/index.ts index 56a0f4c41..8a19c53f9 100644 --- a/src/client/packages/app/src/index.ts +++ b/src/client/packages/app/src/index.ts @@ -5,7 +5,7 @@ export function app(element: HTMLElement) { element, new SimpleReactPyClient({ serverLocation: { - url: document.location.host, + url: document.location.origin, route: document.location.pathname, query: document.location.search, }, diff --git a/src/client/packages/client/src/reactpy-client.ts b/src/client/packages/client/src/reactpy-client.ts index e41ff10ad..8ad8e244a 100644 --- a/src/client/packages/client/src/reactpy-client.ts +++ b/src/client/packages/client/src/reactpy-client.ts @@ -13,14 +13,39 @@ export interface ReactPyClient { loadModule: (moduleName: string) => Promise; } -export type SimpleReactPyClientProprs = { - serverLocation: UrlProps; +export type SimpleReactPyClientProps = { + serverLocation?: LocationProps; reconnectOptions?: ReconnectProps; }; -type UrlProps = { +/** + * The location of the server. + * + * This is used to determine the location of the server's API endpoints. All endpoints + * are expected to be found at the base URL, with the following paths: + * + * - `_reactpy/stream/${route}${query}`: The websocket endpoint for the stream. + * - `_reactpy/modules`: The directory containing the dynamically loaded modules. + * - `_reactpy/assets`: The directory containing the static assets. + */ +type LocationProps = { + /** + * The base URL of the server. + * + * @default - document.location.origin + */ url: string; + /** + * The route to the page being rendered. + * + * @default - document.location.pathname + */ route: string; + /** + * The query string of the page being rendered. + * + * @default - document.location.search + */ query: string; }; @@ -40,14 +65,20 @@ export class SimpleReactPyClient implements ReactPyClient { }; private readonly socket: { current?: WebSocket }; - constructor(props: SimpleReactPyClientProprs) { + constructor(props: SimpleReactPyClientProps) { this.handlers = { "connection-open": [], "connection-close": [], "layout-update": [], }; - this.urls = getServerUrls(props.serverLocation); + this.urls = getServerUrls( + props.serverLocation || { + url: document.location.origin, + route: document.location.pathname, + query: document.location.search, + }, + ); this.resolveShouldOpen = () => { throw new Error("Could not start client"); @@ -118,7 +149,7 @@ type ServerUrls = { assets: string; }; -function getServerUrls(props: UrlProps): ServerUrls { +function getServerUrls(props: LocationProps): ServerUrls { const base = new URL(`${props.url || document.location.origin}/_reactpy`); const modules = `${base}/modules`; const assets = `${base}/assets`; From 7f7b2c929f7eb72b8ae83cbbefd590cf9dce7261 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Thu, 23 Mar 2023 00:22:38 -0700 Subject: [PATCH 15/16] move client under namespace dir --- docs/source/_custom_js/package-lock.json | 27 ++++--- docs/source/_custom_js/package.json | 2 +- noxfile.py | 19 +++-- .../{packages/app => apps/ui}/index.html | 0 .../{packages/app => apps/ui}/package.json | 1 - .../ui}/public/assets/reactpy-logo.ico | Bin .../{packages/app => apps/ui}/src/index.ts | 0 .../{packages/app => apps/ui}/tsconfig.json | 2 +- .../{packages/app => apps/ui}/vite.config.js | 0 src/client/package-lock.json | 75 +++++++++++++----- src/client/package.json | 4 +- .../packages/{ => @reactpy}/client/.gitignore | 0 .../packages/{ => @reactpy}/client/README.md | 0 .../{ => @reactpy}/client/package.json | 0 .../{ => @reactpy}/client/src/components.tsx | 0 .../{ => @reactpy}/client/src/index.ts | 0 .../{ => @reactpy}/client/src/logger.ts | 0 .../{ => @reactpy}/client/src/messages.ts | 0 .../{ => @reactpy}/client/src/mount.tsx | 0 .../client/src/reactpy-client.ts | 23 +++++- .../client/src/reactpy-vdom.tsx | 0 .../{ => @reactpy}/client/tsconfig.json | 4 +- 22 files changed, 110 insertions(+), 47 deletions(-) rename src/client/{packages/app => apps/ui}/index.html (100%) rename src/client/{packages/app => apps/ui}/package.json (96%) rename src/client/{packages/app => apps/ui}/public/assets/reactpy-logo.ico (100%) rename src/client/{packages/app => apps/ui}/src/index.ts (100%) rename src/client/{packages/app => apps/ui}/tsconfig.json (80%) rename src/client/{packages/app => apps/ui}/vite.config.js (100%) rename src/client/packages/{ => @reactpy}/client/.gitignore (100%) rename src/client/packages/{ => @reactpy}/client/README.md (100%) rename src/client/packages/{ => @reactpy}/client/package.json (100%) rename src/client/packages/{ => @reactpy}/client/src/components.tsx (100%) rename src/client/packages/{ => @reactpy}/client/src/index.ts (100%) rename src/client/packages/{ => @reactpy}/client/src/logger.ts (100%) rename src/client/packages/{ => @reactpy}/client/src/messages.ts (100%) rename src/client/packages/{ => @reactpy}/client/src/mount.tsx (100%) rename src/client/packages/{ => @reactpy}/client/src/reactpy-client.ts (92%) rename src/client/packages/{ => @reactpy}/client/src/reactpy-vdom.tsx (100%) rename src/client/packages/{ => @reactpy}/client/tsconfig.json (64%) diff --git a/docs/source/_custom_js/package-lock.json b/docs/source/_custom_js/package-lock.json index e0489b04e..5df90e9fb 100644 --- a/docs/source/_custom_js/package-lock.json +++ b/docs/source/_custom_js/package-lock.json @@ -8,7 +8,7 @@ "name": "reactpy-docs-example-loader", "version": "1.0.0", "dependencies": { - "@reactpy/client": "file:../../../src/client/packages/client" + "@reactpy/client": "file:../../../src/client/packages/@reactpy/client" }, "devDependencies": { "@rollup/plugin-commonjs": "^21.0.1", @@ -19,28 +19,29 @@ } }, "../../../src/client/packages/@reactpy/client": { - "version": "0.44.0", + "version": "0.2.0", "integrity": "sha512-pIK5eNwFSHKXg7ClpASWFVKyZDYxz59MSFpVaX/OqJFkrJaAxBuhKGXNTMXmuyWOL5Iyvb/ErwwDRxQRzMNkfQ==", - "extraneous": true, "license": "MIT", "dependencies": { - "fast-json-patch": "^3.0.0-1", - "htm": "^3.0.3" + "event-to-object": "^0.1.0", + "json-pointer": "^0.6.2" }, "devDependencies": { - "jsdom": "16.5.0", - "lodash": "^4.17.21", - "prettier": "^2.5.1", - "uvu": "^0.5.1" + "@types/json-pointer": "^1.0.31", + "@types/react": "^17.0", + "@types/react-dom": "^17.0", + "prettier": "^3.0.0-alpha.6", + "typescript": "^4.9.5" }, "peerDependencies": { - "react": ">=16", - "react-dom": ">=16" + "react": ">=16 <18", + "react-dom": ">=16 <18" } }, "../../../src/client/packages/client": { "name": "@reactpy/client", "version": "0.2.0", + "extraneous": true, "license": "MIT", "dependencies": { "event-to-object": "^0.1.0", @@ -59,7 +60,7 @@ } }, "node_modules/@reactpy/client": { - "resolved": "../../../src/client/packages/client", + "resolved": "../../../src/client/packages/@reactpy/client", "link": true }, "node_modules/@rollup/plugin-commonjs": { @@ -434,7 +435,7 @@ }, "dependencies": { "@reactpy/client": { - "version": "file:../../../src/client/packages/client", + "version": "file:../../../src/client/packages/@reactpy/client", "requires": { "@types/json-pointer": "^1.0.31", "@types/react": "^17.0", diff --git a/docs/source/_custom_js/package.json b/docs/source/_custom_js/package.json index 5d3e41d13..1d19613f5 100644 --- a/docs/source/_custom_js/package.json +++ b/docs/source/_custom_js/package.json @@ -15,6 +15,6 @@ "rollup": "^2.35.1" }, "dependencies": { - "@reactpy/client": "file:../../../src/client/packages/client" + "@reactpy/client": "file:../../../src/client/packages/@reactpy/client" } } diff --git a/noxfile.py b/noxfile.py index 905a228f8..635a9cac6 100644 --- a/noxfile.py +++ b/noxfile.py @@ -416,7 +416,17 @@ def get_packages(session: Session) -> dict[str, PackageInfo]: } # collect javascript packages - for pkg in (CLIENT_DIR / "packages").glob("*"): + packages: list[Path] = [] + for maybed_pkg in (CLIENT_DIR / "packages").glob("*"): + if not (maybed_pkg / "package.json").exists(): + for nmaybe_namespaced_pkg in maybed_pkg.glob("*"): + if (nmaybe_namespaced_pkg / "package.json").exists(): + packages.append(nmaybe_namespaced_pkg) + else: + packages.append(maybed_pkg) + + # get javascript package info + for pkg in packages: pkg_json_file = pkg / "package.json" if not pkg_json_file.exists(): session.error(f"package.json not found in {pkg}") @@ -426,13 +436,10 @@ def get_packages(session: Session) -> dict[str, PackageInfo]: pkg_name = pkg_json.get("name") pkg_version = pkg_json.get("version") - if pkg_version is None: - session.log(f"Skipping - {pkg_name} has no name or version in package.json") + if pkg_version is None or pkg_name is None: + session.log(f"Skipping - {pkg_name} has no name/version in package.json") continue - if pkg_name is None: - session.error(f"Package {pkg} has no name in package.json") - if pkg_name in packages: session.error(f"Duplicate package name {pkg_name}") diff --git a/src/client/packages/app/index.html b/src/client/apps/ui/index.html similarity index 100% rename from src/client/packages/app/index.html rename to src/client/apps/ui/index.html diff --git a/src/client/packages/app/package.json b/src/client/apps/ui/package.json similarity index 96% rename from src/client/packages/app/package.json rename to src/client/apps/ui/package.json index 33d0e740b..28d233ad4 100644 --- a/src/client/packages/app/package.json +++ b/src/client/apps/ui/package.json @@ -3,7 +3,6 @@ "license": "MIT", "main": "src/dist/index.js", "types": "src/dist/index.d.ts", - "name": "@reactpy/app", "description": "A client application for ReactPy implemented in React", "dependencies": { "@reactpy/client": "^0.2.0", diff --git a/src/client/packages/app/public/assets/reactpy-logo.ico b/src/client/apps/ui/public/assets/reactpy-logo.ico similarity index 100% rename from src/client/packages/app/public/assets/reactpy-logo.ico rename to src/client/apps/ui/public/assets/reactpy-logo.ico diff --git a/src/client/packages/app/src/index.ts b/src/client/apps/ui/src/index.ts similarity index 100% rename from src/client/packages/app/src/index.ts rename to src/client/apps/ui/src/index.ts diff --git a/src/client/packages/app/tsconfig.json b/src/client/apps/ui/tsconfig.json similarity index 80% rename from src/client/packages/app/tsconfig.json rename to src/client/apps/ui/tsconfig.json index 23b2ff31d..397ee2d69 100644 --- a/src/client/packages/app/tsconfig.json +++ b/src/client/apps/ui/tsconfig.json @@ -8,7 +8,7 @@ "include": ["src"], "references": [ { - "path": "../client" + "path": "../../packages/@reactpy/client" } ] } diff --git a/src/client/packages/app/vite.config.js b/src/client/apps/ui/vite.config.js similarity index 100% rename from src/client/packages/app/vite.config.js rename to src/client/apps/ui/vite.config.js diff --git a/src/client/package-lock.json b/src/client/package-lock.json index 006f1fc57..d2c013aa9 100644 --- a/src/client/package-lock.json +++ b/src/client/package-lock.json @@ -7,10 +7,24 @@ "license": "MIT", "workspaces": [ "packages/event-to-object", - "packages/client", - "packages/app" + "packages/@reactpy/client", + "apps/ui" ] }, + "apps/ui": { + "license": "MIT", + "dependencies": { + "@reactpy/client": "^0.2.0", + "preact": "^10.7.0" + }, + "devDependencies": { + "@types/react": "^17.0", + "@types/react-dom": "^17.0", + "prettier": "^3.0.0-alpha.6", + "typescript": "^4.9.5", + "vite": "^3.1.8" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.15.18", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", @@ -43,12 +57,8 @@ "node": ">=12" } }, - "node_modules/@reactpy/app": { - "resolved": "packages/app", - "link": true - }, "node_modules/@reactpy/client": { - "resolved": "packages/client", + "resolved": "packages/@reactpy/client", "link": true }, "node_modules/@types/json-pointer": { @@ -858,6 +868,10 @@ "node": ">=4.2.0" } }, + "node_modules/ui": { + "resolved": "apps/ui", + "link": true + }, "node_modules/uvu": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", @@ -971,8 +985,28 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "dev": true }, + "packages/@reactpy/client": { + "version": "0.2.0", + "license": "MIT", + "dependencies": { + "event-to-object": "^0.1.0", + "json-pointer": "^0.6.2" + }, + "devDependencies": { + "@types/json-pointer": "^1.0.31", + "@types/react": "^17.0", + "@types/react-dom": "^17.0", + "prettier": "^3.0.0-alpha.6", + "typescript": "^4.9.5" + }, + "peerDependencies": { + "react": ">=16 <18", + "react-dom": ">=16 <18" + } + }, "packages/app": { "name": "@reactpy/app", + "extraneous": true, "license": "MIT", "dependencies": { "@reactpy/client": "^0.1.0", @@ -989,6 +1023,7 @@ "packages/client": { "name": "@reactpy/client", "version": "0.2.0", + "extraneous": true, "license": "MIT", "dependencies": { "event-to-object": "^0.1.0", @@ -1043,20 +1078,8 @@ "dev": true, "optional": true }, - "@reactpy/app": { - "version": "file:packages/app", - "requires": { - "@reactpy/client": "^0.1.0", - "@types/react": "^17.0", - "@types/react-dom": "^17.0", - "preact": "^10.7.0", - "prettier": "^3.0.0-alpha.6", - "typescript": "^4.9.5", - "vite": "^3.1.8" - } - }, "@reactpy/client": { - "version": "file:packages/client", + "version": "file:packages/@reactpy/client", "requires": { "@types/json-pointer": "^1.0.31", "@types/react": "^17.0", @@ -1578,6 +1601,18 @@ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true }, + "ui": { + "version": "file:apps/ui", + "requires": { + "@reactpy/client": "^0.2.0", + "@types/react": "^17.0", + "@types/react-dom": "^17.0", + "preact": "^10.7.0", + "prettier": "^3.0.0-alpha.6", + "typescript": "^4.9.5", + "vite": "^3.1.8" + } + }, "uvu": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", diff --git a/src/client/package.json b/src/client/package.json index d550bfa91..d6c1a99ba 100644 --- a/src/client/package.json +++ b/src/client/package.json @@ -11,7 +11,7 @@ }, "workspaces": [ "packages/event-to-object", - "packages/client", - "packages/app" + "packages/@reactpy/client", + "apps/ui" ] } diff --git a/src/client/packages/client/.gitignore b/src/client/packages/@reactpy/client/.gitignore similarity index 100% rename from src/client/packages/client/.gitignore rename to src/client/packages/@reactpy/client/.gitignore diff --git a/src/client/packages/client/README.md b/src/client/packages/@reactpy/client/README.md similarity index 100% rename from src/client/packages/client/README.md rename to src/client/packages/@reactpy/client/README.md diff --git a/src/client/packages/client/package.json b/src/client/packages/@reactpy/client/package.json similarity index 100% rename from src/client/packages/client/package.json rename to src/client/packages/@reactpy/client/package.json diff --git a/src/client/packages/client/src/components.tsx b/src/client/packages/@reactpy/client/src/components.tsx similarity index 100% rename from src/client/packages/client/src/components.tsx rename to src/client/packages/@reactpy/client/src/components.tsx diff --git a/src/client/packages/client/src/index.ts b/src/client/packages/@reactpy/client/src/index.ts similarity index 100% rename from src/client/packages/client/src/index.ts rename to src/client/packages/@reactpy/client/src/index.ts diff --git a/src/client/packages/client/src/logger.ts b/src/client/packages/@reactpy/client/src/logger.ts similarity index 100% rename from src/client/packages/client/src/logger.ts rename to src/client/packages/@reactpy/client/src/logger.ts diff --git a/src/client/packages/client/src/messages.ts b/src/client/packages/@reactpy/client/src/messages.ts similarity index 100% rename from src/client/packages/client/src/messages.ts rename to src/client/packages/@reactpy/client/src/messages.ts diff --git a/src/client/packages/client/src/mount.tsx b/src/client/packages/@reactpy/client/src/mount.tsx similarity index 100% rename from src/client/packages/client/src/mount.tsx rename to src/client/packages/@reactpy/client/src/mount.tsx diff --git a/src/client/packages/client/src/reactpy-client.ts b/src/client/packages/@reactpy/client/src/reactpy-client.ts similarity index 92% rename from src/client/packages/client/src/reactpy-client.ts rename to src/client/packages/@reactpy/client/src/reactpy-client.ts index 8ad8e244a..9a8f8a4bf 100644 --- a/src/client/packages/client/src/reactpy-client.ts +++ b/src/client/packages/@reactpy/client/src/reactpy-client.ts @@ -2,14 +2,35 @@ import { OutgoingMessage, IncomingMessage } from "./messages"; import { ReactPyModule } from "./reactpy-vdom"; import logger from "./logger"; +/** + * A client for communicating with a ReactPy server. + */ export interface ReactPyClient { + /** + * Connect to the server and start receiving messages. + * + * Message handlers should be registered before calling this method in order to + * garuntee that messages are not missed. + */ start: () => void; + /** + * Disconnect from the server and stop receiving messages. + */ stop: () => void; + /** + * Register a handler for a message type. + */ onMessage: ( type: M["type"], handler: (message: M) => void, ) => void; + /** + * Send a message to the server. + */ sendMessage: (message: OutgoingMessage) => void; + /** + * Dynamically load a module from the server. + */ loadModule: (moduleName: string) => Promise; } @@ -101,7 +122,7 @@ export class SimpleReactPyClient implements ReactPyClient { } start(): void { - logger.log("Starting client..."); + logger.log("starting client..."); this.resolveShouldOpen(undefined); } diff --git a/src/client/packages/client/src/reactpy-vdom.tsx b/src/client/packages/@reactpy/client/src/reactpy-vdom.tsx similarity index 100% rename from src/client/packages/client/src/reactpy-vdom.tsx rename to src/client/packages/@reactpy/client/src/reactpy-vdom.tsx diff --git a/src/client/packages/client/tsconfig.json b/src/client/packages/@reactpy/client/tsconfig.json similarity index 64% rename from src/client/packages/client/tsconfig.json rename to src/client/packages/@reactpy/client/tsconfig.json index 27f751157..2e1483e10 100644 --- a/src/client/packages/client/tsconfig.json +++ b/src/client/packages/@reactpy/client/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.package.json", + "extends": "../../../tsconfig.package.json", "compilerOptions": { "outDir": "dist", "rootDir": "src", @@ -8,7 +8,7 @@ "include": ["src"], "references": [ { - "path": "../event-to-object" + "path": "../../event-to-object" } ] } From ee49117d57c79df57572ba1dba55ca275a28f796 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Thu, 23 Mar 2023 00:36:18 -0700 Subject: [PATCH 16/16] fix types in noxfile --- noxfile.py | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/noxfile.py b/noxfile.py index 635a9cac6..a37549103 100644 --- a/noxfile.py +++ b/noxfile.py @@ -302,7 +302,7 @@ def publish( session.error( f"Invalid tag {tag} - must be of the form --" ) - parsed_tags.append(tag_info) + parsed_tags.append(tag_info) # type: ignore publishers: list[tuple[Path, Callable[[bool], None]]] = [] for tag, tag_pkg, tag_ver in parsed_tags: @@ -416,20 +416,18 @@ def get_packages(session: Session) -> dict[str, PackageInfo]: } # collect javascript packages - packages: list[Path] = [] + js_package_paths: list[Path] = [] for maybed_pkg in (CLIENT_DIR / "packages").glob("*"): if not (maybed_pkg / "package.json").exists(): for nmaybe_namespaced_pkg in maybed_pkg.glob("*"): if (nmaybe_namespaced_pkg / "package.json").exists(): - packages.append(nmaybe_namespaced_pkg) + js_package_paths.append(nmaybe_namespaced_pkg) else: - packages.append(maybed_pkg) + js_package_paths.append(maybed_pkg) # get javascript package info - for pkg in packages: - pkg_json_file = pkg / "package.json" - if not pkg_json_file.exists(): - session.error(f"package.json not found in {pkg}") + for pkg in js_package_paths: + pkg_json_file = pkg / "package.json" # we already know this exists pkg_json = json.loads(pkg_json_file.read_text()) @@ -511,11 +509,7 @@ def parse_tag(tag: str) -> TagInfo | None: match = TAG_PATTERN.match(tag) if not match: return None - return TagInfo( - tag, - match["name"], # type: ignore[index] - match["version"], # type: ignore[index] - ) + return TagInfo(tag, match["name"], match["version"]) class TagInfo(NamedTuple): @@ -539,5 +533,4 @@ def get_reactpy_package_version(session: Session) -> str: # type: ignore[return # remove the quotes [1:-1] ) - else: - session.error(f"No version found in {pkg_root_init_file}") + session.error(f"No version found in {pkg_root_init_file}")