diff --git a/README.md b/README.md index 5d11a9b..4eecdad 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,10 @@ npm install --save-prod react-corsair # Overview -URLs don't matter because they aren't a part of your domain. React Corsair is a router that abstracts URLs away from -your application. It replaces URLs -with [`Route`](https://smikhalevski.github.io/react-corsair/functions/createRoute.html) objects that can be used for -location matching, params validation, navigation, type inference, etc. +URLs don't matter since they are almost never part of the application domain logic. React Corsair is a router that +abstracts URLs away from your application. +Use [`Route`](https://smikhalevski.github.io/react-corsair/functions/createRoute.html) objects instead of URLs +to match locations, validate params, navigate between pages, prefetch data, infer types, etc. React Corsair can be used in any environment and doesn't require any browser-specific API to be available. Browser history integration is optional. @@ -447,17 +447,17 @@ createRoute('/foo%3Abar'); You can access matched [pathname params](#pathname-templates) and search params in route components: ```ts -import { createRoute, useRouteState } from 'react-corsair'; +import { createRoute, useRouteParams } from 'react-corsair'; interface TeamParams { teamId: string; sortBy: 'username' | 'createdAt'; } -const teamRoute = createRoute('/teams/:teamId', TeamPage); +const teamRoute = createRoute('/teams/:teamId', TeamPage); function TeamPage() { - const { params } = useRouteState(teamRoute); + const params = useRouteParams(teamRoute); // 🟡 The params type was inferred from the teamRoute. return `Team ${params.teamId} is sorted by ${params.sortBy}.`; @@ -589,7 +589,7 @@ function TeamPage() { } ``` -Here, [`navigation.push`](https://smikhalevski.github.io/react-corsair/classes/Navigation.html#push) triggers +Here, [`navigation.push`](https://smikhalevski.github.io/react-corsair/interfaces/Navigation.html#push) triggers [`onPush`](https://smikhalevski.github.io/react-corsair/interfaces/RouterProps.html#onPush) with the location of `userRoute`. @@ -657,13 +657,13 @@ This tells `` to always render `userRoute.loadingComponent` when `userRo loaded yet. `loadingAppearance` can be set to:
-
"loading"
+
"loading"
A `loadingComponent` is always rendered if a route is matched and a component or a data loader are being loaded.
-
"auto"
+
"auto"
If another route is currently rendered then it would be preserved until a component and data loader of a newly @@ -717,10 +717,10 @@ During route component rendering, you may detect that there's not enough data to the [`notFound`](https://smikhalevski.github.io/react-corsair/functions/notFound.html) function in such case: ```ts -import { notFound, useRouteState } from 'react-corsair'; +import { notFound, useRouteParams } from 'react-corsair'; function UserPage() { - const { params } = useRouteState(userRoute); + const params = useRouteParams(userRoute); const user = useUser(params.userId); @@ -835,11 +835,11 @@ const userRoute = createRoute({ `` matches the route. Router waits for both component and data to be loaded and then renders the component. You can access the loaded data in your route component using -the [useRouteState](https://smikhalevski.github.io/react-corsair/functions/useRouteState.html) hook: +the [useRouteData](https://smikhalevski.github.io/react-corsair/functions/useRouteData.html) hook: ```ts function UserPage() { - const { data } = useRouteState(userRoute); + const userData = useRouteData(userRoute); // Render the data here } @@ -888,7 +888,7 @@ method on a route itself: userRoute.prefetch({ userId: 42 }); ``` -Or user [`Navigation`](https://smikhalevski.github.io/react-corsair/classes/Navigation.html) to prefetch a location: +Or user [`Navigation`](https://smikhalevski.github.io/react-corsair/interfaces/Navigation.html) to prefetch a location: ```ts const navigation = useNavigation(); diff --git a/jest.config.json b/jest.config.json index fee17b1..1fcc27b 100644 --- a/jest.config.json +++ b/jest.config.json @@ -6,7 +6,6 @@ "modulePathIgnorePatterns": [ "/lib" ], - "testEnvironment": "jsdom", "detectOpenHandles": true, "forceExit": true } diff --git a/package-lock.json b/package-lock.json index 21fc4a4..950dc46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,24 +13,25 @@ }, "devDependencies": { "@rollup/plugin-typescript": "^11.1.6", - "@testing-library/jest-dom": "^6.4.6", + "@testing-library/jest-dom": "^6.4.8", "@testing-library/react": "^16.0.0", "@types/jest": "^29.5.12", + "@types/jsdom": "^21.1.7", "@types/react": "^18.3.3", "jest": "^29.7.0", - "jest-environment-jsdom": "^29.7.0", + "jsdom": "^24.1.1", "prettier": "^3.3.3", "rimraf": "^6.0.1", - "rollup": "^4.18.1", - "ts-jest": "^29.2.2", + "rollup": "^4.20.0", + "ts-jest": "^29.2.4", "tslib": "^2.6.3", - "typedoc": "^0.26.4", + "typedoc": "^0.26.5", "typedoc-custom-css": "github:smikhalevski/typedoc-custom-css#master", - "typedoc-plugin-mdn-links": "^3.2.4", - "typescript": "^5.5.3" + "typedoc-plugin-mdn-links": "^3.2.8", + "typescript": "^5.5.4" }, "peerDependencies": { - "react": ">=16.8.0" + "react": ">=18.0.0" } }, "node_modules/@adobe/css-tools": { @@ -66,30 +67,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.9.tgz", - "integrity": "sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", + "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz", - "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.9", - "@babel/helper-compilation-targets": "^7.24.8", - "@babel/helper-module-transforms": "^7.24.9", - "@babel/helpers": "^7.24.8", - "@babel/parser": "^7.24.8", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.8", - "@babel/types": "^7.24.9", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -105,12 +106,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.10.tgz", - "integrity": "sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", "dev": true, "dependencies": { - "@babel/types": "^7.24.9", + "@babel/types": "^7.25.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -120,12 +121,12 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz", - "integrity": "sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.24.8", + "@babel/compat-data": "^7.25.2", "@babel/helper-validator-option": "^7.24.8", "browserslist": "^4.23.1", "lru-cache": "^5.1.1", @@ -135,43 +136,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", - "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", - "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", @@ -186,16 +150,15 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.9.tgz", - "integrity": "sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", "@babel/helper-module-imports": "^7.24.7", "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7" + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -226,18 +189,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.24.8", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", @@ -266,13 +217,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.8.tgz", - "integrity": "sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", + "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", "dev": true, "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.8" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -365,10 +316,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz", - "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", + "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", "dev": true, + "dependencies": { + "@babel/types": "^7.25.2" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -412,6 +366,36 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", @@ -523,6 +507,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", @@ -554,9 +553,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.8.tgz", - "integrity": "sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -566,33 +565,30 @@ } }, "node_modules/@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.8.tgz", - "integrity": "sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", + "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.8", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.8", - "@babel/types": "^7.24.8", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.2", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -601,9 +597,9 @@ } }, "node_modules/@babel/types": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.9.tgz", - "integrity": "sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.24.8", @@ -1158,9 +1154,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz", - "integrity": "sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", + "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", "cpu": [ "arm" ], @@ -1171,9 +1167,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.1.tgz", - "integrity": "sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", + "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", "cpu": [ "arm64" ], @@ -1184,9 +1180,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.1.tgz", - "integrity": "sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", + "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", "cpu": [ "arm64" ], @@ -1197,9 +1193,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.1.tgz", - "integrity": "sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", + "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", "cpu": [ "x64" ], @@ -1210,9 +1206,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.1.tgz", - "integrity": "sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", + "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", "cpu": [ "arm" ], @@ -1223,9 +1219,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.1.tgz", - "integrity": "sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", + "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", "cpu": [ "arm" ], @@ -1236,9 +1232,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.1.tgz", - "integrity": "sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", + "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", "cpu": [ "arm64" ], @@ -1249,9 +1245,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.1.tgz", - "integrity": "sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", + "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", "cpu": [ "arm64" ], @@ -1262,9 +1258,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.1.tgz", - "integrity": "sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", + "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", "cpu": [ "ppc64" ], @@ -1275,9 +1271,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.1.tgz", - "integrity": "sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", + "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", "cpu": [ "riscv64" ], @@ -1288,9 +1284,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.1.tgz", - "integrity": "sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", + "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", "cpu": [ "s390x" ], @@ -1301,9 +1297,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.1.tgz", - "integrity": "sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", + "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", "cpu": [ "x64" ], @@ -1314,9 +1310,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.1.tgz", - "integrity": "sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", + "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", "cpu": [ "x64" ], @@ -1327,9 +1323,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.1.tgz", - "integrity": "sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", + "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", "cpu": [ "arm64" ], @@ -1340,9 +1336,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.1.tgz", - "integrity": "sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", + "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", "cpu": [ "ia32" ], @@ -1353,9 +1349,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.1.tgz", - "integrity": "sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", + "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==", "cpu": [ "x64" ], @@ -1366,9 +1362,9 @@ ] }, "node_modules/@shikijs/core": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.10.3.tgz", - "integrity": "sha512-D45PMaBaeDHxww+EkcDQtDAtzv00Gcsp72ukBtaLSmqRvh0WgGMq3Al0rl1QQBZfuneO75NXMIzEZGFitThWbg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.13.0.tgz", + "integrity": "sha512-Mj5NVfbAXcD1GnwOTSPl8hBn/T8UDpfFQTptp+p41n/CbUcJtOq98WaRD7Lz3hCglYotUTHUWtzu3JhK6XlkAA==", "dev": true, "dependencies": { "@types/hast": "^3.0.4" @@ -1399,9 +1395,9 @@ } }, "node_modules/@testing-library/dom": { - "version": "10.3.2", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.3.2.tgz", - "integrity": "sha512-0bxIdP9mmPiOJ6wHLj8bdJRq+51oddObeCGdEf6PNEhYd93ZYAN+lPRnEOVFtheVwDM7+p+tza3LAQgp0PTudg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", "dev": true, "peer": true, "dependencies": { @@ -1419,9 +1415,9 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.6.tgz", - "integrity": "sha512-8qpnGVincVDLEcQXWaHOf6zmlbwTKc6Us6PPu4CRnPXCzo2OGBS5cwgMMOWdxDpEz1mkbvXHpEy99M5Yvt682w==", + "version": "6.4.8", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.8.tgz", + "integrity": "sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw==", "dev": true, "dependencies": { "@adobe/css-tools": "^4.4.0", @@ -1437,30 +1433,6 @@ "node": ">=14", "npm": ">=6", "yarn": ">=1" - }, - "peerDependencies": { - "@jest/globals": ">= 28", - "@types/bun": "latest", - "@types/jest": ">= 28", - "jest": ">= 28", - "vitest": ">= 0.32" - }, - "peerDependenciesMeta": { - "@jest/globals": { - "optional": true - }, - "@types/bun": { - "optional": true - }, - "@types/jest": { - "optional": true - }, - "jest": { - "optional": true - }, - "vitest": { - "optional": true - } } }, "node_modules/@testing-library/jest-dom/node_modules/chalk": { @@ -1509,15 +1481,6 @@ } } }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -1657,9 +1620,9 @@ "dev": true }, "node_modules/@types/jsdom": { - "version": "20.0.1", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", - "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "version": "21.1.7", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", "dev": true, "dependencies": { "@types/node": "*", @@ -1668,12 +1631,12 @@ } }, "node_modules/@types/node": { - "version": "20.14.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz", - "integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==", + "version": "22.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.3.0.tgz", + "integrity": "sha512-nrWpWVaDZuaVc5X84xJ0vNrLvomM205oQyLsRt7OHNZbSHslcWsvgFR7O7hire2ZonjLrWBbedmotmIlJDVd6g==", "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.18.2" } }, "node_modules/@types/prop-types": { @@ -1711,9 +1674,9 @@ "dev": true }, "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dev": true, "dependencies": { "@types/yargs-parser": "*" @@ -1725,57 +1688,16 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "deprecated": "Use your platform's native atob() and btoa() methods instead", - "dev": true - }, - "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", - "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", - "dev": true, - "dependencies": { - "acorn": "^8.1.0", - "acorn-walk": "^8.0.2" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", - "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", - "dev": true, - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { - "debug": "4" + "debug": "^4.3.4" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/ansi-escapes": { @@ -1929,23 +1851,26 @@ } }, "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", "dev": true, "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0" @@ -1996,9 +1921,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", - "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "dev": true, "funding": [ { @@ -2015,9 +1940,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001640", - "electron-to-chromium": "^1.4.820", - "node-releases": "^2.0.14", + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", "update-browserslist-db": "^1.1.0" }, "bin": { @@ -2073,9 +1998,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001642", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz", - "integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==", + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", "dev": true, "funding": [ { @@ -2251,28 +2176,22 @@ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", "dev": true }, - "node_modules/cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "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==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", + "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==", "dev": true, "dependencies": { - "cssom": "~0.3.6" + "rrweb-cssom": "^0.6.0" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "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==", + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", "dev": true }, "node_modules/csstype": { @@ -2282,23 +2201,22 @@ "dev": true }, "node_modules/data-urls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", - "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", "dev": true, "dependencies": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0" + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -2384,19 +2302,6 @@ "dev": true, "peer": true }, - "node_modules/domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "dependencies": { - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2419,9 +2324,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.829", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.829.tgz", - "integrity": "sha512-5qp1N2POAfW0u1qGAxXEtz6P7bO1m6gpZr5hdf5ve6lxpLM7MpiM4jIPz7xcrNlClQMafbyUDDWjlIQZ1Mw0Rw==", + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.7.tgz", + "integrity": "sha512-6FTNWIWMxMy/ZY6799nBlPtF1DFDQ6VQJ7yyDP27SJNt5lwtQ5ufqVvHylb3fdQefvRcgA3fKcFMJi9OLwBRNw==", "dev": true }, "node_modules/emittery": { @@ -2481,27 +2386,6 @@ "node": ">=8" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -2515,30 +2399,12 @@ "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/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true }, - "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/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -2658,9 +2524,9 @@ } }, "node_modules/foreground-child": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", - "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, "dependencies": { "cross-spawn": "^7.0.0", @@ -2825,15 +2691,15 @@ } }, "node_modules/html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", "dev": true, "dependencies": { - "whatwg-encoding": "^2.0.0" + "whatwg-encoding": "^3.1.1" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/html-escaper": { @@ -2843,30 +2709,29 @@ "dev": true }, "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "dependencies": { - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/human-signals": { @@ -2891,9 +2756,9 @@ } }, "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, "dependencies": { "pkg-dir": "^4.2.0", @@ -2951,9 +2816,9 @@ "dev": true }, "node_modules/is-core-module": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", - "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", "dev": true, "dependencies": { "hasown": "^2.0.2" @@ -3113,9 +2978,9 @@ } }, "node_modules/jake": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz", - "integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", "dev": true, "dependencies": { "async": "^3.2.3", @@ -3450,33 +3315,6 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, - "node_modules/jest-environment-jsdom": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", - "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/jsdom": "^20.0.0", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0", - "jsdom": "^20.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, "node_modules/jest-environment-node": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", @@ -4031,43 +3869,38 @@ } }, "node_modules/jsdom": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", - "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.6", - "acorn": "^8.8.1", - "acorn-globals": "^7.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.4.2", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", + "version": "24.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.1.tgz", + "integrity": "sha512-5O1wWV99Jhq4DV7rCLIoZ/UIhyQeDR7wHVyZAHAshbrvZsLs+Xzz7gtwnlJTJDjleiTKh54F4dXrX70vJQTyJQ==", + "dev": true, + "dependencies": { + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.2", - "parse5": "^7.1.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", + "tough-cookie": "^4.1.4", + "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.11.0", - "xml-name-validator": "^4.0.0" + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" }, "peerDependencies": { - "canvas": "^2.5.0" + "canvas": "^2.11.2" }, "peerDependenciesMeta": { "canvas": { @@ -4368,9 +4201,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz", - "integrity": "sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, "node_modules/normalize-path": { @@ -4896,9 +4729,9 @@ } }, "node_modules/rollup": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.1.tgz", - "integrity": "sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", + "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -4911,25 +4744,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.18.1", - "@rollup/rollup-android-arm64": "4.18.1", - "@rollup/rollup-darwin-arm64": "4.18.1", - "@rollup/rollup-darwin-x64": "4.18.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.18.1", - "@rollup/rollup-linux-arm-musleabihf": "4.18.1", - "@rollup/rollup-linux-arm64-gnu": "4.18.1", - "@rollup/rollup-linux-arm64-musl": "4.18.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.18.1", - "@rollup/rollup-linux-riscv64-gnu": "4.18.1", - "@rollup/rollup-linux-s390x-gnu": "4.18.1", - "@rollup/rollup-linux-x64-gnu": "4.18.1", - "@rollup/rollup-linux-x64-musl": "4.18.1", - "@rollup/rollup-win32-arm64-msvc": "4.18.1", - "@rollup/rollup-win32-ia32-msvc": "4.18.1", - "@rollup/rollup-win32-x64-msvc": "4.18.1", + "@rollup/rollup-android-arm-eabi": "4.20.0", + "@rollup/rollup-android-arm64": "4.20.0", + "@rollup/rollup-darwin-arm64": "4.20.0", + "@rollup/rollup-darwin-x64": "4.20.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.20.0", + "@rollup/rollup-linux-arm-musleabihf": "4.20.0", + "@rollup/rollup-linux-arm64-gnu": "4.20.0", + "@rollup/rollup-linux-arm64-musl": "4.20.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0", + "@rollup/rollup-linux-riscv64-gnu": "4.20.0", + "@rollup/rollup-linux-s390x-gnu": "4.20.0", + "@rollup/rollup-linux-x64-gnu": "4.20.0", + "@rollup/rollup-linux-x64-musl": "4.20.0", + "@rollup/rollup-win32-arm64-msvc": "4.20.0", + "@rollup/rollup-win32-ia32-msvc": "4.20.0", + "@rollup/rollup-win32-x64-msvc": "4.20.0", "fsevents": "~2.3.2" } }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -4989,12 +4828,12 @@ } }, "node_modules/shiki": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.10.3.tgz", - "integrity": "sha512-eneCLncGuvPdTutJuLyUGS8QNPAVFO5Trvld2wgEq1e002mwctAhJKeMGWtWVXOIEzmlcLRqcgPSorR6AVzOmQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.13.0.tgz", + "integrity": "sha512-e0dWfnONbEv6xl7FJy3XIhsVHQ/65XHDZl92+6H9+4xWjfdo7pmkqG7Kg47KWtDiEtzM5Z+oEfb4vtRvoZ/X9w==", "dev": true, "dependencies": { - "@shikijs/core": "1.10.3", + "@shikijs/core": "1.13.0", "@types/hast": "^3.0.4" } }, @@ -5252,25 +5091,25 @@ } }, "node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", "dev": true, "dependencies": { - "punycode": "^2.1.1" + "punycode": "^2.3.1" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/ts-jest": { - "version": "29.2.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.2.tgz", - "integrity": "sha512-sSW7OooaKT34AAngP6k1VS669a0HdLxkQZnlC7T76sckGCokXFnvJ3yRlQZGRTAoV5K19HfSgCiSwWOSIfcYlg==", + "version": "29.2.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.4.tgz", + "integrity": "sha512-3d6tgDyhCI29HlpwIq87sNuI+3Q6GLTTCeYRHCs7vDz+/3GCMwEtV9jezLyl4ZtnBgx00I7hm8PCP8cTksMGrw==", "dev": true, "dependencies": { "bs-logger": "0.x", - "ejs": "^3.0.0", + "ejs": "^3.1.10", "fast-json-stable-stringify": "2.x", "jest-util": "^29.0.0", "json5": "^2.2.3", @@ -5351,9 +5190,9 @@ } }, "node_modules/typedoc": { - "version": "0.26.4", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.4.tgz", - "integrity": "sha512-FlW6HpvULDKgc3rK04V+nbFyXogPV88hurarDPOjuuB5HAwuAlrCMQ5NeH7Zt68a/ikOKu6Z/0hFXAeC9xPccQ==", + "version": "0.26.5", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.5.tgz", + "integrity": "sha512-Vn9YKdjKtDZqSk+by7beZ+xzkkr8T8CYoiasqyt4TTRFy5+UHzL/mF/o4wGBjRF+rlWQHDb0t6xCpA3JNL5phg==", "dev": true, "dependencies": { "lunr": "^2.3.9", @@ -5373,14 +5212,14 @@ } }, "node_modules/typedoc-custom-css": { - "resolved": "git+ssh://git@github.com/smikhalevski/typedoc-custom-css.git#4112e4ab9f05d1fd75de812b6da849442dbb3d95", + "resolved": "git+ssh://git@github.com/smikhalevski/typedoc-custom-css.git#549467cdeab5736f61f9f107314d6318909e646a", "dev": true, "license": "MIT" }, "node_modules/typedoc-plugin-mdn-links": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-3.2.4.tgz", - "integrity": "sha512-TmBBmtIuTR4mSuYjcmiibjvmpM2oSeUC0E5cgPnrM3HEdhiXsDGNA6SyPorvauqYN5Hd0si2EJLJs6KjP9b4Lw==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-3.2.8.tgz", + "integrity": "sha512-3MH1AC53biA6eq8bSpE44gJWAXgjUGVeVKIjcFPLNA9gmN6IF4vROrco/943vKqTekhJzG3MMZnKOJZG9SJLrA==", "dev": true, "peerDependencies": { "typedoc": ">= 0.23.14 || 0.24.x || 0.25.x || 0.26.x" @@ -5411,9 +5250,9 @@ } }, "node_modules/typescript": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", - "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -5430,9 +5269,9 @@ "dev": true }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.18.2.tgz", + "integrity": "sha512-5ruQbENj95yDYJNS3TvcaxPMshV7aizdv/hWYjGIKoANWKjhWNBsr2YEuYZKodQulB1b8l7ILOuDQep3afowQQ==", "dev": true }, "node_modules/universalify": { @@ -5499,15 +5338,15 @@ } }, "node_modules/w3c-xmlserializer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", - "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", "dev": true, "dependencies": { - "xml-name-validator": "^4.0.0" + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/walker": { @@ -5529,37 +5368,37 @@ } }, "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==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", "dev": true, "dependencies": { "iconv-lite": "0.6.3" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "dev": true, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", "dev": true, "dependencies": { - "tr46": "^3.0.0", + "tr46": "^5.0.0", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/which": { @@ -5653,12 +5492,12 @@ } }, "node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", "dev": true, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/xmlchars": { @@ -5683,9 +5522,9 @@ "dev": true }, "node_modules/yaml": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", - "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", + "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", "dev": true, "bin": { "yaml": "bin.mjs" diff --git a/package.json b/package.json index 49db302..bfb9a99 100644 --- a/package.json +++ b/package.json @@ -38,21 +38,22 @@ "homepage": "https://github.com/smikhalevski/react-corsair#readme", "devDependencies": { "@rollup/plugin-typescript": "^11.1.6", - "@testing-library/jest-dom": "^6.4.6", + "@testing-library/jest-dom": "^6.4.8", "@testing-library/react": "^16.0.0", "@types/jest": "^29.5.12", + "@types/jsdom": "^21.1.7", "@types/react": "^18.3.3", "jest": "^29.7.0", - "jest-environment-jsdom": "^29.7.0", + "jsdom": "^24.1.1", "prettier": "^3.3.3", "rimraf": "^6.0.1", - "rollup": "^4.18.1", - "ts-jest": "^29.2.2", + "rollup": "^4.20.0", + "ts-jest": "^29.2.4", "tslib": "^2.6.3", - "typedoc": "^0.26.4", + "typedoc": "^0.26.5", "typedoc-custom-css": "github:smikhalevski/typedoc-custom-css#master", - "typedoc-plugin-mdn-links": "^3.2.4", - "typescript": "^5.5.3" + "typedoc-plugin-mdn-links": "^3.2.8", + "typescript": "^5.5.4" }, "peerDependencies": { "react": ">=18.0.0" diff --git a/src/main/InternalRouter.tsx b/src/main/InternalRouter.tsx new file mode 100644 index 0000000..ebc224b --- /dev/null +++ b/src/main/InternalRouter.tsx @@ -0,0 +1,123 @@ +import React, { Component } from 'react'; +import { createNavigation } from './createNavigation'; +import { matchRoutes, RouteMatch } from './matchRoutes'; +import { Outlet } from './Outlet'; +import { Route } from './Route'; +import { RouterProps } from './Router'; +import { ChildSlotControllerContext } from './Slot'; +import { LoadingSlotController, NotFoundSlotController, RouteSlotController, SlotController } from './SlotController'; +import { Location } from './types'; +import { InternalRouterContext } from './useInternalRouter'; +import { hydrateRoutes, loadServerRoutes, RouterHydrationScript } from './content-loaders'; +import { isArrayEqual } from './utils'; + +interface InternalRouterProps extends Omit, 'context'> { + routerId: string; + context?: undefined; +} + +interface InternalRouterState { + location: Partial | null; + routes: Route[]; + slotControllers: SlotController[]; + isServer: boolean; +} + +export class InternalRouter extends Component { + static displayName = 'InternalRouter'; + + readonly navigation = createNavigation(this); + + static getDerivedStateFromProps( + props: InternalRouterProps, + state: InternalRouterState + ): Partial | null { + if (state.location === props.location && isArrayEqual(state.routes, props.routes)) { + return null; + } + + const { pathname = '/', searchParams = {} } = props.location; + + const routeMatches = matchRoutes(pathname, searchParams, props.routes); + + return { + location: props.location, + routes: props.routes, + slotControllers: createSlotControllers(state.slotControllers, routeMatches, props, state.isServer), + }; + } + + constructor(props: InternalRouterProps) { + super(props); + + this.state = { + location: null, + routes: props.routes, + slotControllers: [], + isServer: typeof window === 'undefined', + }; + } + + render() { + const controller = this.state.slotControllers[0]; + + return ( + + + {this.state.isServer && controller instanceof RouteSlotController && } + {this.props.children === undefined ? : this.props.children} + + + ); + } +} + +export function createSlotControllers( + prevControllers: SlotController[], + routeMatches: RouteMatch[] | null, + routerProps: InternalRouterProps, + isServer: boolean +): SlotController[] { + if (routeMatches === null) { + // Not found + return [new NotFoundSlotController(routerProps)]; + } + + const routeContents = isServer + ? loadServerRoutes(routeMatches, routerProps.context) + : hydrateRoutes(routerProps.routerId, routeMatches, routerProps.context, routerProps.stateParser); + + const slotControllers: SlotController[] = []; + + // Matched a route + for (let i = 0; i < routeContents.length; ++i) { + slotControllers.push( + new RouteSlotController(prevControllers[i], { + index: i, + routeMatch: routeMatches[i], + routeContent: routeContents[i], + errorComponent: i === 0 ? routerProps.errorComponent : undefined, + loadingComponent: i === 0 ? routerProps.loadingComponent : undefined, + notFoundComponent: i === 0 ? routerProps.notFoundComponent : undefined, + }) + ); + } + + if (isServer && slotControllers.length !== routeMatches.length) { + // Client-only routes aren't rendered on the server + const { route } = routeMatches[slotControllers.length]; + + slotControllers.push( + new LoadingSlotController( + slotControllers.length === 0 ? route.loadingComponent || routerProps.loadingComponent : route.loadingComponent + ) + ); + } + + // Parent-child relationships + for (let i = 1; i < slotControllers.length; ++i) { + slotControllers[i - 1].childController = slotControllers[i]; + } + + return slotControllers; +} diff --git a/src/main/Navigation.ts b/src/main/Navigation.ts deleted file mode 100644 index 5821a5e..0000000 --- a/src/main/Navigation.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { matchRoutes } from './matchRoutes'; -import { Router, RouterProps } from './Router'; -import { To } from './types'; -import { toLocation } from './utils'; - -/** - * Provides components a way to trigger router navigation. - */ -export class Navigation { - /** - * Creates a new {@link Navigation} instance. - * - * @param _router A router to which navigation is attached. - * @internal - */ - constructor(private _router: Router) {} - - /** - * Triggers {@link RouterProps.onPush} with the requested location. - * - * @param to A location or route. - */ - push(to: To): void { - this._router.props.onPush?.(toLocation(to)); - } - - /** - * Triggers {@link RouterProps.onReplace} with the requested location. - * - * @param to A location or route. - */ - replace(to: To): void { - this._router.props.onPush?.(toLocation(to)); - } - - /** - * Triggers {@link RouterProps.onBack}. - */ - back(): void { - this._router.props.onBack?.(); - } - - /** - * Prefetch a content of a route matched by a location and content of its ancestors. - * - * @param to A location or route. - * @returns `true` if the route was prefetched, or `false` if there's no route in the router that matches the provided - * location. - */ - prefetch(to: To): boolean { - const location = toLocation(to); - const { routes, context } = this._router.props; - const routeMatch = matchRoutes(location.pathname, location.searchParams, routes)?.pop(); - - if (routeMatch === undefined) { - return false; - } - routeMatch.route.prefetch(routeMatch.params, context); - return true; - } -} diff --git a/src/main/Outlet.tsx b/src/main/Outlet.tsx index 43bb114..c58e9dc 100644 --- a/src/main/Outlet.tsx +++ b/src/main/Outlet.tsx @@ -1,8 +1,8 @@ import React, { ReactNode, useContext } from 'react'; -import { Slot, SlotValueContext } from './Slot'; +import { ChildSlotControllerContext, Slot } from './Slot'; /** - * Props of an {@link Outlet}. + * Props of the {@link Outlet} component. */ export interface OutletProps { /** @@ -15,9 +15,12 @@ export interface OutletProps { * Renders a route provided by an enclosing {@link Router}. */ export function Outlet(props: OutletProps): ReactNode { - const value = useContext(SlotValueContext); + const controller = useContext(ChildSlotControllerContext); - return value === undefined ? props.children : ; + return controller === undefined ? props.children : ; } +/** + * @internal + */ Outlet.displayName = 'Outlet'; diff --git a/src/main/Route.ts b/src/main/Route.ts index aabdc2c..bbd7056 100644 --- a/src/main/Route.ts +++ b/src/main/Route.ts @@ -1,13 +1,27 @@ import { ComponentType } from 'react'; +import { NotFoundError } from './notFound'; import { Outlet } from './Outlet'; import { PathnameTemplate } from './PathnameTemplate'; -import { Dict, LoadingAppearance, Location, LocationOptions, ParamsAdapter, RouteOptions } from './types'; +import { Redirect } from './redirect'; +import { + Dict, + LoadingAppearance, + Location, + LocationOptions, + ParamsAdapter, + RenderingDisposition, + RouteOptions, +} from './types'; -type Squash = { [K in keyof T]: T[K] } & {}; +type Squash = { [K in keyof T]: T[K] } & unknown; + +type PartialToVoid = Partial extends T ? T | void : T; /** * A route that can be rendered by a router. * + * Use {@link createRoute} to create a {@link Route} instance. + * * @template Parent A parent route or `null` if there is no parent. * @template Params Route params. * @template Data Data loaded by a route. @@ -24,7 +38,7 @@ export class Route< * * @internal */ - declare _params: Parent extends Route ? Squash : Params; + declare _params: PartialToVoid : Params>; /** * The type of route context. @@ -49,6 +63,14 @@ export class Route< */ paramsAdapter: ParamsAdapter | undefined; + /** + * Loads data required to render a route. + * + * @param params Route params extracted from a location. + * @param context A {@link RouterProps.context} provided to a {@link Router}. + */ + loader: ((params: Params, context: Context) => PromiseLike | Data) | undefined; + /** * A component that is rendered when an error was thrown during route rendering. */ @@ -70,12 +92,9 @@ export class Route< loadingAppearance: LoadingAppearance; /** - * Loads data required to render a route. - * - * @param params Route params extracted from a location. - * @param context A {@link RouterProps.context} provided to a {@link Router}. + * Where the route is rendered. */ - loader: ((params: Params, context: Context) => PromiseLike | Data) | undefined; + renderingDisposition: RenderingDisposition; /** * Loads and caches a route component. @@ -98,11 +117,12 @@ export class Route< this.parent = parent; this.pathnameTemplate = new PathnameTemplate(options.pathname || '/', options.isCaseSensitive); this.paramsAdapter = typeof paramsAdapter === 'function' ? { parse: paramsAdapter } : paramsAdapter; + this.loader = options.loader; this.errorComponent = options.errorComponent; this.loadingComponent = options.loadingComponent; this.notFoundComponent = options.notFoundComponent; this.loadingAppearance = options.loadingAppearance || 'auto'; - this.loader = options.loader; + this.renderingDisposition = options.renderingDisposition || 'server'; let component: Promise | ComponentType | undefined = options.component; @@ -224,6 +244,10 @@ export class Route< route.getComponent(); route.loader?.(params, context); } catch (error) { + if (error instanceof NotFoundError || error instanceof Redirect) { + return; + } + setTimeout(() => { // Force uncaught exception throw error; diff --git a/src/main/Router.tsx b/src/main/Router.tsx index 1b9747b..4ed05ba 100644 --- a/src/main/Router.tsx +++ b/src/main/Router.tsx @@ -1,13 +1,7 @@ -import React, { Component, ComponentType, ReactNode } from 'react'; -import { matchRoutes, RouteMatch } from './matchRoutes'; -import { Navigation } from './Navigation'; -import { Outlet } from './Outlet'; +import React, { ComponentType, ReactElement, ReactNode, useId } from 'react'; import { Route } from './Route'; -import { Location } from './types'; -import { NavigationContext } from './useNavigation'; -import { SlotValueContext } from './Slot'; -import { SlotValue } from './SlotValue'; -import { isArrayEqual } from './utils'; +import { Location, RouteState } from './types'; +import { InternalRouter } from './InternalRouter'; /** * Props of the {@link Router} component. @@ -72,118 +66,54 @@ export interface RouterProps { * the {@link location}. */ notFoundComponent?: ComponentType; -} -/** - * Options of a {@link Router} that doesn't provide any context for a {@link RouteOptions.loader}. - */ -interface NoContextRouterProps extends Omit, 'context'> { /** - * An arbitrary context provided to {@link RouteOptions.loader}. + * Parses a route state when route is hydrated on the client after SSR. + * + * @param stateStr A stringified state to parse. + * @default JSON.parse */ - context?: undefined; -} - -interface RouterState { - navigation: Navigation; - location: Partial | null; - routes: Route[]; - slotValues: SlotValue[]; -} + stateParser?: (stateStr: string) => RouteState; -/** - * A router that renders a route that matches the provided location. - * - * @template Context A context provided by a {@link Router} for a {@link RouteOptions.loader}. - */ -export class Router extends Component, RouterState> { /** - * @internal + * Stringifies a route state during SSR. + * + * @param state A route state to stringify. + * @default JSON.stringify */ - static displayName = 'Router'; + stateStringifier?: (state: RouteState) => string; /** - * @internal + * A [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src) + * nonce that should be also passed as `script-src` directive in an HTTP header. */ - static getDerivedStateFromProps(props: RouterProps, state: RouterState): Partial | null { - if (state.location === props.location && isArrayEqual(state.routes, props.routes)) { - return null; - } - - const { pathname = '/', searchParams = {} } = props.location; - - const routeMatches = matchRoutes(pathname, searchParams, props.routes); + nonce?: string; +} - return { - location: props.location, - routes: props.routes, - slotValues: createSlotValues(state.slotValues, routeMatches, props), - }; - } +/** + * A router that renders a route that matches the provided location. + */ +export function Router(props: Omit, 'context'>): ReactElement; - /** - * @internal - */ - constructor(props: RouterProps) { - super(props); +/** + * A router that renders a route that matches the provided location. + * + * @template Context A context provided by a {@link Router} for a {@link RouteOptions.loader}. + */ +export function Router(props: RouterProps): ReactElement; - this.state = { - navigation: new Navigation(this), - location: null, - routes: props.routes, - slotValues: [], - }; - } +export function Router(props: Omit, 'context'> | RouterProps): ReactElement { + const routerId = useId().toLowerCase(); - /** - * @internal - */ - render() { - return ( - - - {this.props.children === undefined ? : this.props.children} - - - ); - } + return ( + + ); } -export function createSlotValues( - oldSlotValues: SlotValue[], - routeMatches: RouteMatch[] | null, - routerProps: RouterProps -): SlotValue[] { - const { context, errorComponent, loadingComponent, notFoundComponent } = routerProps; - - if (routeMatches === null) { - // Not found - return [ - new SlotValue({ - errorComponent, - loadingComponent, - notFoundComponent, - }), - ]; - } - - const slotValues: SlotValue[] = []; - - // Matched a route - for (let i = routeMatches.length; i-- > 0; ) { - const route = routeMatches[i].route; - - slotValues[i] = new SlotValue({ - oldValue: oldSlotValues[i], - childValue: slotValues[i + 1], - route, - params: routeMatches[i].params, - context, - errorComponent: i === 0 ? errorComponent : undefined, - loadingComponent: i === 0 ? loadingComponent : undefined, - notFoundComponent: i === 0 ? notFoundComponent : undefined, - }); - } - - return slotValues; -} +/** + * @internal + */ +Router.displayName = 'Router'; diff --git a/src/main/Slot.tsx b/src/main/Slot.tsx index 216feea..ecce457 100644 --- a/src/main/Slot.tsx +++ b/src/main/Slot.tsx @@ -1,96 +1,114 @@ -import React, { Component, createElement, memo, ReactNode, Suspense } from 'react'; -import { RouteContent, SlotValue } from './SlotValue'; +import React, { Component, createElement, ReactNode, Suspense } from 'react'; +import { SlotController } from './SlotController'; +import { useInternalRouter } from './useInternalRouter'; +import { SlotControllerContext } from './useSlotController'; -export const SlotValueContext = React.createContext(undefined); +export const ChildSlotControllerContext = React.createContext(undefined); -SlotValueContext.displayName = 'SlotValueContext'; - -export const RouteContentContext = React.createContext(undefined); - -RouteContentContext.displayName = 'RouteContentContext'; +ChildSlotControllerContext.displayName = 'ChildSlotControllerContext'; interface SlotProps { - value: SlotValue; + controller: SlotController; } interface SlotState { - hasError: boolean; + /** + * An error captured by an error boundary. + */ error: unknown; + + /** + * `true` if an error boundary was triggered. + */ + hasError: boolean; } -/** - * Renders a volatile value that reflects a route content. - */ -export const Slot = memo( - class extends Component { - static displayName = 'Slot'; +export class Slot extends Component { + static displayName = 'Slot'; - constructor(props: SlotProps) { - super(props); + private declare _unsubscribe: () => void; - this.state = { hasError: false, error: undefined }; - } + constructor(props: SlotProps) { + super(props); - static getDerivedStateFromError(error: unknown): Partial | null { - return { hasError: true, error }; - } + this.state = { error: undefined, hasError: false }; - static getDerivedStateFromProps(nextProps: Readonly, prevState: SlotState): Partial | null { - if (prevState.hasError) { - // Move error to the content - nextProps.value.setError(prevState.error); + this.forceUpdate = this.forceUpdate.bind(this); + } + + static getDerivedStateFromError(error: unknown): Partial | null { + return { error, hasError: true }; + } - return { hasError: false, error: undefined }; - } - return null; + static getDerivedStateFromProps(nextProps: Readonly, prevState: SlotState): Partial | null { + if (prevState.hasError) { + nextProps.controller.renderedController.onCatch(prevState.error); + + return { error: undefined, hasError: false }; } + return null; + } - componentWillUnmount(): void { - this.props.value.freeze(); + componentDidMount() { + this._unsubscribe = this.props.controller.subscribe(this.forceUpdate); + } + + shouldComponentUpdate(nextProps: Readonly, _nextState: Readonly, _nextContext: any): boolean { + return this.props.controller !== nextProps.controller; + } + + componentDidUpdate(prevProps: Readonly, _prevState: Readonly, _snapshot?: any) { + if (this.props.controller !== prevProps.controller) { + this._unsubscribe(); + this._unsubscribe = this.props.controller.subscribe(this.forceUpdate); } + } + + componentWillUnmount() { + this._unsubscribe(); + } - render(): ReactNode { - const { props } = this; - - return ( - - } - > - - - ); - } - }, - (prevProps, nextProps) => prevProps.value === nextProps.value -); + } + > + + + ); + } +} -interface SlotRendererProps { - isSuspended: boolean; - value: SlotValue; +interface InternalSlotProps { + isSuspendable: boolean; + controller: SlotController; } -function SlotRenderer({ isSuspended, value }: SlotRendererProps): ReactNode { - const component = isSuspended ? value.fallbackComponent : value.component; +function InternalSlot(props: InternalSlotProps): ReactNode { + const { isSuspendable, controller } = props; + const { isServer } = useInternalRouter().state; + const component = isSuspendable ? (controller.onSuspend(), controller.component) : controller.fallbackComponent; - if (!isSuspended && value.promise !== undefined) { - throw value.promise; - } if (component === undefined) { return null; } + return ( - - {createElement(component)} - + + + {isServer && isSuspendable && controller.renderHydrationScript()} + {createElement(component)} + + ); } -SlotRenderer.displayName = 'SlotRenderer'; +InternalSlot.displayName = 'InternalSlot'; diff --git a/src/main/SlotController.tsx b/src/main/SlotController.tsx new file mode 100644 index 0000000..8b8fe67 --- /dev/null +++ b/src/main/SlotController.tsx @@ -0,0 +1,283 @@ +import { PubSub } from 'parallel-universe'; +import React, { ComponentType, ReactNode } from 'react'; +import { RouteMatch } from './matchRoutes'; +import { RouteContent, RouteHydrationScript } from './content-loaders'; +import { NotFoundError } from './notFound'; +import { isPromiseLike } from './utils'; + +/** + * A controller of a {@link Slot}. + */ +export interface SlotController { + /** + * A controller that is actually rendered by a {@link Slot}. This allows swapping content without UI changes. + * For example, if a new route has {@link RouteOptions.loadingAppearance} set to `"auto"`, an old route can be kept + * on screen until the new route finishes loading. + */ + renderedController: SlotController; + + /** + * A controller that is propagated to a child {@link Slot} when this controller is rendered. + */ + childController?: SlotController; + + /** + * A component rendered in a {@link !Suspense} body. + */ + component?: ComponentType; + + /** + * A component rendered in a {@link !Suspense} fallback. + */ + fallbackComponent?: ComponentType; + + /** + * Called if an error was thrown during rendering. + */ + onCatch(error: unknown): void; + + /** + * Called when a component is ready to be suspended. Throw a {@link !Promise} here to suspend the component. + */ + onSuspend(): void; + + /** + * Subscribes a listener to controller changes. + */ + subscribe(listener: () => void): () => void; + + /** + * Renders a script tag during SSR that hydrates the route rendered by the controller. + */ + renderHydrationScript(): ReactNode; +} + +/** + * Common {@link SlotController} options. + */ +export interface SlotControllerOptions { + /** + * A component that is rendered when an error was thrown during a slot rendering. + */ + errorComponent?: ComponentType; + + /** + * A component that is rendered when a {@link !Suspense} boundary is hit. + */ + loadingComponent?: ComponentType; + + /** + * A component that is rendered if {@link notFound} was called during rendering or of there's no route to render. + */ + notFoundComponent?: ComponentType; +} + +/** + * A slot controller of a Not Found page. Rendered by router if no routes matched the provided location. + */ +export class NotFoundSlotController implements SlotController { + renderedController = this; + component; + fallbackComponent; + error: unknown = undefined; + hasError = false; + + protected _pubSub = new PubSub(); + protected _errorComponent; + + constructor(options: SlotControllerOptions) { + this.component = options.notFoundComponent; + this.fallbackComponent = options.loadingComponent; + + this._errorComponent = options.errorComponent; + } + + onCatch(error: unknown): void { + if (this.hasError) { + // Unrecoverable error because it occurred in an _errorComponent + throw error; + } + + this.component = this._errorComponent; + this.error = error; + this.hasError = true; + this._pubSub.publish(); + } + + onSuspend() { + // noop + } + + subscribe(listener: () => void): () => void { + return this._pubSub.subscribe(listener); + } + + renderHydrationScript(): ReactNode { + return undefined; + } +} + +/** + * A slot controller rendered exclusively on the server if matched route rendering disposition is client-only. + */ +export class LoadingSlotController implements SlotController { + renderedController = this; + component; + + constructor(loadingComponent: ComponentType | undefined) { + this.component = loadingComponent; + } + + onCatch(error: unknown): void { + // Error boundaries aren't supported by React during SSR + throw error; + } + + onSuspend() { + // noop + } + + subscribe(_listener: () => void): () => void { + // There are no subscriptions during SSR + return undefined!; + } + + renderHydrationScript(): ReactNode { + return undefined; + } +} + +export interface RouteSlotControllerOptions extends SlotControllerOptions { + /** + * An index of a route match. + */ + index: number; + + /** + * A matched route and params. + */ + routeMatch: RouteMatch; + + /** + * A content of a matched route. + */ + routeContent: Promise | RouteContent; +} + +/** + * A slot controller that renders a matched route. + */ +export class RouteSlotController implements SlotController { + renderedController: SlotController; + childController: SlotController | undefined; + component: ComponentType | undefined; + fallbackComponent; + route; + params; + data: unknown; + error: unknown; + status: 'ok' | 'error' | undefined; + + protected _index; + protected _promise: Promise | undefined; + protected _pubSub = new PubSub(); + protected _errorComponent; + protected _notFoundComponent; + + /** + * @param replacedController A controller that is being replaced in a slot with this controller. + * @param options Controller options. + */ + constructor(replacedController: SlotController | undefined, options: RouteSlotControllerOptions) { + if (replacedController instanceof RouteSlotController) { + replacedController._abort(); + } + + const { route, params } = options.routeMatch; + + this.renderedController = route.loadingAppearance === 'loading' ? this : replacedController || this; + this.childController = this.data = this.error = this.status = this._promise = undefined; + this.component = this.fallbackComponent = route.loadingComponent || options.loadingComponent; + this.component = this.renderedController.component; + this.route = route; + this.params = params; + + this._index = options.index; + this._errorComponent = route.errorComponent || options.errorComponent; + this._notFoundComponent = route.notFoundComponent || options.notFoundComponent; + + this._setContent(options.routeContent); + } + + onCatch(error: unknown): void { + if (this.status === 'error') { + // Unrecoverable error because it occurred in an _errorComponent + throw error; + } + + this.component = this._errorComponent; + this.error = error; + this.status = 'error'; + this._pubSub.publish(); + } + + onSuspend(): void { + if (this._promise !== undefined) { + throw this._promise; + } + } + + subscribe(listener: () => void): () => void { + return this._pubSub.subscribe(listener); + } + + renderHydrationScript(): ReactNode { + return ( + + ); + } + + protected _setContent(content: Promise | RouteContent): void { + if (isPromiseLike(content)) { + const promise = content.then(content => { + if (promise === this._promise) { + this._promise = undefined; + this._setContent(content); + } + }); + + this._promise = promise; + this._pubSub.publish(); + return; + } + + if (content.hasError) { + this.component = content.error instanceof NotFoundError ? this._notFoundComponent : this._errorComponent; + this.status = 'error'; + } else { + this.component = content.component; + this.status = 'ok'; + } + + this.renderedController = this; + this.data = content.data; + this.error = content.error; + this._promise = undefined; + this._pubSub.publish(); + } + + protected _abort(): void { + this._promise = undefined; + + if (this.childController instanceof RouteSlotController) { + this.childController._abort(); + } + } +} diff --git a/src/main/SlotValue.ts b/src/main/SlotValue.ts deleted file mode 100644 index 0dd585f..0000000 --- a/src/main/SlotValue.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { ComponentType } from 'react'; -import { NotFoundError } from './notFound'; -import { Route } from './Route'; -import { isPromiseLike } from './utils'; - -export interface RouteContent { - /** - * A matched route, or `undefined` if there's no route. - */ - route: Route | undefined; - - /** - * Params of the matched {@link route}, or `undefined` if there's no route. - */ - params: any; - - /** - * Data loaded by a {@link RouteOptions.loader}. - */ - data: any; - - /** - * An error that was thrown during rendering. - */ - error: unknown; - - /** - * `true` if an {@link error} was thrown during rendering. - */ - hasError: boolean; -} - -export interface SlotValueOptions { - /** - * A value that was rendered in a slot and is now replaced with a new value. - */ - oldValue?: SlotValue; - - /** - * A value that is propagated to a child slot. - */ - childValue?: SlotValue; - - /** - * A route that is rendered in a slot. - */ - route?: Route; - - /** - * Params required by a {@link RouteOptions.loader}, or `undefined` if there's no {@link route}. - */ - params?: object; - - /** - * A context required by a {@link RouteOptions.loader}, or `undefined` if there's no {@link route}. - */ - context?: unknown; - - /** - * A component that is rendered when an error was thrown during a slot rendering. - */ - errorComponent?: ComponentType; - - /** - * A component that is rendered when a {@link !Suspense} boundary is hit. - */ - loadingComponent?: ComponentType; - - /** - * A component that is rendered if {@link notFound} was called during rendering or of there's no route to render. - */ - notFoundComponent?: ComponentType; -} - -/** - * A value rendered by a {@link Slot}. - */ -export class SlotValue { - routeContent: RouteContent; - - /** - * A value that is propagated to a child slot. - */ - childValue: SlotValue | undefined; - - /** - * A promise that is thrown in a {@link !Suspense} body if value is being loaded. - */ - promise: Promise | undefined; - - /** - * A component rendered in a {@link !Suspense} body. - */ - component: ComponentType | undefined; - - /** - * A component rendered in a {@link !Suspense} fallback. - */ - fallbackComponent: ComponentType | undefined; - - protected _oldValue: SlotValue | undefined; - protected _childValue: SlotValue | undefined; - protected _route: Route | undefined; - protected _params: object | undefined; - protected _data: unknown; - protected _context: unknown; - protected _errorComponent: ComponentType | undefined; - protected _loadingComponent: ComponentType | undefined; - protected _notFoundComponent: ComponentType | undefined; - - constructor(options: SlotValueOptions) { - const { oldValue, route, params } = options; - - this.routeContent = { - route, - params, - data: undefined, - error: undefined, - hasError: false, - }; - - this.childValue = this.promise = undefined; - - this._oldValue = oldValue; - this._childValue = options.childValue; - this._route = route; - this._params = params; - this._context = options.context; - this._errorComponent = route?.errorComponent || options.errorComponent; - this._loadingComponent = route?.loadingComponent || options.loadingComponent; - this._notFoundComponent = route?.notFoundComponent || options.notFoundComponent; - - if (route === undefined) { - // Render not found - this.component = this._notFoundComponent; - this.fallbackComponent = this._loadingComponent; - } else if (oldValue === undefined || route.loadingAppearance === 'loading') { - // Render loading component - this.component = undefined; - this.fallbackComponent = this._loadingComponent; - } else { - // Render an old content until a new one is loaded - this.routeContent = oldValue.routeContent; - this.childValue = oldValue.childValue; - this.fallbackComponent = oldValue.component; - } - - // Freeze the old value to prevent UI changes - oldValue?.freeze(); - - this._load(); - } - - setError(error: unknown): void { - const value = this._oldValue || this; - - // Old value is already disposed - if (this.component === value._errorComponent) { - // Rethrow because an error was thrown inside error component - throw error; - } - - this.routeContent = { - route: value._route, - params: value._params, - data: value._data, - error, - hasError: true, - }; - - this.component = error instanceof NotFoundError ? value._notFoundComponent : value._errorComponent; - this.fallbackComponent = value._loadingComponent; - } - - protected _setPayload(component: ComponentType, data: unknown): void { - this.routeContent = { - route: this._route, - params: this._params, - data, - error: undefined, - hasError: false, - }; - - this.childValue = this._childValue; - this.component = component; - this.fallbackComponent = this._loadingComponent; - - // Dispose the old value - this._oldValue = undefined; - } - - protected _load(): void { - if (this._route === undefined) { - // No route to load data for - return; - } - - this.promise = undefined; - - let component; - let data; - - try { - component = this._route.getComponent(); - data = this._route.loader?.(this._params, this._context); - } catch (error) { - // Dispose the old value - this._oldValue = undefined; - this.setError(error); - return; - } - - if (!isPromiseLike(component) && !isPromiseLike(data)) { - this._setPayload(component, data); - return; - } - - const promise = Promise.all([component, data]).then( - ([component, data]) => { - if (promise === this.promise) { - this.promise = undefined; - this._setPayload(component, data); - } - }, - error => { - if (promise === this.promise) { - // Dispose the old value - this.promise = this._oldValue = undefined; - this.setError(error); - } - } - ); - - this.promise = promise; - } - - /** - * Prevents this value from changing. - */ - freeze(): void { - this.promise = undefined; - this.childValue?.freeze(); - } -} diff --git a/src/main/content-loaders.tsx b/src/main/content-loaders.tsx new file mode 100644 index 0000000..32d82f0 --- /dev/null +++ b/src/main/content-loaders.tsx @@ -0,0 +1,184 @@ +import React, { ComponentType, ReactElement } from 'react'; +import { RouteMatch } from './matchRoutes'; +import { RouteState } from './types'; +import { useInternalRouter } from './useInternalRouter'; +import { isPromiseLike } from './utils'; + +/** + * A content required to render a route. + */ +export interface RouteContent { + component: ComponentType | undefined; + data: unknown; + error: unknown; + hasError: boolean; +} + +export function loadRoutes(routeMatches: RouteMatch[], context: unknown): Array | RouteContent> { + return routeMatches.map(routeMatch => loadRoute(routeMatch, context)); +} + +/** + * Unconditionally loads route content. + */ +export function loadRoute(routeMatch: RouteMatch, context: unknown): Promise | RouteContent { + let component; + let data; + + try { + component = routeMatch.route.getComponent(); + + data = routeMatch.route.loader?.(routeMatch.params, context); + } catch (error) { + return createErrorContent(error); + } + + if (isPromiseLike(component) || isPromiseLike(data)) { + return Promise.all([component, data]).then(pair => createOkContent(pair[0], pair[1]), createErrorContent); + } + + return createOkContent(component, data); +} + +/** + * Called exclusively on the server. + * + * Loads route contents for server-rendered routes. + */ +export function loadServerRoutes( + routeMatches: RouteMatch[], + context: unknown +): Array | RouteContent> { + const contents = []; + + for (const routeMatch of routeMatches) { + if (routeMatch.route.renderingDisposition !== 'server') { + break; + } + contents.push(loadRoute(routeMatch, context)); + } + + return contents; +} + +/** + * Called exclusively on the client. + * + * Tries to hydrate routes rendered by a router during SSR, or loads routes unconditionally. + */ +export function hydrateRoutes( + routerId: string, + routeMatches: RouteMatch[], + context: unknown, + stateParser: (stateStr: string) => RouteState = JSON.parse +): Array | RouteContent> { + const stateStrs = window.__REACT_CORSAIR_SSR_STATE__?.[routerId]; + + if (!(stateStrs instanceof Map)) { + // No SSR state, already hydrated, or router didn't render any routes during SSR + return loadRoutes(routeMatches, context); + } + + const resolvers = new Map void>(); + + window.__REACT_CORSAIR_SSR_STATE__![routerId] = { + set(index, stateStr) { + const resolve = resolvers.get(index); + + if (resolve !== undefined) { + resolve(stateParser(stateStr)); + resolvers.delete(index); + } + }, + }; + + let isHydrated = true; + + return routeMatches.map((routeMatch, i) => { + if (!isHydrated || routeMatch.route.renderingDisposition !== 'server') { + // Client routes and their children are always loaded + isHydrated = false; + return loadRoute(routeMatch, context); + } + + let component; + let state; + + try { + component = routeMatch.route.getComponent(); + + state = stateStrs.has(i) + ? stateParser(stateStrs.get(i)) + : new Promise(resolve => resolvers.set(i, resolve)); + } catch (error) { + return createErrorContent(error); + } + + if (isPromiseLike(component) || isPromiseLike(state)) { + return Promise.all([component, state]).then(hydrateContent, createErrorContent); + } + + return hydrateContent([component, state]); + }); +} + +/** + * A script tag that must be rendered by a {@link Router} during SSR to initiate route hydration on the client. + */ +export function RouterHydrationScript(): ReactElement { + const { routerId, nonce } = useInternalRouter().props; + + return ( +