diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..654c6d4 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets). + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md). diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..a6e4a9d --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.3/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "restricted", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000..89592d4 --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,15 @@ +name: 'Setup pnpm and install dependencies' +description: 'Checks out code, sets up pnpm, Node.js, and runs pnpm install' +runs: + using: 'composite' + steps: + - uses: pnpm/action-setup@v4 + with: + version: '10' + - uses: actions/setup-node@v4 + with: + node-version: '24' + cache: 'pnpm' + - name: Install dependencies + shell: bash + run: pnpm i --frozen-lockfile diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..f8ecb2e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,31 @@ +# .github/workflows/build.yml +name: Build + +on: + workflow_call: + outputs: + build-status: + description: 'Build status' + value: ${{ jobs.build.outputs.status }} + +jobs: + build: + runs-on: ubuntu-latest + outputs: + status: ${{ job.status }} + steps: + - uses: actions/checkout@v3 + + - name: Setup environment + uses: ./.github/actions/setup + + - name: Build + run: pnpm build + + - name: Upload dist artifacts + uses: actions/upload-artifact@v4 + with: + name: build-output + path: | + packages/**/dist + if-no-files-found: error diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000..2f74622 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,55 @@ +name: Deploy docs + +on: + push: + paths: + - 'docs/**' + - 'packages/core/package.json' + branches: [main] + + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Setup environment + uses: ./.github/actions/setup + + - name: Install dependencies + working-directory: ./docs + run: pnpm i --frozen-lockfile + + - name: Build + working-directory: ./docs + run: pnpm run docs:build + + - name: Configure GitHub Pages + uses: actions/configure-pages@v5 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v4 + with: + path: docs/.vitepress/dist + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml new file mode 100644 index 0000000..f1cc01e --- /dev/null +++ b/.github/workflows/pr-checks.yml @@ -0,0 +1,57 @@ +name: PR Checks + +on: + pull_request: + types: [opened, synchronize] + +jobs: + build: + uses: ./.github/workflows/build.yml + + typecheck: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup environment + uses: ./.github/actions/setup + + - uses: actions/download-artifact@v4 + with: + name: build-output + path: ./packages + + - run: pnpm run typecheck + + lint: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup environment + uses: ./.github/actions/setup + + - uses: actions/download-artifact@v4 + with: + name: build-output + path: ./packages + + - run: pnpm run lint + + test: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup environment + uses: ./.github/actions/setup + + - uses: actions/download-artifact@v4 + with: + name: build-output + path: ./packages + + - run: pnpm run test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..5ca5bc3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,39 @@ +name: Release version on npm + +on: workflow_dispatch + +permissions: + id-token: write + pull-requests: write + contents: write + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup environment + uses: ./.github/actions/setup + + - name: Install Dependencies + run: pnpm i --frozen-lockfile + + - name: Building libraries + run: pnpm build + + - name: Typecheck + run: pnpm run typecheck + + - name: Running tests workflow + run: pnpm run test + + - name: Create Pull request & Publish to npm + id: changesets + uses: changesets/action@v1 + with: + publish: pnpm release diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..80a690c --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +cache +dist-ssr +*.local +.husky + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +packages/playground \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..a1a872d --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,4 @@ +export default { + singleQuote: true, + trailingComma: 'all', +}; diff --git a/LICENSE.MD b/LICENSE.MD new file mode 100644 index 0000000..12381b5 --- /dev/null +++ b/LICENSE.MD @@ -0,0 +1,20 @@ +MIT License + +Copyright (c) 2024 Edward Gigolaev & Sergey Sova & Anton Kosykh + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..68a0610 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# ☄️ effector/router diff --git a/decisions/core/async-bundles.md b/decisions/core/async-bundles.md deleted file mode 100644 index e7836d0..0000000 --- a/decisions/core/async-bundles.md +++ /dev/null @@ -1,11 +0,0 @@ -# Atomic router approach - -Atomic router doesn't have mechanisms for creating lazy-loaded routes. - -# Argon router approach - -In argon router any route has its own set of beforeOpen rules and the setAsyncImport function, which is used in createLazyView, which allows you to specify which bundle should be loaded before opening the route. - -# Conclusion - -Merge argon-router approach, but now `createLazyView` adds an effect in beforeOpen instead of calling route's `setAsyncImport`. \ No newline at end of file diff --git a/decisions/core/grouping.md b/decisions/core/grouping.md deleted file mode 100644 index b6dab02..0000000 --- a/decisions/core/grouping.md +++ /dev/null @@ -1,27 +0,0 @@ -# Atomic router approach - -— - -# Argon router approach - -```ts -import { group, createRoute } from '@argon-router/core'; -import { createEvent, createEffect } from 'effector'; - -const signInRoute = createRoute({ path: '/auth/sign-in' }); -const signUpRoute = createRoute({ path: '/auth/sign-up' }); -const authorizationRoute = group([signInRoute, signUpRoute]); - -signInRoute.open(); // authorizationRoute.$isOpened —> true -signUpRoute.open(); // authorizationRoute.$isOpened —> true -signInRoute.close(); // authorizationRoute.$isOpened —> true -signUpRoute.close(); // authorizationRoute.$isOpened —> false -``` - -# Note - -Route grouping is important feature, with which you can create gorgeous animated interfaces. - -# Conclusion - -Merge argon-router approach without changes. \ No newline at end of file diff --git a/decisions/core/history-adapters.md b/decisions/core/history-adapters.md deleted file mode 100644 index f5c25b7..0000000 --- a/decisions/core/history-adapters.md +++ /dev/null @@ -1,30 +0,0 @@ -# Atomic router approach - -— - -# Argon router approach - -— - -# Note - -Both of routers works only with memory history & browser history objects. - -# Conclusion - -We need to add support for built-in and custom adapters. This may be useful for query-string routing and other cases, where -memory history is not good choice for builing front-end application. - -```ts -const router = createRouter(); - -router.setHistory(new BrowserHistoryAdapter(history)); - -// or - -router.setHistory(new MemoryHistoryAdapter(history)); - -// or - -router.setHistory(new CustomHistoryAdapter(history)); -``` \ No newline at end of file diff --git a/decisions/core/path-nesting.md b/decisions/core/path-nesting.md deleted file mode 100644 index f6e2c2b..0000000 --- a/decisions/core/path-nesting.md +++ /dev/null @@ -1,18 +0,0 @@ -# Atomic router approach - -— - -# Argon router approach - -```ts -const dashboardRoute = createRoute({ path: '/dashboard', beforeOpen: [adminGuardFx] }); -const statsRoute = createRoute({ parent: dashboardRoute, path: '/stats' }); // path —> /dashboard/stats, beforeOpen -> [adminGuardFx] -``` - -# Note - -Path nesting is powerful feature which allows you to don't duplicate route pre-validation logic & nest paths - -# Conclusion - -Merge argon-router approach without changes. \ No newline at end of file diff --git a/decisions/core/paths.md b/decisions/core/paths.md deleted file mode 100644 index 12eb312..0000000 --- a/decisions/core/paths.md +++ /dev/null @@ -1,16 +0,0 @@ -# Atomic router approach - -`path-to-regexp` library. - -# Argon router approach - -`@argon-router/paths` library. - -# Note - -Argon router have more powerful instruments to describe paths (like generics, arguments count and other features), but this -may cause errors in SSR with express/hono/other back-end frameworks - -# Conclusion - -Merge argon-router approach, but make paths converter from `@argon-router/paths` to back-end frameworks format. \ No newline at end of file diff --git a/decisions/core/queries.md b/decisions/core/queries.md deleted file mode 100644 index 4d042c4..0000000 --- a/decisions/core/queries.md +++ /dev/null @@ -1,16 +0,0 @@ -# Atomic router approach - -`router.$query` & `querySync` - -# Argon router approach - -`router.$query` & `router.trackQuery` - -# Note - -Argon router's `trackQuery` has more possibilities to validate, match, write and read query parameters instead of atomic router's `querySync`. -Also argon-router has `router.$query` for straight writing & reading query parameters. - -# Conclusion - -Merge argon-router approach without changes. \ No newline at end of file diff --git a/decisions/core/redirects.md b/decisions/core/redirects.md deleted file mode 100644 index 22428f5..0000000 --- a/decisions/core/redirects.md +++ /dev/null @@ -1,16 +0,0 @@ -# Atomic router approach - -```ts -redirect({ - clock: someEvent, - route: someRoute, -}); -``` - -# Argon router approach - -— - -# Conclusion - -Merge atomic-router approach without changes. \ No newline at end of file diff --git a/decisions/core/route-chaining.md b/decisions/core/route-chaining.md deleted file mode 100644 index e1f5669..0000000 --- a/decisions/core/route-chaining.md +++ /dev/null @@ -1,31 +0,0 @@ -# Atomic & Argon router approach - -```ts -// route define,,, - -const authorized = createEvent(); -const rejected = createEvent(); - -const checkAuthorizationFx = createEffect(async ({ params }) => /* some logic */); - -sample({ - clock: checkAuthorizationFx.doneData, - target: authorized, -}); - -sample({ - clock: checkAuthorizationFx.failData, - target: rejected, -}); - -const virtual = chainRoute({ - route, - beforeOpen: checkAuthorizationFx, - openOn: authorized, - cancelOn: rejected, -}); -``` - -# Conclusion - -Merge without changes. \ No newline at end of file diff --git a/decisions/core/route-validations.md b/decisions/core/route-validations.md deleted file mode 100644 index 8f69e52..0000000 --- a/decisions/core/route-validations.md +++ /dev/null @@ -1,13 +0,0 @@ -# Atomic router approach - -Atomic router only have chained routes for post-routing validation - -# Argon router approach - -```ts -const route = createRoute({ beforeOpen: [authGuardFx], path: '/route' }); -``` - -# Conclusion - -Merge argon-router approach without changes. \ No newline at end of file diff --git a/decisions/core/router-controls.md b/decisions/core/router-controls.md deleted file mode 100644 index bb64e74..0000000 --- a/decisions/core/router-controls.md +++ /dev/null @@ -1,9 +0,0 @@ -# Atomic & Argon router approach - -```ts -const controls = createRouterControls(); -``` - -# Conclusion - -Merge without changes. \ No newline at end of file diff --git a/decisions/core/routers.md b/decisions/core/routers.md deleted file mode 100644 index 5540ad2..0000000 --- a/decisions/core/routers.md +++ /dev/null @@ -1,39 +0,0 @@ -# Atomic router approach - -```ts -const userRoute = createRoute(); - -const router = createRouter({ - routes: [ - { path: '/user/:id', route: userRoute }, - ], -}); -``` - -# Argon router approach - -```ts -const userRoute = createRoute({ path: '/user/:id' }); - -const router = createRouter({ - routes: [userRoute], -}); -``` - -# Note - -When choosing an approach for [/routes](declaring routes), we came to the conclusion that we need to support two types of routes: those that are not bound to a path, and those that are immediately associated with a path. This decision shapes the architecture of the router configuration itself. In the new router, we decided to adopt a flexible method: you can either specify a path for a route (if it is not already path-based), or simply pass a path-based route as is. - -# Conclusion - -```ts -const userRoute = createRoute(); -const usersRoute = createRoute({ path: '/users' }); - -const router = createRouter({ - routes: [ - { path: '/user/:id', route: userRoute }, - usersRoute, - ], -}); -``` \ No newline at end of file diff --git a/decisions/core/routes.md b/decisions/core/routes.md deleted file mode 100644 index 9cb8e1f..0000000 --- a/decisions/core/routes.md +++ /dev/null @@ -1,22 +0,0 @@ -# Atomic router approach - -```ts -const usersRoute = createRoute(); -``` - -# Argon router approach - -```ts -const usersRoute = createRoute({ path: '/users' }); -``` - -# Note - -Both routers (atomic-router and argon-router) declare an approach where each route is a separate entity with a set of parameters. The key difference is that in atomic-router, the path is not bound to the route itself but is specified in the router configuration, whereas argon-router establishes a strict route-path association at the moment of route declaration, which helps infer types from the path during the creation of the route. At the same time, argon-router has a separate abstract entity called VirtualRoute, which is similar to a regular route in atomic-router. Both approaches have their nuances, so the chosen solution was to combine both methods. This way, you can either declare a route and then assign a path to it in the router, or immediately create a path-based route. Such an approach offers significantly greater flexibility and allows you to change declarations quickly, regardless of the initial decision made, covering both the majority of simple use cases as well as especially complex ones, without imposing a specific ideology and giving you the freedom to organize your application's architecture. - -# Conclusion - -```ts -const userRoute = createRoute(); // Route -const usersRoute = createRoute({ path: '/users' }); // PathRoute -``` \ No newline at end of file diff --git a/decisions/react/helpers.md b/decisions/react/helpers.md deleted file mode 100644 index 7f00de8..0000000 --- a/decisions/react/helpers.md +++ /dev/null @@ -1,25 +0,0 @@ -# Atomic router approach - -`useLink` — builds route path -`useIsOpened` — returns if route opened or not - -# Argon router approach - -`withLayout` — set passed layout for routes -`useRouter` — get router `@@unitShape` api in UI components -`useRouterContext` — get router object in UI components - -# Note - -In both routers approach we don't have helper `useRoute` (for migration from hooks-oriented code). - -# Conclusion - -Merge both routers approarch with `useRoute` helper. - -`useLink` — builds route path -`useIsOpened` — returns if route opened or not -`withLayout` — set passed layout for routes -`useRouter` — get router `@@unitShape` api in UI components -`useRouterContext` — get router object in UI components -`useRoute` — get route `@@unitShape` api in UI components \ No newline at end of file diff --git a/decisions/react/links.md b/decisions/react/links.md deleted file mode 100644 index 3debe3b..0000000 --- a/decisions/react/links.md +++ /dev/null @@ -1,9 +0,0 @@ -# Atomic & Argon routers approach - - - Some link - - -# Conclusion - -Merge without changes. \ No newline at end of file diff --git a/decisions/react/views.md b/decisions/react/views.md deleted file mode 100644 index 357f795..0000000 --- a/decisions/react/views.md +++ /dev/null @@ -1,15 +0,0 @@ -# Atomic router approach - -`createRouteView` & `createRoutesView` - -# Argon router approach - -`createRouteView`, `createLazyRouteView` & `createRoutesView` - -# Note - -For async-bundled routes we need to use built-in `createLazyRouteView` function. - -# Conclusion - -Merge argon-router approach without changes. \ No newline at end of file diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts new file mode 100644 index 0000000..22acf78 --- /dev/null +++ b/docs/.vitepress/config.mts @@ -0,0 +1,124 @@ +import { defineConfig } from 'vitepress'; + +import path from 'path'; +import fs from 'fs'; + +const { version } = JSON.parse( + fs.readFileSync(path.resolve(__dirname, '../../packages/core/package.json'), { + encoding: 'utf-8', + }), +); + +export default defineConfig({ + title: 'effector router', + description: 'effector router documentation', + base: '/', + head: [['link', { rel: 'icon', href: '/favicon.ico' }]], + themeConfig: { + logo: './logo.svg', + nav: [ + { text: 'Home', link: '/' }, + { text: 'Introduction', link: '/introduction/getting-started' }, + { + text: `v${version}`, + items: [ + { + items: [ + { + text: `v${version}`, + link: `#`, + }, + { + text: 'Releases Notes', + link: 'https://github.com/effector/router/blob/main/CHANGELOG.md', + }, + ], + }, + ], + }, + ], + + sidebar: [ + { + text: 'Introduction', + items: [ + { text: 'Getting started', link: '/introduction/getting-started' }, + ], + }, + { + text: 'Core', + collapsed: false, + items: [ + { text: 'Overview', link: '/core/' }, + { text: 'createRoute', link: '/core/create-route' }, + { text: 'createRouter', link: '/core/create-router' }, + { + text: 'createRouterControls', + link: '/core/create-router-controls', + }, + { text: 'Adapters', link: '/core/adapters' }, + { text: 'trackQuery', link: '/core/track-query' }, + { text: 'chainRoute', link: '/core/chain-route' }, + { text: 'group', link: '/core/group' }, + { text: 'createVirtualRoute', link: '/core/create-virtual-route' }, + ], + }, + { + text: 'Paths', + collapsed: false, + items: [{ text: 'Overview', link: '/paths/' }], + }, + { + text: 'React', + collapsed: false, + items: [ + { text: 'Overview', link: '/react/' }, + { text: 'RouterProvider', link: '/react/router-provider' }, + { text: 'createRouteView', link: '/react/create-route-view' }, + { + text: 'createLazyRouteView', + link: '/react/create-lazy-route-view', + }, + { text: 'createRoutesView', link: '/react/create-routes-view' }, + { text: 'Link', link: '/react/link' }, + { text: 'Outlet', link: '/react/outlet' }, + { text: 'useRouter', link: '/react/use-router' }, + { text: 'useLink', link: '/react/use-link' }, + { text: 'useIsOpened', link: '/react/use-is-opened' }, + { text: 'useOpenedViews', link: '/react/use-opened-views' }, + { text: 'withLayout', link: '/react/with-layout' }, + ], + }, + { + text: 'React Native β', + collapsed: false, + items: [ + { text: 'Overview', link: '/react-native/' }, + { + text: 'Stack Navigator', + link: '/react-native/stack-navigator', + }, + { + text: 'Bottom Tabs Navigator', + link: '/react-native/bottom-tabs-navigator', + }, + ], + }, + ], + + footer: { + message: 'Released under the MIT License.', + copyright: 'Copyright © 2025-PRESENT movpushmov', + }, + + socialLinks: [ + { + icon: { + svg: '', + }, + link: 'https://t.me/effector_ru', + }, + { icon: 'github', link: 'https://github.com/effector/router' }, + ], + }, +}); diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts new file mode 100644 index 0000000..0763655 --- /dev/null +++ b/docs/.vitepress/theme/index.ts @@ -0,0 +1,14 @@ +// https://vitepress.dev/guide/custom-theme +import { h } from 'vue'; +import type { Theme } from 'vitepress'; +import DefaultTheme from 'vitepress/theme'; +import './style.css'; +import 'uno.css'; + +export default { + extends: DefaultTheme, + Layout: () => { + return h(DefaultTheme.Layout, null, {}); + }, + enhanceApp() {}, +} satisfies Theme; diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css new file mode 100644 index 0000000..1bd56ff --- /dev/null +++ b/docs/.vitepress/theme/style.css @@ -0,0 +1,138 @@ +/** + * Customize default theme styling by overriding CSS variables: + * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css + */ + +/** + * Colors + * + * Each colors have exact same color scale system with 3 levels of solid + * colors with different brightness, and 1 soft color. + * + * - `XXX-1`: The most solid color used mainly for colored text. It must + * satisfy the contrast ratio against when used on top of `XXX-soft`. + * + * - `XXX-2`: The color used mainly for hover state of the button. + * + * - `XXX-3`: The color for solid background, such as bg color of the button. + * It must satisfy the contrast ratio with pure white (#ffffff) text on + * top of it. + * + * - `XXX-soft`: The color used for subtle background such as custom container + * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors + * on top of it. + * + * The soft color must be semi transparent alpha channel. This is crucial + * because it allows adding multiple "soft" colors on top of each other + * to create a accent, such as when having inline code block inside + * custom containers. + * + * - `default`: The color used purely for subtle indication without any + * special meanings attched to it such as bg color for menu hover state. + * + * - `brand`: Used for primary brand colors, such as link text, button with + * brand theme, etc. + * + * - `tip`: Used to indicate useful information. The default theme uses the + * brand color for this by default. + * + * - `warning`: Used to indicate warning to the users. Used in custom + * container, badges, etc. + * + * - `danger`: Used to show error, or dangerous message to the users. Used + * in custom container, badges, etc. + * -------------------------------------------------------------------------- */ + +:root { + --vp-c-default-1: var(--vp-c-gray-1); + --vp-c-default-2: var(--vp-c-gray-2); + --vp-c-default-3: var(--vp-c-gray-3); + --vp-c-default-soft: var(--vp-c-gray-soft); + + --vp-c-brand-1: var(--vp-c-indigo-1); + --vp-c-brand-2: var(--vp-c-indigo-2); + --vp-c-brand-3: var(--vp-c-indigo-3); + --vp-c-brand-soft: var(--vp-c-indigo-soft); + + --vp-c-tip-1: var(--vp-c-brand-1); + --vp-c-tip-2: var(--vp-c-brand-2); + --vp-c-tip-3: var(--vp-c-brand-3); + --vp-c-tip-soft: var(--vp-c-brand-soft); + + --vp-c-warning-1: var(--vp-c-yellow-1); + --vp-c-warning-2: var(--vp-c-yellow-2); + --vp-c-warning-3: var(--vp-c-yellow-3); + --vp-c-warning-soft: var(--vp-c-yellow-soft); + + --vp-c-danger-1: var(--vp-c-red-1); + --vp-c-danger-2: var(--vp-c-red-2); + --vp-c-danger-3: var(--vp-c-red-3); + --vp-c-danger-soft: var(--vp-c-red-soft); +} + +/** + * Component: Button + * -------------------------------------------------------------------------- */ + +:root { + --vp-button-brand-border: transparent; + --vp-button-brand-text: var(--vp-c-white); + --vp-button-brand-bg: var(--vp-c-brand-3); + --vp-button-brand-hover-border: transparent; + --vp-button-brand-hover-text: var(--vp-c-white); + --vp-button-brand-hover-bg: var(--vp-c-brand-2); + --vp-button-brand-active-border: transparent; + --vp-button-brand-active-text: var(--vp-c-white); + --vp-button-brand-active-bg: var(--vp-c-brand-1); +} + +/** + * Component: Home + * -------------------------------------------------------------------------- */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #bd34fe 30%, + #41d1ff + ); + + --vp-home-hero-image-background-image: linear-gradient( + -45deg, + #bd34fe 50%, + #47caff 50% + ); + --vp-home-hero-image-filter: blur(44px); +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(68px); + } +} + +/** + * Component: Custom Block + * -------------------------------------------------------------------------- */ + +:root { + --vp-custom-block-tip-border: transparent; + --vp-custom-block-tip-text: var(--vp-c-text-1); + --vp-custom-block-tip-bg: var(--vp-c-brand-soft); + --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); +} + +/** + * Component: Algolia + * -------------------------------------------------------------------------- */ + +.DocSearch { + --docsearch-primary-color: var(--vp-c-brand-1) !important; +} diff --git a/docs/core/adapters.md b/docs/core/adapters.md new file mode 100644 index 0000000..ee5c055 --- /dev/null +++ b/docs/core/adapters.md @@ -0,0 +1,760 @@ +# Adapters + +Adapters bridge effector/router with browser history APIs, enabling URL synchronization and navigation. effector/router includes two built-in adapters and supports custom adapter creation. + +## Overview + +An adapter translates between effector/router's internal navigation system and external history management libraries (like the `history` package). Adapters handle: + +- Reading and updating URL location +- Managing browser history stack +- Listening to navigation events +- Providing back/forward navigation + +## Built-in Adapters + +### historyAdapter + +Standard adapter for pathname-based navigation using the [`history`](https://github.com/remix-run/history) library. + +**Use case:** Traditional web navigation where routes are in the URL pathname. + +**Installation:** + +```bash +npm install history +``` + +**Basic Usage:** + +```ts +import { createRouter, historyAdapter } from '@effector/router'; +import { createBrowserHistory } from 'history'; + +const router = createRouter({ + routes: [homeRoute, aboutRoute], +}); + +const history = createBrowserHistory(); +router.setHistory(historyAdapter(history)); + +// Navigation changes URL pathname +aboutRoute.open(); +// URL: /about +``` + +**With Effector Scope:** + +```ts +import { allSettled, fork } from 'effector'; +import { createBrowserHistory } from 'history'; +import { historyAdapter } from '@effector/router'; + +const scope = fork(); +const history = createBrowserHistory(); + +await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), +}); +``` + +**History Types:** + +```ts +// Browser History - Full URLs +import { createBrowserHistory } from 'history'; +const history = createBrowserHistory(); +// URL: http://localhost:3000/about + +// Hash History - Static hosting +import { createHashHistory } from 'history'; +const history = createHashHistory(); +// URL: http://localhost:3000/#/about + +// Memory History - Testing, SSR, React Native +import { createMemoryHistory } from 'history'; +const history = createMemoryHistory({ + initialEntries: ['/'], + initialIndex: 0, +}); +// No URL changes, all in memory +``` + +**React Application Example:** + +```ts +import { createRoot } from 'react-dom/client'; +import { Provider } from 'effector-react'; +import { allSettled, fork } from 'effector'; +import { createBrowserHistory } from 'history'; +import { historyAdapter } from '@effector/router'; + +async function render() { + const scope = fork(); + const history = createBrowserHistory(); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + createRoot(document.getElementById('root')!).render( + + + + + , + ); +} + +render(); +``` + +### queryAdapter + +Specialized adapter that stores navigation state in URL query parameters instead of the pathname. + +**Use case:** Modal routing, tabs, embedded apps, or secondary navigation where the main URL should remain constant. + +**Basic Usage:** + +```ts +import { createRouter, queryAdapter } from '@effector/router'; +import { createBrowserHistory } from 'history'; + +const router = createRouter({ + routes: [settingsModal, profileModal], +}); + +const history = createBrowserHistory(); +router.setHistory(queryAdapter(history)); + +// Navigation changes query params, not pathname +settingsModal.open(); +// URL: /app?page=/settings +``` + +**Comparison:** + +| Feature | historyAdapter | queryAdapter | +| ------------ | --------------- | --------------------- | +| URL Location | Pathname | Query parameters | +| Example URL | `/user/123` | `/app?page=/user/123` | +| Use Case | Main navigation | Modal/tab navigation | +| SEO | ✅ Good | ⚠️ Limited | + +**Modal Routing Example:** + +```ts +// Main router (pathname) +const mainRouter = createRouter({ + routes: [homeRoute, aboutRoute], +}); +mainRouter.setHistory(historyAdapter(createBrowserHistory())); + +// Modal router (query params) +const modalRouter = createRouter({ + routes: [loginModal, settingsModal], +}); +modalRouter.setHistory(queryAdapter(createBrowserHistory())); + +// Navigate main route +aboutRoute.open(); +// URL: /about + +// Open modal +loginModal.open(); +// URL: /about?modal=/login + +// Main route stays /about while modal changes +``` + +**Tab Navigation Example:** + +```ts +const tabRouter = createRouter({ + routes: [overviewTab, analyticsTab, settingsTab], +}); + +tabRouter.setHistory(queryAdapter(createBrowserHistory())); + +// Switch tabs +overviewTab.open(); +// URL: /app?tab=/overview + +analyticsTab.open(); +// URL: /app?tab=/analytics + +// Back button works! +history.back(); +// URL: /app?tab=/overview +``` + +## Custom Adapters + +Create custom adapters to integrate with any navigation system. + +### Adapter Interface + +```typescript +interface RouterAdapter { + location: RouterLocation; + push: (to: To) => void; + replace: (to: To) => void; + goBack: () => void; + goForward: () => void; + listen: (callback: (location: RouterLocation) => void) => Subscription; +} + +interface RouterLocation { + pathname: string; + search: string; + hash: string; +} + +type To = string | Partial; +``` + +### Creating a Custom Adapter + +**Example 1: Console Logger Adapter** + +```ts +import type { RouterAdapter } from '@effector/router'; + +function consoleAdapter(): RouterAdapter { + let currentLocation = { + pathname: '/', + search: '', + hash: '', + }; + + const listeners = new Set<(location: RouterLocation) => void>(); + + const notify = () => { + listeners.forEach((listener) => listener(currentLocation)); + }; + + return { + location: currentLocation, + + push: (to) => { + if (typeof to === 'string') { + currentLocation = { pathname: to, search: '', hash: '' }; + } else { + currentLocation = { + pathname: to.pathname ?? currentLocation.pathname, + search: to.search ?? currentLocation.search, + hash: to.hash ?? currentLocation.hash, + }; + } + console.log('Navigate to:', currentLocation); + notify(); + }, + + replace: (to) => { + if (typeof to === 'string') { + currentLocation = { pathname: to, search: '', hash: '' }; + } else { + currentLocation = { + pathname: to.pathname ?? currentLocation.pathname, + search: to.search ?? currentLocation.search, + hash: to.hash ?? currentLocation.hash, + }; + } + console.log('Replace with:', currentLocation); + notify(); + }, + + goBack: () => { + console.log('Go back'); + }, + + goForward: () => { + console.log('Go forward'); + }, + + listen: (callback) => { + listeners.add(callback); + return { + unsubscribe: () => { + listeners.delete(callback); + }, + }; + }, + }; +} + +// Use it +router.setHistory(consoleAdapter()); +``` + +**Example 2: Local Storage Adapter** + +```ts +function localStorageAdapter(): RouterAdapter { + const STORAGE_KEY = 'router-location'; + + const getLocation = (): RouterLocation => { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored) { + return JSON.parse(stored); + } + return { pathname: '/', search: '', hash: '' }; + }; + + const setLocation = (location: RouterLocation) => { + localStorage.setItem(STORAGE_KEY, JSON.stringify(location)); + }; + + let currentLocation = getLocation(); + const listeners = new Set<(location: RouterLocation) => void>(); + + const updateLocation = (to: To) => { + if (typeof to === 'string') { + currentLocation = { pathname: to, search: '', hash: '' }; + } else { + currentLocation = { + pathname: to.pathname ?? currentLocation.pathname, + search: to.search ?? currentLocation.search, + hash: to.hash ?? currentLocation.hash, + }; + } + setLocation(currentLocation); + listeners.forEach((listener) => listener(currentLocation)); + }; + + return { + location: currentLocation, + push: updateLocation, + replace: updateLocation, + goBack: () => console.log('Back not supported'), + goForward: () => console.log('Forward not supported'), + listen: (callback) => { + listeners.add(callback); + return { unsubscribe: () => listeners.delete(callback) }; + }, + }; +} +``` + +**Example 3: React Native Adapter** + +```ts +import { Linking } from 'react-native'; + +function reactNativeAdapter(): RouterAdapter { + let currentLocation: RouterLocation = { + pathname: '/', + search: '', + hash: '', + }; + + const listeners = new Set<(location: RouterLocation) => void>(); + + // Parse deep link URL + const parseUrl = (url: string): RouterLocation => { + try { + const parsed = new URL(url); + return { + pathname: parsed.pathname, + search: parsed.search, + hash: parsed.hash, + }; + } catch { + return { pathname: url, search: '', hash: '' }; + } + }; + + // Initialize with current URL + Linking.getInitialURL().then((url) => { + if (url) { + currentLocation = parseUrl(url); + } + }); + + // Listen to deep links + const subscription = Linking.addEventListener('url', ({ url }) => { + currentLocation = parseUrl(url); + listeners.forEach((listener) => listener(currentLocation)); + }); + + return { + location: currentLocation, + + push: (to) => { + const newLocation = + typeof to === 'string' + ? parseUrl(to) + : { + pathname: to.pathname ?? currentLocation.pathname, + search: to.search ?? currentLocation.search, + hash: to.hash ?? currentLocation.hash, + }; + + currentLocation = newLocation; + + // Update React Native navigation + const url = `myapp://${newLocation.pathname}${newLocation.search}${newLocation.hash}`; + Linking.openURL(url); + + listeners.forEach((listener) => listener(currentLocation)); + }, + + replace: (to) => { + // Same as push for React Native + this.push(to); + }, + + goBack: () => { + // Handle via React Navigation or custom logic + }, + + goForward: () => { + // Not typically supported in mobile + }, + + listen: (callback) => { + listeners.add(callback); + return { + unsubscribe: () => { + listeners.delete(callback); + subscription.remove(); + }, + }; + }, + }; +} +``` + +**Example 4: Electron IPC Adapter** + +```ts +import { ipcRenderer } from 'electron'; + +function electronAdapter(): RouterAdapter { + let currentLocation: RouterLocation = { + pathname: '/', + search: '', + hash: '', + }; + + const listeners = new Set<(location: RouterLocation) => void>(); + + // Listen to navigation from main process + ipcRenderer.on('navigate', (_, location: RouterLocation) => { + currentLocation = location; + listeners.forEach((listener) => listener(currentLocation)); + }); + + return { + location: currentLocation, + + push: (to) => { + const newLocation = + typeof to === 'string' + ? { pathname: to, search: '', hash: '' } + : { + pathname: to.pathname ?? currentLocation.pathname, + search: to.search ?? currentLocation.search, + hash: to.hash ?? currentLocation.hash, + }; + + currentLocation = newLocation; + + // Send to main process + ipcRenderer.send('router-navigate', newLocation); + + listeners.forEach((listener) => listener(currentLocation)); + }, + + replace: (to) => { + // Same as push for Electron + this.push(to); + }, + + goBack: () => { + ipcRenderer.send('router-back'); + }, + + goForward: () => { + ipcRenderer.send('router-forward'); + }, + + listen: (callback) => { + listeners.add(callback); + return { + unsubscribe: () => { + listeners.delete(callback); + }, + }; + }, + }; +} +``` + +## Adapter Requirements + +When creating a custom adapter, ensure: + +### 1. Initial Location + +Provide initial location when created: + +```ts +return { + location: { + pathname: '/', + search: '', + hash: '', + }, + // ... +}; +``` + +### 2. Handle String and Object Navigation + +Support both formats: + +```ts +push: (to) => { + if (typeof to === 'string') { + // Handle string: '/about' + navigate({ pathname: to, search: '', hash: '' }); + } else { + // Handle object: { pathname: '/about', search: '?id=1' } + navigate({ + pathname: to.pathname ?? current.pathname, + search: to.search ?? current.search, + hash: to.hash ?? current.hash, + }); + } +}; +``` + +### 3. Notify Listeners + +Call all listeners when location changes: + +```ts +const listeners = new Set<(location: RouterLocation) => void>(); + +const notify = () => { + listeners.forEach((listener) => listener(currentLocation)); +}; + +// After navigation +push: (to) => { + // ... update location + notify(); +}; +``` + +### 4. Return Unsubscribe Function + +The `listen` method must return an object with `unsubscribe`: + +```ts +listen: (callback) => { + listeners.add(callback); + + return { + unsubscribe: () => { + listeners.delete(callback); + // Cleanup any resources + }, + }; +}; +``` + +### 5. Maintain Location State + +Keep `location` property synchronized: + +```ts +const adapter = { + location: currentLocation, // Always current + + push: (to) => { + currentLocation = newLocation; + this.location = currentLocation; // Update reference + notify(); + }, +}; +``` + +## Testing Adapters + +### Test with Memory History + +```ts +import { createMemoryHistory } from 'history'; +import { historyAdapter } from '@effector/router'; +import { allSettled, fork } from 'effector'; + +test('navigation works', async () => { + const scope = fork(); + const history = createMemoryHistory({ initialEntries: ['/'] }); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + await allSettled(aboutRoute.open, { scope }); + + expect(history.location.pathname).toBe('/about'); + expect(scope.getState(aboutRoute.$isOpened)).toBe(true); +}); +``` + +### Test Custom Adapter + +```ts +test('custom adapter', async () => { + const locations: RouterLocation[] = []; + + const mockAdapter: RouterAdapter = { + location: { pathname: '/', search: '', hash: '' }, + push: (to) => { + const location = + typeof to === 'string' + ? { pathname: to, search: '', hash: '' } + : { pathname: to.pathname ?? '/', search: '', hash: '' }; + locations.push(location); + }, + replace: vi.fn(), + goBack: vi.fn(), + goForward: vi.fn(), + listen: () => ({ unsubscribe: () => {} }), + }; + + const scope = fork(); + await allSettled(router.setHistory, { scope, params: mockAdapter }); + + await allSettled(aboutRoute.open, { scope }); + + expect(locations).toContainEqual({ + pathname: '/about', + search: '', + hash: '', + }); +}); +``` + +## Best Practices + +### Use Built-in Adapters When Possible + +```ts +// ✅ Recommended for web apps +router.setHistory(historyAdapter(createBrowserHistory())); + +// ✅ Recommended for modals/tabs +modalRouter.setHistory(queryAdapter(createBrowserHistory())); + +// ⚠️ Only create custom adapters when necessary +router.setHistory(customAdapter()); +``` + +### Initialize Early + +Set adapter before any navigation: + +```ts +// ✅ Good +await allSettled(router.setHistory, { scope, params: adapter }); +await allSettled(homeRoute.open, { scope }); + +// ❌ Bad +await allSettled(homeRoute.open, { scope }); +await allSettled(router.setHistory, { scope, params: adapter }); +``` + +### Single Adapter Instance + +Create only one adapter per router: + +```ts +// ✅ Good +const adapter = historyAdapter(createBrowserHistory()); +router.setHistory(adapter); + +// ❌ Bad +router.setHistory(historyAdapter(createBrowserHistory())); +router.setHistory(historyAdapter(createBrowserHistory())); // Different instance +``` + +### Clean Up Resources + +Ensure proper cleanup in custom adapters: + +```ts +listen: (callback) => { + listeners.add(callback); + + // Setup subscriptions + const subscription = externalLibrary.subscribe(callback); + + return { + unsubscribe: () => { + listeners.delete(callback); + subscription.unsubscribe(); // ✅ Cleanup + }, + }; +}; +``` + +## API Reference + +### `historyAdapter(history: History): RouterAdapter` + +Creates a standard pathname-based adapter. + +**Parameters:** + +- `history: History` - History instance from `history` package + +**Returns:** `RouterAdapter` + +### `queryAdapter(history: History): RouterAdapter` + +Creates a query parameter-based adapter. + +**Parameters:** + +- `history: History` - History instance from `history` package + +**Returns:** `RouterAdapter` + +### Types + +```typescript +interface RouterAdapter { + location: RouterLocation; + push: (to: To) => void; + replace: (to: To) => void; + goBack: () => void; + goForward: () => void; + listen: (callback: (location: RouterLocation) => void) => Subscription; +} + +interface RouterLocation { + pathname: string; + search: string; + hash: string; +} + +type To = string | Partial; + +interface Subscription { + unsubscribe: () => void; +} +``` + +## See Also + +- [createRouter](/core/create-router) - Create a router with adapters +- [createRouterControls](/core/create-router-controls) - Create navigation controls +- [trackQuery](/core/track-query) - Track query parameters diff --git a/docs/core/chain-route.md b/docs/core/chain-route.md new file mode 100644 index 0000000..79c9424 --- /dev/null +++ b/docs/core/chain-route.md @@ -0,0 +1,308 @@ +# chainRoute + +Create a virtual route that wraps an existing route with additional lifecycle hooks and conditions. Useful for implementing guards, loading data before navigation, or creating conditional navigation flows. + +## API + +```typescript +function chainRoute( + props: ChainRouteProps, +): VirtualRoute, T>; +``` + +### Parameters + +| Parameter | Type | Description | +| ------------------ | -------------------------- | ------------------------------------------------------------------- | +| `props.route` | `Route` | The route to wrap | +| `props.beforeOpen` | `Event \| Effect \| Array` | Unit(s) to execute when route opens | +| `props.openOn` | `Unit \| Unit[]` | Optional. Unit(s) that trigger the virtual route to open | +| `props.cancelOn` | `Unit \| Unit[]` | Optional. Unit(s) that close the virtual route and fire `cancelled` | + +### Returns + +`VirtualRoute, T>` - A virtual route with all standard virtual route properties plus a `cancelled` event. + +## Usage + +### Basic Authorization Guard + +```ts +import { createRoute, chainRoute } from '@effector/router'; +import { createEvent, createEffect, sample } from 'effector'; + +const profileRoute = createRoute({ path: '/profile' }); + +const authorized = createEvent(); +const rejected = createEvent(); + +const checkAuthFx = createEffect(async () => { + const isAuthorized = await checkUserAuth(); + if (!isAuthorized) throw new Error('Not authorized'); +}); + +sample({ + clock: checkAuthFx.doneData, + target: authorized, +}); + +sample({ + clock: checkAuthFx.failData, + target: rejected, +}); + +const guardedProfile = chainRoute({ + route: profileRoute, + beforeOpen: checkAuthFx, + openOn: authorized, + cancelOn: rejected, +}); + +// Redirect to login when cancelled +sample({ + clock: guardedProfile.cancelled, + target: loginRoute.open, +}); +``` + +### Load Data Before Route Opens + +```ts +import { createRoute, chainRoute } from '@effector/router'; +import { createEffect, sample } from 'effector'; + +const userRoute = createRoute<{ userId: string }>({ path: '/user/:userId' }); + +const loadUserDataFx = createEffect( + async ({ params }: { params: { userId: string } }) => { + return await fetchUser(params.userId); + }, +); + +const userRouteWithData = chainRoute({ + route: userRoute, + beforeOpen: loadUserDataFx, + openOn: loadUserDataFx.done, +}); + +// Use the loaded data +sample({ + clock: loadUserDataFx.doneData, + fn: (data) => data.result, + target: $currentUser, +}); +``` + +### Multiple Before Open Effects + +```ts +import { createRoute, chainRoute } from '@effector/router'; +import { createEffect } from 'effector'; + +const dashboardRoute = createRoute({ path: '/dashboard' }); + +const checkAuthFx = createEffect(async () => { + await verifyAuth(); +}); + +const loadDashboardDataFx = createEffect(async () => { + return await fetchDashboardData(); +}); + +const guardedDashboard = chainRoute({ + route: dashboardRoute, + // Effects run sequentially in order + beforeOpen: [checkAuthFx, loadDashboardDataFx], + openOn: loadDashboardDataFx.done, + cancelOn: [checkAuthFx.fail, loadDashboardDataFx.fail], +}); +``` + +### Chain Multiple Guards + +```ts +import { createRoute, chainRoute } from '@effector/router'; + +const adminRoute = createRoute({ path: '/admin' }); + +// First check: authentication +const authenticatedRoute = chainRoute({ + route: adminRoute, + beforeOpen: checkAuthFx, + openOn: checkAuthFx.done, + cancelOn: checkAuthFx.fail, +}); + +// Second check: admin role +const authorizedAdminRoute = chainRoute({ + route: authenticatedRoute, + beforeOpen: checkAdminRoleFx, + openOn: checkAdminRoleFx.done, + cancelOn: checkAdminRoleFx.fail, +}); + +// Third check: load admin data +const adminRouteWithData = chainRoute({ + route: authorizedAdminRoute, + beforeOpen: loadAdminDataFx, + openOn: loadAdminDataFx.done, +}); +``` + +### Conditional Navigation + +```ts +import { createRoute, chainRoute } from '@effector/router'; +import { createEvent, sample } from 'effector'; + +const purchaseRoute = createRoute({ path: '/purchase/:productId' }); + +const hasInventory = createEvent(); +const outOfStock = createEvent(); + +const checkInventoryFx = createEffect(async ({ params }) => { + const product = await fetchProduct(params.productId); + if (product.stock === 0) throw new Error('Out of stock'); + return product; +}); + +sample({ + clock: checkInventoryFx.doneData, + target: hasInventory, +}); + +sample({ + clock: checkInventoryFx.failData, + target: outOfStock, +}); + +const validatedPurchase = chainRoute({ + route: purchaseRoute, + beforeOpen: checkInventoryFx, + openOn: hasInventory, + cancelOn: outOfStock, +}); + +// Show error when out of stock +sample({ + clock: validatedPurchase.cancelled, + fn: () => 'Product is out of stock', + target: showErrorToast, +}); +``` + +### With Multiple Cancel Conditions + +```ts +import { createRoute, chainRoute } from '@effector/router'; +import { createEvent } from 'effector'; + +const editorRoute = createRoute({ path: '/edit/:documentId' }); + +const documentLocked = createEvent(); +const permissionDenied = createEvent(); +const documentDeleted = createEvent(); + +const guardedEditor = chainRoute({ + route: editorRoute, + beforeOpen: checkDocumentAccessFx, + openOn: checkDocumentAccessFx.done, + cancelOn: [documentLocked, permissionDenied, documentDeleted], +}); + +// Handle different cancellation reasons +sample({ + clock: guardedEditor.cancelled, + source: { + isLocked: documentLocked, + isDenied: permissionDenied, + isDeleted: documentDeleted, + }, + fn: (reasons) => { + if (reasons.isLocked) return 'Document is locked'; + if (reasons.isDenied) return 'Permission denied'; + if (reasons.isDeleted) return 'Document was deleted'; + }, + target: showErrorMessage, +}); +``` + +## How It Works + +1. When the original `route.opened` fires, `beforeOpen` effect(s) execute sequentially +2. If `openOn` is provided, the virtual route opens when those units trigger +3. If `cancelOn` units trigger, the virtual route closes and `cancelled` event fires +4. The virtual route's `$params` store contains the transformed route parameters + +## Using the Cancelled Event + +The virtual route includes a `cancelled` event that fires when navigation is prevented: + +```ts +const guardedRoute = chainRoute({ + route: someRoute, + beforeOpen: checkSomethingFx, + openOn: checkSomethingFx.done, + cancelOn: checkSomethingFx.fail, +}); + +// React to cancellation +sample({ + clock: guardedRoute.cancelled, + target: showAccessDeniedMessage, +}); +``` + +## Best Practices + +### Use for Guards + +Perfect for implementing authorization, permissions, and data loading: + +```ts +// ✅ Good: Clear guard pattern +const guardedRoute = chainRoute({ + route: protectedRoute, + beforeOpen: checkPermissionsFx, + openOn: checkPermissionsFx.done, + cancelOn: checkPermissionsFx.fail, +}); +``` + +### Chain Multiple Concerns + +Keep each concern separate by chaining multiple guards: + +```ts +// ✅ Good: Separate authentication and authorization +const authenticated = chainRoute({ + route: baseRoute, + beforeOpen: checkAuthFx, + openOn: checkAuthFx.done, + cancelOn: checkAuthFx.fail, +}); + +const authorized = chainRoute({ + route: authenticated, + beforeOpen: checkRoleFx, + openOn: checkRoleFx.done, + cancelOn: checkRoleFx.fail, +}); +``` + +### Handle All Cancel Cases + +Always handle the `cancelled` event to provide user feedback: + +```ts +sample({ + clock: guardedRoute.cancelled, + target: redirectToLogin, +}); +``` + +## See Also + +- [createVirtualRoute](/core/create-virtual-route) - Create virtual routes +- [createRoute](/core/create-route) - Create regular routes +- [group](/core/group) - Group multiple routes diff --git a/docs/core/create-route.md b/docs/core/create-route.md new file mode 100644 index 0000000..dbf884b --- /dev/null +++ b/docs/core/create-route.md @@ -0,0 +1,328 @@ +# createRoute + +Create a route with or without a path. Routes are the building blocks of navigation in effector/router. + +## API + +```typescript +// Path route +function createRoute( + config: CreateRouteConfig, +): PathRoute>; + +// Pathless route +function createRoute( + config?: WithBaseRouteConfig, +): PathlessRoute; +``` + +### Config + +| Parameter | Type | Description | +| ------------ | ------------ | ------------------------------------------- | +| `path` | `string` | Optional. URL path template with parameters | +| `parent` | `Route` | Optional. Parent route for nesting | +| `beforeOpen` | `Effect[]` | Optional. Effects to run before opening | + +### Returns + +Returns either `PathRoute` or `PathlessRoute` depending on whether `path` is provided. + +| Property | Type | Description | +| ---------------- | -------------------------------------- | ------------------------------------------- | +| `$params` | `Store` | Route parameters | +| `$isOpened` | `Store` | Whether route (or its children) are opened | +| `$isPending` | `Store` | Whether beforeOpen effects are running | +| `open` | `EventCallable>` | Open the route and its parents | +| `opened` | `Event>` | Fires when route opens (client or server) | +| `openedOnServer` | `Event>` | Fires when opened on server (SSR) | +| `openedOnClient` | `Event>` | Fires when opened on client | +| `closed` | `Event` | Fires when route closes | +| `path` | `string` | _PathRoute only_: The route's path template | +| `parent` | `Route` | Optional. The parent route | +| `beforeOpen` | `Effect[]` | Optional. Before-open effects | + +## Usage + +### Path Routes + +Routes with paths for URL-based navigation: + +```ts +import { createRoute } from '@effector/router'; + +// Basic route +const homeRoute = createRoute({ path: '/' }); +homeRoute.open(); + +// Route with parameters +const userRoute = createRoute({ path: '/user/:id' }); +userRoute.open({ params: { id: '123' } }); + +// Route with query +const searchRoute = createRoute({ path: '/search' }); +searchRoute.open({ query: { q: 'hello' } }); +``` + +### Pathless Routes + +Routes without paths for dialogs, modals, or other non-URL navigation: + +```ts +import { createRoute } from '@effector/router'; + +// Without parameters +const dialogRoute = createRoute(); +dialogRoute.open(); + +// With typed parameters +const confirmDialog = createRoute<{ title: string; message: string }>(); +confirmDialog.open({ + params: { title: 'Delete', message: 'Are you sure?' }, +}); +``` + +::: warning Register Pathless Routes +Pathless routes must be assigned a path when registered in the router: + +```ts +import { createRouter } from '@effector/router'; + +const router = createRouter({ + routes: [ + homeRoute, // Has path: '/' + { path: '/dialog', route: dialogRoute }, // Assign path to pathless route + ], +}); +``` + +::: + +### Path Parameters + +Path parameters are automatically parsed and type-checked: + +```ts +import { createRoute } from '@effector/router'; + +// String parameters (default) +const userRoute = createRoute({ path: '/user/:id' }); +// ^- Route<{ id: string }> + +userRoute.open({ params: { id: '123' } }); +``` + +#### Typed Parameters + +Specify parameter types for validation and type safety: + +```ts +// Number parameters +const postRoute = createRoute({ path: '/post/:id' }); +// ^- Route<{ id: number }> + +postRoute.open({ params: { id: 42 } }); + +// Union types +const modeRoute = createRoute({ path: '/edit/:mode' }); +// ^- Route<{ mode: 'create' | 'update' }> + +modeRoute.open({ params: { mode: 'create' } }); + +// Multiple parameters +const blogRoute = createRoute({ + path: '/blog/:year/:month/:slug', +}); +// ^- Route<{ year: number; month: number; slug: string }> + +blogRoute.open({ + params: { year: 2024, month: 1, slug: 'hello-world' }, +}); +``` + +See [@effector/router-paths](https://github.com/movpushmov/effector/router/tree/main/packages/effector/router-paths#supported-types) for all supported parameter types and modifiers. + +### Nested Routes (Parent) + +Create route hierarchies where child routes inherit their parent's path and lifecycle: + +```ts +import { createRoute } from '@effector/router'; + +const profileRoute = createRoute({ path: '/profile/:userId' }); + +// Child routes inherit parent path +const friendsRoute = createRoute({ + path: '/friends', + parent: profileRoute, +}); +// Full path: /profile/:userId/friends + +const postsRoute = createRoute({ + path: '/posts', + parent: profileRoute, +}); +// Full path: /profile/:userId/posts + +// Opening child opens parent +postsRoute.open({ params: { userId: '123' } }); +// profileRoute.$isOpened → true +// postsRoute.$isOpened → true +``` + +#### Parent Lifecycle + +Parent routes open automatically when children open: + +```ts +import { sample } from 'effector'; + +const parentRoute = createRoute({ path: '/parent' }); +const childRoute = createRoute({ path: '/child', parent: parentRoute }); + +// Track parent opening +sample({ + clock: parentRoute.opened, + fn: () => console.log('Parent opened'), +}); + +// Opens both parent and child +childRoute.open(); +``` + +#### Parent beforeOpen Effects + +```ts +import { createEffect } from 'effector'; + +const checkAuthFx = createEffect(async () => { + return await verifyAuth(); +}); + +const dashboardRoute = createRoute({ + path: '/dashboard', + beforeOpen: [checkAuthFx], +}); + +const settingsRoute = createRoute({ + path: '/settings', + parent: dashboardRoute, // Inherits checkAuthFx +}); + +// checkAuthFx runs before opening +settingsRoute.open(); +``` + +### Before Open Effects + +Run effects before a route opens: + +```ts +import { createRoute } from '@effector/router'; +import { createEffect, sample } from 'effector'; + +const loadUserFx = createEffect(async () => { + return await fetchUser(); +}); + +const profileRoute = createRoute({ + path: '/profile', + beforeOpen: [loadUserFx], +}); + +// loadUserFx executes, then route opens +profileRoute.open(); + +// Track loading state +sample({ + clock: profileRoute.$isPending, + fn: (isPending) => console.log('Loading:', isPending), +}); +``` + +### Open with Query Parameters + +```ts +const searchRoute = createRoute({ path: '/search' }); + +searchRoute.open({ + query: { q: 'typescript', sort: 'recent' }, +}); +// URL: /search?q=typescript&sort=recent +``` + +### Replace History Entry + +```ts +const route = createRoute({ path: '/page' }); + +route.open({ replace: true }); +// Replaces current history entry instead of adding new one +``` + +## Best Practices + +### Use Path Routes for URLs + +Use path routes when the route should be reflected in the browser URL: + +```ts +// ✅ Good: URL-based navigation +const productsRoute = createRoute({ path: '/products' }); +const productRoute = createRoute({ path: '/product/:id' }); +``` + +### Use Pathless Routes for UI State + +Use pathless routes for UI elements that don't need URLs: + +```ts +// ✅ Good: UI state without URL +const confirmDialog = createRoute<{ message: string }>(); +const drawer = createRoute(); +const tooltip = createRoute<{ text: string }>(); +``` + +### Type Your Parameters + +Always specify parameter types for type safety: + +```ts +// ✅ Good: Typed parameters +const route = createRoute({ path: '/user/:id' }); + +// ❌ Bad: Untyped (defaults to string) +const route = createRoute({ path: '/user/:id' }); +``` + +### Use Parent for Shared Logic + +Extract common path prefixes and guards into parent routes: + +```ts +// ✅ Good: Shared auth guard +const adminRoute = createRoute({ + path: '/admin', + beforeOpen: [checkAdminAuthFx], +}); + +const usersRoute = createRoute({ path: '/users', parent: adminRoute }); +const settingsRoute = createRoute({ path: '/settings', parent: adminRoute }); + +// ❌ Bad: Duplicate guards +const usersRoute = createRoute({ + path: '/admin/users', + beforeOpen: [checkAdminAuthFx], +}); +const settingsRoute = createRoute({ + path: '/admin/settings', + beforeOpen: [checkAdminAuthFx], +}); +``` + +## See Also + +- [createRouter](/core/create-router) - Create router with routes +- [createVirtualRoute](/core/create-virtual-route) - Create virtual routes +- [chainRoute](/core/chain-route) - Wrap routes with guards +- [@effector/router-paths](https://github.com/movpushmov/effector/router/tree/main/packages/effector/router-paths) - Path parameter syntax diff --git a/docs/core/create-router-controls.md b/docs/core/create-router-controls.md new file mode 100644 index 0000000..9caedb6 --- /dev/null +++ b/docs/core/create-router-controls.md @@ -0,0 +1,337 @@ +# createRouterControls + +Create the core navigation controls for managing browser history, URL paths, and query parameters. These controls are typically used internally by `createRouter`, but can also be used independently for custom routing solutions. + +## API + +```typescript +function createRouterControls(): RouterControls; +``` + +### Returns + +`RouterControls` object with the following properties: + +| Property | Type | Description | +| ----------------- | ------------------------------ | ---------------------------------------- | +| `$history` | `Store` | Current history adapter instance | +| `$locationState` | `Store` | Current path and query state | +| `$query` | `Store` | Current query parameters | +| `$path` | `Store` | Current pathname | +| `setHistory` | `Event` | Initialize controls with history adapter | +| `navigate` | `Event` | Navigate to a path with query parameters | +| `back` | `Event` | Navigate back in history | +| `forward` | `Event` | Navigate forward in history | +| `locationUpdated` | `Event<{ pathname, query }>` | Fires when location changes | +| `trackQuery` | `function` | Create query parameter trackers | + +## Usage + +### Basic Setup + +```ts +import { createRouterControls, historyAdapter } from '@effector/router'; +import { createBrowserHistory } from 'history'; + +const controls = createRouterControls(); + +// Initialize with browser history +controls.setHistory(historyAdapter(createBrowserHistory())); +``` + +::: warning Initialization Required +Router controls must be initialized with `setHistory` before use. Without a history adapter, navigation methods will throw errors. +::: + +### Navigate to Paths + +```ts +import { sample } from 'effector'; + +// Navigate to a new path +sample({ + clock: goToHomePage, + fn: () => ({ path: '/' }), + target: controls.navigate, +}); + +// Navigate with query parameters +sample({ + clock: searchSubmitted, + fn: (searchTerm) => ({ + path: '/search', + query: { q: searchTerm }, + }), + target: controls.navigate, +}); + +// Replace current history entry +sample({ + clock: updateFilters, + fn: (filters) => ({ + query: { filters }, + replace: true, // Don't add new history entry + }), + target: controls.navigate, +}); +``` + +### Update Query Parameters + +```ts +import { sample } from 'effector'; + +// Add query parameters while keeping current path +sample({ + clock: filterChanged, + fn: (filter) => ({ + query: { filter, page: '1' }, + }), + target: controls.navigate, +}); + +// Clear specific query parameter +sample({ + clock: clearSearch, + fn: () => ({ query: { q: undefined } }), + target: controls.navigate, +}); +``` + +### Navigate Back/Forward + +```ts +import { sample } from 'effector'; + +// Browser back button +sample({ + clock: backButtonClicked, + target: controls.back, +}); + +// Browser forward button +sample({ + clock: forwardButtonClicked, + target: controls.forward, +}); +``` + +### Read Current State + +```ts +import { useUnit } from 'effector-react'; + +function CurrentLocation() { + const path = useUnit(controls.$path); + const query = useUnit(controls.$query); + + return ( +
+

Path: {path}

+

Query: {JSON.stringify(query)}

+
+ ); +} +``` + +### Track Query Parameters + +```ts +const $searchQuery = controls.trackQuery('q'); +const $pageNumber = controls.trackQuery('page', { + defaultValue: '1', +}); + +// Use in components +function SearchResults() { + const query = useUnit($searchQuery); + const page = useUnit($pageNumber); + + return
Searching for "{query}" (page {page})
; +} +``` + +### Server-Side Rendering + +```ts +import { createRouterControls, historyAdapter } from '@effector/router'; +import { createMemoryHistory } from 'history'; +import { fork, allSettled } from 'effector'; + +const controls = createRouterControls(); +const scope = fork(); + +// Initialize with memory history for SSR +await allSettled(controls.setHistory, { + scope, + params: historyAdapter( + createMemoryHistory({ + initialEntries: ['/products/123'], + }), + ), +}); +``` + +### Custom History Adapter + +```ts +import { createRouterControls } from '@effector/router'; + +const controls = createRouterControls(); + +// Use custom adapter +const customAdapter = { + location: { + pathname: '/current-path', + search: '?query=value', + }, + push: (location) => { + console.log('Navigate to:', location); + }, + replace: (location) => { + console.log('Replace with:', location); + }, + goBack: () => console.log('Go back'), + goForward: () => console.log('Go forward'), + listen: (listener) => { + // Subscribe to location changes + return { + unsubscribe: () => { + // Cleanup + }, + }; + }, +}; + +controls.setHistory(customAdapter); +``` + +### React to Location Changes + +```ts +import { sample } from 'effector'; + +// Track all navigation +sample({ + clock: controls.locationUpdated, + fn: ({ pathname, query }) => { + console.log('Navigated to:', pathname, query); + }, +}); + +// Analytics tracking +sample({ + clock: controls.locationUpdated, + fn: ({ pathname }) => ({ + event: 'pageview', + path: pathname, + }), + target: sendAnalyticsFx, +}); +``` + +### Derive State from Path + +```ts +import { combine } from 'effector'; + +const $isHomePage = controls.$path.map((path) => path === '/'); + +const $currentSection = controls.$path.map((path) => { + if (path.startsWith('/docs')) return 'docs'; + if (path.startsWith('/blog')) return 'blog'; + return 'home'; +}); + +const $breadcrumbs = controls.$path.map((path) => { + return path.split('/').filter(Boolean); +}); +``` + +## NavigatePayload + +The `navigate` event accepts the following payload: + +```typescript +interface NavigatePayload { + path?: string; // Optional: new pathname + query?: Query; // Optional: query parameters + replace?: boolean; // Optional: replace history entry (default: false) +} +``` + +## Query Type + +Query parameters are represented as: + +```typescript +type Query = Record; +``` + +Examples: + +```ts +// Single value +{ + search: 'apple'; +} + +// Multiple values +{ + tags: ['javascript', 'typescript']; +} + +// Remove parameter +{ + filter: undefined; +} +``` + +## Best Practices + +### Use createRouter Instead + +For most applications, use `createRouter` which includes controls automatically: + +```ts +// ✅ Recommended for most cases +import { createRouter } from '@effector/router'; + +const router = createRouter({ + routes: [...], + controls: createRouterControls(), +}); +``` + +### Initialize Early + +Initialize history adapter as early as possible: + +```ts +// In app entry point +import { createBrowserHistory } from 'history'; + +controls.setHistory(historyAdapter(createBrowserHistory())); +``` + +### Batch Query Updates + +Use `replace: true` when updating query parameters frequently: + +```ts +// ❌ Creates multiple history entries +controls.navigate({ query: { page: '1' } }); +controls.navigate({ query: { filter: 'active' } }); + +// ✅ Single history entry +controls.navigate({ + query: { page: '1', filter: 'active' }, + replace: true, +}); +``` + +## See Also + +- [createRouter](/core/create-router) - Create complete router with controls +- [Adapters](/core/adapters) - History adapters and custom adapter creation +- [trackQuery](/core/track-query) - Track individual query parameters diff --git a/docs/core/create-router.md b/docs/core/create-router.md new file mode 100644 index 0000000..94531d8 --- /dev/null +++ b/docs/core/create-router.md @@ -0,0 +1,266 @@ +# createRouter + +Creates a router instance that manages navigation state and routes. + +## Basic Usage + +```ts +import { createRouter } from '@effector/router'; +import { homeRoute, profileRoute } from './routes'; + +const router = createRouter({ + routes: [homeRoute, profileRoute], +}); +``` + +::: warning +Router must be initialized with `setHistory` event using history from the `history` package: + +```ts +import { createBrowserHistory } from 'history'; +import { historyAdapter } from '@effector/router'; + +const history = createBrowserHistory(); +router.setHistory(historyAdapter(history)); +``` + +For React apps with Effector scope: + +```ts +import { createRoot } from 'react-dom/client'; +import { allSettled, fork } from 'effector'; +import { createBrowserHistory } from 'history'; +import { Provider } from 'effector-react'; +import { historyAdapter } from '@effector/router'; + +const root = createRoot(document.getElementById('root')!); + +async function render() { + const scope = fork(); + const history = createBrowserHistory(); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + root.render( + + + + ); +} + +render(); +``` + +::: + +## Configuration + +### `routes` (required) + +Array of routes to register. Can include: + +- **Path routes** - routes with paths +- **Pathless routes** - routes without paths (must assign path here) +- **Nested routers** - other router instances + +```ts +const dialogRoute = createRoute(); // Pathless route + +const router = createRouter({ + routes: [ + homeRoute, // Path route + profileRoute, // Path route + { path: '/dialog', route: dialogRoute }, // Pathless route with assigned path + nestedRouter, // Nested router + ], +}); +``` + +### `base` (optional) + +Base path prefix for all routes in this router: + +```ts +const apiRouter = createRouter({ + base: '/api', + routes: [usersRoute, postsRoute], // Will be /api/users, /api/posts +}); +``` + +### `controls` (optional) + +Custom router controls instance (for advanced use cases): + +```ts +import { createRouterControls } from '@effector/router'; + +const controls = createRouterControls(); + +const router = createRouter({ + routes: [homeRoute], + controls, // Use custom controls +}); +``` + +## Navigation + +### Direct Navigation + +Use `navigate` event to navigate programmatically: + +```ts +import { sample } from 'effector'; + +// Navigate to path +sample({ + clock: goToPage, + fn: () => ({ path: '/page' }), + target: router.navigate, +}); + +// Update query parameters +sample({ + clock: addQuery, + fn: () => ({ query: { param1: 'hello', params2: [1, 2] } }), + target: router.navigate, +}); + +// Navigate with replace +sample({ + clock: replacePage, + fn: () => ({ path: '/new-page', replace: true }), + target: router.navigate, +}); +``` + +### Route-based Navigation + +Open routes directly (recommended): + +```ts +homeRoute.open(); +profileRoute.open({ params: { id: '123' } }); +profileRoute.open({ query: { tab: 'posts' }, replace: true }); +``` + +## Reading State + +### Current Path + +```ts +router.$path.watch((path) => { + console.log('Current path:', path); +}); + +// Or with map +const isHomePage = router.$path.map((path) => path === '/home'); +``` + +### Query Parameters + +```ts +router.$query.watch((query) => { + console.log('Query params:', query); +}); + +// Extract specific param +const searchQuery = router.$query.map((query) => query.search); +``` + +### Active Routes + +```ts +router.$activeRoutes.watch((routes) => { + console.log('Currently active routes:', routes); +}); +``` + +## History Navigation + +```ts +// Go back +router.back(); + +// Go forward +router.forward(); +``` + +## Dynamic Route Registration + +Register routes after router creation: + +```ts +const router = createRouter({ + routes: [homeRoute], +}); + +// Later... +router.registerRoute(newRoute); +router.registerRoute({ path: '/modal', route: modalRoute }); +``` + +## Nested Routers + +Routers can be nested to create modular route structures: + +```ts +const adminRouter = createRouter({ + base: '/admin', + routes: [dashboardRoute, usersRoute, settingsRoute], +}); + +const mainRouter = createRouter({ + routes: [ + homeRoute, + aboutRoute, + adminRouter, // Nested router + ], +}); +``` + +## API Reference + +| Name | Type | Description | +| --------------- | -------------------------------- | ---------------------------------------------------------- | +| `$query` | `Store` | Current query parameters | +| `$path` | `Store` | Current path | +| `$history` | `Store` | Current history adapter | +| `$activeRoutes` | `Store[]>` | Currently active routes | +| `back` | `EventCallable` | Navigate back (if possible) | +| `forward` | `EventCallable` | Navigate forward (if possible) | +| `navigate` | `EventCallable` | Navigate to path with query | +| `setHistory` | `EventCallable` | Initialize router with history adapter | +| `trackQuery` | `(config) => QueryTracker` | Track query parameters, see [trackQuery](./track-query.md) | +| `registerRoute` | `(route: InputRoute) => void` | Dynamically register a route | +| `ownRoutes` | `MappedRoute[]` | Routes owned by this router | +| `knownRoutes` | `MappedRoute[]` | All known routes (including nested) | + +## Types + +### NavigatePayload + +```ts +type NavigatePayload = { + path?: string; // Path to navigate to + query?: Query; // Query parameters + replace?: boolean; // Replace instead of push +}; +``` + +### Query + +```ts +type Query = Record>; +``` + +### InputRoute + +```ts +type InputRoute = + | PathRoute // Route with path + | { path: string; route: PathlessRoute } // Pathless route with assigned path + | Router; // Nested router +``` diff --git a/docs/core/create-virtual-route.md b/docs/core/create-virtual-route.md new file mode 100644 index 0000000..4827383 --- /dev/null +++ b/docs/core/create-virtual-route.md @@ -0,0 +1,244 @@ +# createVirtualRoute + +Create a virtual route without a path. Virtual routes are used for UI state management, dialogs, popups, and as building blocks for other routing utilities like `chainRoute` and `group`. + +## API + +```typescript +function createVirtualRoute( + options?: VirtualRouteOptions, +): VirtualRoute; +``` + +### Parameters + +| Parameter | Type | Description | +| --------------------- | ----------------------------------- | --------------------------------------------- | +| `options` | `VirtualRouteOptions` | Optional configuration | +| `options.beforeOpen` | `Effect[]` | Effects to run before opening the route | +| `options.$isPending` | `Store` | Custom pending state store | +| `options.transformer` | `(payload: T) => TransformerResult` | Transform payload before storing in `$params` | + +### Returns + +`VirtualRoute` with the following properties: + +| Property | Type | Description | +| ---------------- | -------------------------- | ------------------------------------- | +| `$params` | `Store` | Current route parameters | +| `$isOpened` | `Store` | Whether route is opened | +| `$isPending` | `Store` | Whether route is in pending state | +| `open` | `Event` | Open the route with parameters | +| `opened` | `Event` | Fires when route opens | +| `openedOnClient` | `Event` | Fires when opened on client side | +| `openedOnServer` | `Event` | Fires when opened on server side | +| `close` | `Event` | Close the route | +| `closed` | `Event` | Fires when route closes | +| `cancelled` | `Event` | Fires when route opening is cancelled | + +## Usage + +### Basic Virtual Route + +```ts +import { createVirtualRoute } from '@effector/router'; + +const route = createVirtualRoute(); + +route.open(); // Opens the route +route.close(); // Closes the route +``` + +### Dialog/Modal Management + +```ts +import { createVirtualRoute } from '@effector/router'; + +interface DialogParams { + title: string; + message: string; +} + +const confirmDialog = createVirtualRoute({ + transformer: (params) => params, +}); + +// Open dialog +confirmDialog.open({ + title: 'Delete Item', + message: 'Are you sure you want to delete this item?', +}); + +// React component +function ConfirmDialog() { + const isOpen = useUnit(confirmDialog.$isOpened); + const params = useUnit(confirmDialog.$params); + + if (!isOpen) return null; + + return ( +
+

{params.title}

+

{params.message}

+ + +
+ ); +} +``` + +### With Parameter Transformer + +```ts +import { createVirtualRoute } from '@effector/router'; + +interface OpenPayload { + userId: string; +} + +interface TransformedParams { + userId: string; + timestamp: number; +} + +const userModal = createVirtualRoute({ + transformer: (payload) => ({ + userId: payload.userId, + timestamp: Date.now(), + }), +}); + +userModal.open({ userId: '123' }); +// $params will contain: { userId: '123', timestamp: 1234567890 } +``` + +### With Before Open Effects + +```ts +import { createVirtualRoute } from '@effector/router'; +import { createEffect } from 'effector'; + +const loadUserDataFx = createEffect(async () => { + return await fetchUserData(); +}); + +const profileModal = createVirtualRoute({ + beforeOpen: [loadUserDataFx], +}); + +// loadUserDataFx will execute before the modal opens +profileModal.open(); +``` + +### Sidebar State + +```ts +import { createVirtualRoute } from '@effector/router'; + +interface SidebarParams { + section: 'notifications' | 'settings' | 'profile'; +} + +const sidebar = createVirtualRoute({ + transformer: (params) => params, +}); + +function Sidebar() { + const isOpen = useUnit(sidebar.$isOpened); + const params = useUnit(sidebar.$params); + + return ( + + ); +} + +// Usage +sidebar.open({ section: 'notifications' }); +``` + +### Popup with Custom Pending State + +```ts +import { createVirtualRoute } from '@effector/router'; +import { createStore } from 'effector'; +import { pending } from 'patronum'; + +const loadContentFx = createEffect(async () => { + return await fetchPopupContent(); +}); + +const $isPending = pending({ effects: [loadContentFx] }); + +const popup = createVirtualRoute({ + $isPending, + beforeOpen: [loadContentFx], +}); + +function Popup() { + const isOpen = useUnit(popup.$isOpened); + const isPending = useUnit(popup.$isPending); + + if (!isOpen) return null; + + return ( +
+ {isPending ? : } +
+ ); +} +``` + +## Server/Client Split + +Virtual routes can distinguish between server and client-side opens: + +```ts +import { createVirtualRoute } from '@effector/router'; +import { sample } from 'effector'; + +const route = createVirtualRoute(); + +sample({ + clock: route.openedOnServer, + fn: () => console.log('Opened on server'), +}); + +sample({ + clock: route.openedOnClient, + fn: () => console.log('Opened on client'), +}); +``` + +## Best Practices + +### Use for UI State + +Virtual routes are perfect for managing UI state that needs Effector integration: + +```ts +// ✅ Good use cases +const dialog = createVirtualRoute(); +const drawer = createVirtualRoute(); +const tooltip = createVirtualRoute(); +const contextMenu = createVirtualRoute(); +``` + +### Not for URL Routing + +Virtual routes don't have paths and won't update the browser URL: + +```ts +// ❌ For URL routing, use createRoute instead +const userRoute = createRoute({ path: '/user/:id' }); +``` + +## See Also + +- [group](/core/group) - Group multiple routes into a virtual route +- [chainRoute](/core/chain-route) - Create sequential route chains +- [createRoute](/core/create-route) - Create routes with paths diff --git a/docs/core/group.md b/docs/core/group.md new file mode 100644 index 0000000..51e4816 --- /dev/null +++ b/docs/core/group.md @@ -0,0 +1,103 @@ +# group + +Create a virtual route that opens when any of the passed routes is opened, and closes when all passed routes are closed. + +## API + +```typescript +function group(routes: Route[]): VirtualRoute; +``` + +### Parameters + +| Parameter | Type | Description | +| --------- | -------------- | --------------------------------- | +| `routes` | `Route[]` | Array of routes to group together | + +### Returns + +`VirtualRoute` - A virtual route that tracks the combined state of all passed routes. + +## Usage + +### Basic Example + +```ts +import { group, createRoute } from '@effector/router'; + +const signInRoute = createRoute({ path: '/auth/sign-in' }); +const signUpRoute = createRoute({ path: '/auth/sign-up' }); +const authorizationRoute = group([signInRoute, signUpRoute]); + +signInRoute.open(); // authorizationRoute.$isOpened → true +signUpRoute.open(); // authorizationRoute.$isOpened → true +signInRoute.close(); // authorizationRoute.$isOpened → true (signUpRoute still open) +signUpRoute.close(); // authorizationRoute.$isOpened → false (all closed) +``` + +### Guard Multiple Routes + +```ts +import { group, createRoute, createRouter } from '@effector/router'; +import { sample } from 'effector'; + +const profileRoute = createRoute({ path: '/profile' }); +const settingsRoute = createRoute({ path: '/settings' }); +const dashboardRoute = createRoute({ path: '/dashboard' }); + +const authenticatedRoutes = group([ + profileRoute, + settingsRoute, + dashboardRoute, +]); + +// Redirect to login if trying to access any authenticated route +sample({ + clock: authenticatedRoutes.opened, + filter: () => !isAuthenticated(), + target: loginRoute.open, +}); +``` + +### Track Section State + +```ts +import { group, createRoute } from '@effector/router'; +import { useUnit } from 'effector-react'; + +const productsRoute = createRoute({ path: '/shop/products' }); +const cartRoute = createRoute({ path: '/shop/cart' }); +const checkoutRoute = createRoute({ path: '/shop/checkout' }); + +const shopSection = group([productsRoute, cartRoute, checkoutRoute]); + +function ShopIndicator() { + const isShopActive = useUnit(shopSection.$isOpened); + + return ( +
+ Shopping Section +
+ ); +} +``` + +## How It Works + +The `group` function creates a virtual route that: + +- Opens when **any** of the grouped routes opens +- Closes when **all** of the grouped routes close +- Tracks combined pending state (`$isPending`) from all routes + +This is useful for: + +- Protecting multiple routes with the same guard +- Showing UI indicators for route sections +- Tracking navigation state across related routes + +## See Also + +- [createVirtualRoute](/core/create-virtual-route) - Create custom virtual routes +- [createRoute](/core/create-route) - Create regular routes +- [chainRoute](/core/chain-route) - Create sequential route chains diff --git a/docs/core/index.md b/docs/core/index.md new file mode 100644 index 0000000..92b3778 --- /dev/null +++ b/docs/core/index.md @@ -0,0 +1,161 @@ +# Core + +The core package of effector/router Router provides the fundamental routing primitives powered by [Effector](https://effector.dev). + +## Overview + +`@effector/router` is the foundation of effector/router Router. It provides: + +- **Route Creation** - Define routes with paths and parameters +- **Router Management** - Central navigation state management +- **Type Safety** - Full TypeScript support with automatic type inference +- **State Management** - Powered by Effector for predictable updates +- **Query Tracking** - Monitor and react to query parameter changes + +## Installation + +```bash +npm install @effector/router effector +``` + +## Key Concepts + +### Routes + +Routes represent navigable locations in your app. They can be created with or without a path: + +**Path routes** - Routes with URL paths: + +```tsx +import { createRoute } from '@effector/router'; + +const homeRoute = createRoute({ path: '/home' }); +const userRoute = createRoute({ path: '/user/:id' }); +``` + +**Pathless routes** - Routes without URL paths: + +```tsx +const dialogRoute = createRoute(); // No path +const modalRoute = createRoute<{ title: string }>(); // With typed params +``` + +All routes have: + +- State stores: `$isOpened`, `$params`, `$isPending` +- Events: `open`, `opened`, `closed` +- Optional: `parent`, `beforeOpen` + +### Router + +The router manages multiple routes and their state. You can register routes with paths directly, or assign paths to pathless routes: + +```tsx +import { createRouter } from '@effector/router'; + +// Routes with paths +const router = createRouter({ + routes: [homeRoute, userRoute, profileRoute], +}); + +// Pathless routes need paths assigned in router +const dialogRoute = createRoute(); +const router = createRouter({ + routes: [ + homeRoute, + { path: '/dialog', route: dialogRoute }, // Assign path here + ], +}); +``` + +### Navigation + +Navigate by opening routes: + +```tsx +// Simple navigation +homeRoute.open(); + +// With parameters +userRoute.open({ params: { id: '123' } }); + +// With query parameters +homeRoute.open({ query: { tab: 'settings' } }); + +// Replace instead of push +profileRoute.open({ replace: true }); +``` + +## Core APIs + +### Route Management + +- [createRoute](/core/create-route) - Create a route with path and parameters +- [createVirtualRoute](/core/create-virtual-route) - Create a route without a path +- [chainRoute](/core/chain-route) - Create routes with conditional navigation + +### Router Management + +- [createRouter](/core/create-router) - Create a router instance +- [createRouterControls](/core/create-router-controls) - Create router controls separately + +### Advanced Features + +- [trackQuery](/core/track-query) - Track query parameter changes +- [group](/core/group) - Group related routes + +## Quick Example + +```tsx +import { createRoute, createRouter, historyAdapter } from '@effector/router'; +import { createMemoryHistory } from 'history'; + +// 1. Create routes +const homeRoute = createRoute({ path: '/home' }); +const profileRoute = createRoute({ path: '/profile/:id' }); + +// 2. Create router +const router = createRouter({ + routes: [homeRoute, profileRoute], +}); + +// 3. Connect to history +const history = createMemoryHistory(); +router.setHistory(historyAdapter(history)); + +// 4. Navigate +homeRoute.open(); // Opens /home +profileRoute.open({ params: { id: '123' } }); // Opens /profile/123 + +// 5. React to state changes +router.$path.watch((path) => { + console.log('Current path:', path); +}); + +homeRoute.$isOpened.watch((isOpened) => { + console.log('Home route opened:', isOpened); +}); +``` + +## Type Safety + +effector/router Router provides full type safety: + +```tsx +const userRoute = createRoute({ path: '/user/:id/:section' }); +// Type: Route<{ id: string; section: string }> + +// ✅ Type-safe +userRoute.open({ params: { id: '1', section: 'posts' } }); + +// ❌ TypeScript error +userRoute.open({ params: { id: 1 } }); // id must be string +userRoute.open({ params: { id: '1' } }); // section is required +``` + +## Next Steps + +- [createRoute](/core/create-route) - Learn about creating routes +- [createRouter](/core/create-router) - Set up your router +- [React Package](/react/create-route-view) - Use with React +- [React Native Package](/react-native/index) - Use with React Native diff --git a/docs/core/track-query.md b/docs/core/track-query.md new file mode 100644 index 0000000..dbd7bde --- /dev/null +++ b/docs/core/track-query.md @@ -0,0 +1,428 @@ +# trackQuery + +Track specific query parameters in the URL with schema validation. When the specified parameters appear in the URL and match the schema, the tracker enters; when they're removed or validation fails, it exits. + +## API + +```typescript +// From router +router.trackQuery(config: QueryTrackerConfig): QueryTracker + +// From controls +controls.trackQuery(config: Omit, 'forRoutes'>): QueryTracker +``` + +### Config + +| Parameter | Type | Description | +| ------------ | --------- | --------------------------------------------------------------- | +| `parameters` | `ZodType` | Zod schema for query parameter validation | +| `forRoutes` | `Route[]` | Optional (router only). Only track when these routes are active | + +### Returns + +`QueryTracker` with the following properties: + +| Property | Type | Description | +| --------- | -------------------------------------------- | -------------------------------------- | +| `enter` | `Event>` | Programmatically add parameters to URL | +| `entered` | `Event>` | Fires when parameters match schema | +| `exit` | `Event<{ ignoreParams?: string[] } \| void>` | Programmatically remove parameters | +| `exited` | `Event` | Fires when parameters no longer match | + +## Usage + +### Basic Query Tracking + +```ts +import { createRouter, createRoute } from '@effector/router'; +import { z } from 'zod'; + +const searchRoute = createRoute({ path: '/search' }); + +const router = createRouter({ + routes: [searchRoute], +}); + +// Track search query parameter +const searchTracker = router.trackQuery({ + parameters: z.object({ + q: z.string(), + }), + forRoutes: [searchRoute], +}); + +// Listen when search query appears +sample({ + clock: searchTracker.entered, + fn: (params) => console.log('Search query:', params.q), +}); + +// Listen when search query is removed +sample({ + clock: searchTracker.exited, + fn: () => console.log('Search cleared'), +}); +``` + +### One-time query tracker + +May be useful in get query once design + +```ts +import { createRouter, createRoute } from '@effector/router'; +import { z } from 'zod'; +import { acceptInvitationFx } from '@shared/api'; + +// event for global app initialization +import { appStarted } from '@shared/global'; + +const familyRoute = createRoute({ path: '/search' }); + +const router = createRouter({ + routes: [familyRoute], +}); + +const invitationTracker = router.trackQuery({ + check: appStarted, + parameters: z.object({ + inviteId: z.string(), + }), +}); + +sample({ + clock: invitationTracker.entered, + target: acceptInvitationFx, +}); + +// in root of app +// event for global app initialization +import { appStarted } from '@shared/global'; + +await allSettled(router.setHistory, { scope, params: ... }); +await allSettled(appStarted); +``` + +### Add/Remove Query Parameters + +```ts +import { z } from 'zod'; + +const filterTracker = router.trackQuery({ + parameters: z.object({ + status: z.enum(['active', 'inactive']), + category: z.string(), + }), + forRoutes: [productsRoute], +}); + +// Add filters to URL +filterTracker.enter({ + status: 'active', + category: 'electronics', +}); +// URL becomes: /products?status=active&category=electronics + +// Remove all tracked parameters +filterTracker.exit(); +// URL becomes: /products + +// Remove tracked parameters but keep others +filterTracker.exit({ ignoreParams: ['page'] }); +// Removes status and category, keeps page param +``` + +### Pagination + +```ts +import { z } from 'zod'; + +const paginationTracker = router.trackQuery({ + parameters: z.object({ + page: z.string().regex(/^\d+$/), + limit: z.string().regex(/^\d+$/), + }), + forRoutes: [listRoute], +}); + +// Go to page 2 +paginationTracker.enter({ page: '2', limit: '20' }); + +// Reset to first page +paginationTracker.exit(); +``` + +### Optional Parameters + +```ts +import { z } from 'zod'; + +const advancedSearchTracker = router.trackQuery({ + parameters: z.object({ + q: z.string(), + tags: z.array(z.string()).optional(), + minPrice: z.string().optional(), + maxPrice: z.string().optional(), + }), + forRoutes: [searchRoute], +}); + +// Required query only +advancedSearchTracker.enter({ q: 'laptop' }); + +// With optional parameters +advancedSearchTracker.enter({ + q: 'laptop', + tags: ['electronics', 'computers'], + minPrice: '500', + maxPrice: '2000', +}); +``` + +### Multiple Trackers + +```ts +import { z } from 'zod'; + +// Track search independently +const searchTracker = router.trackQuery({ + parameters: z.object({ q: z.string() }), + forRoutes: [searchRoute], +}); + +// Track filters independently +const filterTracker = router.trackQuery({ + parameters: z.object({ + category: z.string(), + status: z.string(), + }), + forRoutes: [searchRoute], +}); + +// Track sort independently +const sortTracker = router.trackQuery({ + parameters: z.object({ + sort: z.enum(['asc', 'desc']), + sortBy: z.string(), + }), + forRoutes: [searchRoute], +}); + +// Each tracker manages its own parameters +searchTracker.enter({ q: 'phone' }); +filterTracker.enter({ category: 'mobile', status: 'active' }); +sortTracker.enter({ sort: 'asc', sortBy: 'price' }); +// URL: /search?q=phone&category=mobile&status=active&sort=asc&sortBy=price +``` + +### With Router Controls + +```ts +import { createRouterControls } from '@effector/router'; +import { z } from 'zod'; + +const controls = createRouterControls(); + +// trackQuery from controls doesn't support forRoutes +const themeTracker = controls.trackQuery({ + parameters: z.object({ + theme: z.enum(['light', 'dark']), + }), +}); + +// Theme parameter works on all routes +themeTracker.enter({ theme: 'dark' }); +``` + +### React Integration + +```tsx +import { useUnit } from 'effector-react'; +import { z } from 'zod'; + +const filterTracker = router.trackQuery({ + parameters: z.object({ + search: z.string(), + category: z.string().optional(), + }), + forRoutes: [productsRoute], +}); + +function ProductFilters() { + const [search, setSearch] = useState(''); + const [category, setCategory] = useState(''); + + const handleApplyFilters = () => { + filterTracker.enter({ + search, + ...(category && { category }), + }); + }; + + const handleClearFilters = () => { + filterTracker.exit(); + setSearch(''); + setCategory(''); + }; + + return ( +
+ setSearch(e.target.value)} + placeholder="Search..." + /> + + + +
+ ); +} +``` + +### Load Data on Query Change + +```ts +import { sample } from 'effector'; +import { z } from 'zod'; + +const searchTracker = router.trackQuery({ + parameters: z.object({ + q: z.string(), + page: z.string().optional(), + }), + forRoutes: [searchRoute], +}); + +const loadSearchResultsFx = createEffect( + async (params: { q: string; page?: string }) => { + return await fetchSearchResults(params); + }, +); + +// Load results when search query is entered +sample({ + clock: searchTracker.entered, + target: loadSearchResultsFx, +}); + +// Clear results when search is exited +sample({ + clock: searchTracker.exited, + target: $searchResults.reinit, +}); +``` + +### Validation Handling + +```ts +import { z } from 'zod'; + +const strictPaginationTracker = router.trackQuery({ + parameters: z.object({ + page: z + .string() + .regex(/^\d+$/) + .refine((val) => parseInt(val) > 0), + limit: z.enum(['10', '20', '50', '100']), + }), + forRoutes: [listRoute], +}); + +// ✅ Valid - tracker enters +// URL: /list?page=1&limit=20 + +// ❌ Invalid - tracker won't enter or will exit +// URL: /list?page=0&limit=20 (page must be > 0) +// URL: /list?page=abc&limit=20 (page must be number) +// URL: /list?page=1&limit=30 (limit must be 10/20/50/100) +``` + +## How It Works + +1. **Validation**: Continuously validates current query parameters against the schema +2. **Entered**: When parameters match the schema, `entered` fires with validated data +3. **Exited**: When parameters no longer match (removed or invalid), `exited` fires +4. **Route Filtering**: If `forRoutes` is specified, only tracks when those routes are active + +## Best Practices + +### Use Specific Schemas + +Define precise validation rules: + +```ts +// ✅ Good: Specific validation +const tracker = router.trackQuery({ + parameters: z.object({ + page: z + .string() + .regex(/^\d+$/) + .refine((val) => parseInt(val) > 0), + sortBy: z.enum(['name', 'date', 'price']), + }), + forRoutes: [listRoute], +}); + +// ❌ Bad: Too permissive +const tracker = router.trackQuery({ + parameters: z.object({ + page: z.any(), + sortBy: z.string(), + }), + forRoutes: [listRoute], +}); +``` + +### Scope to Routes + +Only track query parameters for relevant routes: + +```ts +// ✅ Good: Scoped to search route +const searchTracker = router.trackQuery({ + parameters: z.object({ q: z.string() }), + forRoutes: [searchRoute], +}); + +// ❌ Bad: Tracks on all routes (unless intended) +const searchTracker = controls.trackQuery({ + parameters: z.object({ q: z.string() }), +}); +``` + +### Separate Concerns + +Create separate trackers for different parameter groups: + +```ts +// ✅ Good: Separate trackers for independent concerns +const searchTracker = router.trackQuery({ + parameters: z.object({ q: z.string() }), + forRoutes: [searchRoute], +}); + +const paginationTracker = router.trackQuery({ + parameters: z.object({ page: z.string() }), + forRoutes: [searchRoute], +}); + +// ❌ Bad: Mixed concerns +const mixedTracker = router.trackQuery({ + parameters: z.object({ + q: z.string(), + page: z.string(), + theme: z.string(), + }), + forRoutes: [searchRoute], +}); +``` + +## See Also + +- [createRouter](/core/create-router) - Create router with trackQuery +- [createRouterControls](/core/create-router-controls) - Create controls with trackQuery diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..79f0475 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,31 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: '@effector/router' + text: 'official effector library for routing' + image: + src: ./logo.svg + actions: + - theme: brand + text: Get Started + link: /introduction/getting-started.md + - theme: alt + text: View on GitHub + link: https://github.com/effector/router + +features: + - title: Effector-based + icon: ☄️ + details: Experience the power of effector + - title: TypeScript + icon: + details: Out-of-box TypeScript support + - title: Flexible + icon: 💪🏻 + details: Live without path-based routing + - title: SSR compatible + icon: 🗄️️ + details: Prepare router on server and use it on a client +--- diff --git a/docs/introduction/getting-started.md b/docs/introduction/getting-started.md new file mode 100644 index 0000000..6581529 --- /dev/null +++ b/docs/introduction/getting-started.md @@ -0,0 +1,142 @@ +--- +title: Getting started +--- + +# Getting started + +## Installation + +::: code-group + +```bash [npm] +npm install @effector/router history +``` + +```bash [yarn] +yarn add @effector/router history +``` + +```bash [pnpm] +pnpm add @effector/router history +``` + +::: + +::: tip +In SSR project you must add @effector/router in "factories" +list in [effector babel plugin](https://effector.dev/en/api/effector/babel-plugin/#configuration-factories) +::: + +## React bindings + +::: code-group + +```bash [npm] +npm install @effector/router-react +``` + +```bash [yarn] +yarn add @effector/router-react +``` + +```bash [pnpm] +pnpm add @effector/router-react +``` + +::: + +## Writing first router + +As an example, we will write a simple router with `feed` and `profile` routes. + +```ts +import { createRoute, createRouter } from '@effector/router'; +import { fork } from 'effector'; + +const scope = fork(); + +const routes = { + feed: createRoute({ path: '/' }), + profile: createRoute({ path: '/profile' }), +}; + +const router = createRouter({ + routes: [routes.feed, routes.profile], +}); +``` + +```tsx +// profile.tsx +import { createRouteView } from '@effector/router-react'; + +const Profile = () => { + return <>...; +}; + +export const ProfileScreen = createRouteView({ + route: routes.profile, + view: Profile, +}); +``` + +```tsx +// feed.tsx +import { createRouteView } from '@effector/router-react'; + +const Feed = () => { + return <>...; +}; + +export const FeedScreen = createRouteView({ route: routes.feed, view: Feed }); +``` + +```tsx +// app.tsx +import { createRoutesView } from '@effector/router-react'; +import { FeedScreen, ProfileScreen } from './screens'; +import { router } from './shared/routing'; + +const RoutesView = createRoutesView({ routes: [FeedScreen, ProfileScreen] }); + +export function App() { + return ( + + + + ); +} +``` + +::: warning + +router need to be initialzed with `setHistory` event, which requires memory or browser history from `history` package. + +```ts +import { createRoot } from 'react-dom/client'; +import { allSettled, fork } from 'effector'; +import { createBrowserHistory } from 'history'; +import { Provider } from 'effector-react'; +import { router } from './shared/routing'; +import { App } from './app'; + +const root = createRoot(document.getElementById('root')!); + +async function render() { + const scope = fork(); + + await allSettled(router.setHistory, { + scope, + params: createBrowserHistory(), + }); + + root.render( + + + , + ); +} + +render(); +``` + +::: diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..9a63ed1 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,20 @@ +{ + "name": "@effector/router-docs", + "private": true, + "type": "module", + "devDependencies": { + "@iconify-json/logos": "1.2.10", + "@unocss/preset-icons": "66.6.4", + "unocss": "66.6.4", + "vite": "7.3.1", + "vitepress": "1.6.4" + }, + "scripts": { + "docs:dev": "vitepress dev", + "docs:build": "vitepress build", + "docs:preview": "vitepress preview" + }, + "dependencies": { + "vue": "^3.5.29" + } +} diff --git a/docs/paths/index.md b/docs/paths/index.md new file mode 100644 index 0000000..bfad7ba --- /dev/null +++ b/docs/paths/index.md @@ -0,0 +1,468 @@ +# Paths + +The `@effector/router-paths` package provides powerful path parsing and building utilities with full TypeScript type inference. + +## Overview + +`@effector/router-paths` is a standalone library for working with URL paths. It compiles path patterns into parser and builder functions with automatic parameter type extraction. + +**Key features:** + +- **Type-Safe** - Full TypeScript inference of path parameters +- **Flexible** - Support for strings, numbers, unions, and arrays +- **Modifiers** - Optional (`?`), repeating (`+`, `*`), and range (`{min,max}`) parameters +- **Validation** - Runtime validation of path parameters +- **Standalone** - Can be used independently or with `@effector/router` + +## Installation + +```bash +npm install @effector/router-paths +``` + +## Quick Start + +```ts +import { compile } from '@effector/router-paths'; + +// Compile a path pattern +const { parse, build } = compile('/user/:id'); + +// Parse a path +const result = parse('/user/123'); +// { path: '/user/123', params: { id: 123 } } + +// Build a path +const path = build({ id: 456 }); +// '/user/456' +``` + +## Parameter Types + +### String Parameters (Default) + +```ts +const { parse, build } = compile('/user/:name'); +// ^- { name: string } + +build({ name: 'john' }); // '/user/john' +parse('/user/jane'); // { path: '/user/jane', params: { name: 'jane' } } +``` + +### Number Parameters + +```ts +const { parse, build } = compile('/post/:id'); +// ^- { id: number } + +build({ id: 123 }); // '/post/123' +parse('/post/456'); // { path: '/post/456', params: { id: 456 } } +parse('/post/abc'); // null (validation failed) +``` + +### Union Parameters + +```ts +const { parse, build } = compile('/edit/:mode'); +// ^- { mode: 'create' | 'update' | 'delete' } + +build({ mode: 'create' }); // '/edit/create' +parse('/edit/update'); // { path: '/edit/update', params: { mode: 'update' } } +parse('/edit/other'); // null (not in union) +``` + +### Multiple Parameters + +```ts +const { parse, build } = compile('/blog/:year/:month/:slug'); +// ^- { year: number; month: number; slug: string } + +build({ year: 2024, month: 1, slug: 'hello-world' }); +// '/blog/2024/1/hello-world' + +parse('/blog/2024/12/typescript-tips'); +// { path: '/blog/2024/12/typescript-tips', params: { year: 2024, month: 12, slug: 'typescript-tips' } } +``` + +## Parameter Modifiers + +### Optional Parameters (`?`) + +```ts +const { parse, build } = compile('/user/:id?'); +// ^- { id?: string } + +build({}); // '/user' +build({ id: '123' }); // '/user/123' + +parse('/user'); // { path: '/user', params: {} } +parse('/user/456'); // { path: '/user/456', params: { id: '456' } } +``` + +### Optional with Type + +```ts +const { parse, build } = compile('/post/:id?'); +// ^- { id?: number } + +build({}); // '/post' +build({ id: 123 }); // '/post/123' +``` + +### Repeating Parameters (`+`) + +One or more values: + +```ts +const { parse, build } = compile('/category/:tags+'); +// ^- { tags: string[] } + +build({ tags: ['js'] }); // '/category/js' +build({ tags: ['js', 'ts', 'react'] }); // '/category/js/ts/react' + +parse('/category/javascript'); // { path: '...', params: { tags: ['javascript'] } } +parse('/category/js/typescript'); // { path: '...', params: { tags: ['js', 'typescript'] } } +parse('/category'); // null (requires at least one) +``` + +### Zero or More Parameters (`*`) + +```ts +const { parse, build } = compile('/files/:path*'); +// ^- { path: string[] } + +build({ path: [] }); // '/files' +build({ path: ['docs', 'api', 'index'] }); // '/files/docs/api/index' + +parse('/files'); // { path: '/files', params: { path: [] } } +parse('/files/src/utils'); // { path: '...', params: { path: ['src', 'utils'] } } +``` + +### Range Parameters (`{min,max}`) + +Specify exact ranges: + +```ts +const { parse, build } = compile('/path/:segments{2,3}'); +// ^- { segments: string[] } + +build({ segments: ['a', 'b'] }); // '/path/a/b' +build({ segments: ['a', 'b', 'c'] }); // '/path/a/b/c' +build({ segments: ['a'] }); // Error: must have 2-3 items + +parse('/path/x/y'); // { path: '...', params: { segments: ['x', 'y'] } } +parse('/path/x/y/z'); // { path: '...', params: { segments: ['x', 'y', 'z'] } } +parse('/path/x'); // null (need 2-3 segments) +parse('/path/w/x/y/z'); // null (max 3 segments) +``` + +### Combining Modifiers + +```ts +// Optional range +const { parse, build } = compile('/items/:ids{1,3}?'); +// ^- { ids?: number[] } + +build({}); // '/items' +build({ ids: [1, 2] }); // '/items/1/2' + +// Range with type +const { parse, build } = compile('/tag/:names{2,2}'); +// ^- { names: ('create' | 'update' | 'delete')[] } + +build({ names: ['create', 'delete'] }); // '/tag/create/delete' +``` + +## TypeScript Integration + +All parameter types are automatically inferred: + +```ts +import { compile, ParseUrlParams } from '@effector/router-paths'; + +// Extract parameter types +type UserParams = ParseUrlParams<'/user/:id'>; +// ^- { id: number } + +type BlogParams = ParseUrlParams<'/blog/:year/:month/:slug'>; +// ^- { year: number; month: number; slug: string } + +type TagsParams = ParseUrlParams<'/tags/:items+'>; +// ^- { items: string[] } + +type OptionalParams = ParseUrlParams<'/post/:id?'>; +// ^- { id?: string } + +// Use with compile +const { build, parse } = compile('/user/:id'); + +// ✅ Type-safe +build({ id: 123 }); + +// ❌ TypeScript error +build({ id: '123' }); // Expected number, got string +build({}); // Missing required parameter +``` + +## Advanced Examples + +### File Path with Wildcard + +```ts +const { parse, build } = compile('/files/:path*'); + +function serveFile(url: string) { + const result = parse(url); + if (!result) return null; + + const filePath = result.params.path.join('/'); + return readFile(filePath); +} + +serveFile('/files/docs/api/routes.md'); +// Reads: docs/api/routes.md +``` + +### API Version Routing + +```ts +const { parse, build } = compile('/api/:version/:resource'); + +function routeApiRequest(url: string) { + const result = parse(url); + if (!result) return null; + + const { version, resource } = result.params; + return routeToHandler(version, resource); +} +``` + +### Breadcrumb Navigation + +```ts +const { parse, build } = compile('/:segments+'); + +function generateBreadcrumbs(url: string) { + const result = parse(url); + if (!result) return []; + + return result.params.segments.map((segment, index) => ({ + label: segment, + path: build({ segments: result.params.segments.slice(0, index + 1) }), + })); +} + +generateBreadcrumbs('/products/electronics/laptops'); +// [ +// { label: 'products', path: '/products' }, +// { label: 'electronics', path: '/products/electronics' }, +// { label: 'laptops', path: '/products/electronics/laptops' } +// ] +``` + +### Date-based Routes + +```ts +const { parse, build } = compile( + '/archive/:year/:month?/:day?', +); + +function buildArchiveUrl(date: Date) { + return build({ + year: date.getFullYear(), + month: date.getMonth() + 1, + day: date.getDate(), + }); +} + +buildArchiveUrl(new Date('2024-01-15')); // '/archive/2024/1/15' +``` + +## Best Practices + +### Validate Before Building + +```ts +const { build, parse } = compile('/user/:id'); + +function navigateToUser(id: unknown) { + // Validate at runtime + if (typeof id !== 'number') { + throw new Error('Invalid user ID'); + } + + return build({ id }); +} +``` + +### Handle Parse Failures + +```ts +const { parse } = compile('/post/:id'); + +function getPostId(url: string): number | null { + const result = parse(url); + return result ? result.params.id : null; +} +``` + +### Use Type Extraction + +```ts +import { ParseUrlParams } from '@effector/router-paths'; + +// Define path template +const USER_PATH = '/user/:id/profile' as const; +type UserPathParams = ParseUrlParams; + +// Use in functions +function buildUserUrl(params: UserPathParams) { + const { build } = compile(USER_PATH); + return build(params); +} +``` + +### Reuse Compiled Patterns + +```ts +// ✅ Good: Compile once, reuse many times +const userPath = compile('/user/:id'); + +function parseUser(url: string) { + return userPath.parse(url); +} + +function buildUser(id: number) { + return userPath.build({ id }); +} + +// ❌ Bad: Compiling every time +function parseUser(url: string) { + const { parse } = compile('/user/:id'); + return parse(url); +} +``` + +## Path Conversion + +### `convertPath(path, mode)` + +Convert paths from effector/router format to express or other path format. + +**Parameters:** + +- `path: string` - The path to convert +- `mode: 'express'` - The result format + +**Returns:** `string` - The converted path + +```ts +import { convertPath } from '@effector/router-paths'; + +// Convert effector/router patterns to Express format +convertPath('/user/:id<.+>', 'express'); +// '/user/:id' + +convertPath('/files/:id+', 'express'); +// '/files/*id' + +convertPath('/files/:id*', 'express'); +// '/files/*id' + +convertPath('/files/:id{.+}', 'express'); +// '/files/*id' + +// Convert optional parameters +convertPath('/user/:id?', 'express'); +// '/user/{:id}' + +convertPath('/:id?', 'express'); +// '{:id}' + +// Nested optional parameters +convertPath('/api/:version?/*path?', 'express'); +// '/api{/:version}/{/*path}' +``` + +### effector/router-Router to Express Conversion + +When you need to use effector/router paths with Express.js or generate Express-compatible routes, use `convertPath`: + +| effector/router-Router Pattern | Express Pattern | Description | +| ------------------------------ | --------------- | ------------------------ | +| `:id<.+>` | `:id` | Removes regex patterns | +| `:id+` | `*id` | One or more → wildcard | +| `:id*` | `*id` | Zero or more → wildcard | +| `:id{.+}` | `*id` | Custom regex → wildcard | +| `:id?` | `{:id}` | Wraps optional params | +| `*id?` | `{*id}` | Wraps optional wildcards | + +**Example usage:** + +```ts +import { convertPath } from '@effector/router-paths'; + +// effector/router paths with advanced features +const routerPaths = [ + '/users/:id', + '/files/:path+', + '/api/:version?/:resource', +]; + +// Convert to Express-compatible format +const expressPaths = routerPaths.map((path) => convertPath(path, 'express')); + +// Use with Express.js +import express from 'express'; +const app = express(); + +expressPaths.forEach((path) => { + app.get(path, (req, res) => { + // Handle request + }); +}); +``` + +## API Reference + +### `compile(path: T)` + +Compile a path pattern into parser and builder functions. + +**Parameters:** + +- `path: string` - The path pattern to compile + +**Returns:** + +```typescript +{ + parse: (path: string) => { path: string; params: Params } | null; + build: (params: Params) => string; +} +``` + +### `convertPath(path: string, mode: 'express')` + +Convert an effector/router path pattern to another router format. + +**Parameters:** + +- `path: string` - The effector/router path pattern to convert +- `mode: 'express'` - The target router format + +**Returns:** `string` - The converted path pattern in the target format + +### `ParseUrlParams` + +Type utility to extract parameter types from a path template. + +```typescript +type Params = ParseUrlParams<'/user/:id'>; +// { id: number } +``` + +## See Also + +- [createRoute](/core/create-route) - Use with effector/router +- [createRouter](/core/create-router) - Router with path compilation diff --git a/docs/public/CNAME b/docs/public/CNAME new file mode 100644 index 0000000..9eaeb57 --- /dev/null +++ b/docs/public/CNAME @@ -0,0 +1 @@ +router.effector.dev diff --git a/docs/public/favicon.ico b/docs/public/favicon.ico new file mode 100644 index 0000000..ceedffb Binary files /dev/null and b/docs/public/favicon.ico differ diff --git a/docs/public/logo.svg b/docs/public/logo.svg new file mode 100644 index 0000000..e1864c1 --- /dev/null +++ b/docs/public/logo.svg @@ -0,0 +1 @@ + diff --git a/docs/public/telegram-logo.svg b/docs/public/telegram-logo.svg new file mode 100644 index 0000000..60b3d3d --- /dev/null +++ b/docs/public/telegram-logo.svg @@ -0,0 +1 @@ + diff --git a/docs/react-native/bottom-tabs-navigator.md b/docs/react-native/bottom-tabs-navigator.md new file mode 100644 index 0000000..f5ac672 --- /dev/null +++ b/docs/react-native/bottom-tabs-navigator.md @@ -0,0 +1,220 @@ +# Bottom Tabs Navigator + +Creates a Bottom Tabs Navigator that integrates effector/router Router with React Navigation's Bottom Tabs Navigator. + +## Import + +```ts +import { createeffector/routerBottomTabsNavigator } from '@effector/router-react-native'; +``` + +## Usage + +```tsx +import { NavigationContainer } from '@react-navigation/native'; +import { createeffector/routerBottomTabsNavigator } from '@effector/router-react-native'; +import { createRouter, createRoute } from '@effector/router'; +import { createRouteView, RouterProvider } from '@effector/router-react'; +import Icon from 'react-native-vector-icons/Ionicons'; + +const homeRoute = createRoute({ path: '/home' }); +const searchRoute = createRoute({ path: '/search' }); +const profileRoute = createRoute({ path: '/profile' }); + +const router = createRouter({ + routes: [homeRoute, searchRoute, profileRoute], +}); + +const HomeScreen = createRouteView({ + route: homeRoute, + view: () => Home Screen, +}); + +const SearchScreen = createRouteView({ + route: searchRoute, + view: () => Search Screen, +}); + +const ProfileScreen = createRouteView({ + route: profileRoute, + view: () => Profile Screen, +}); + +const TabsNavigator = createeffector/routerBottomTabsNavigator({ + router, + routes: [HomeScreen, SearchScreen, ProfileScreen], + screenOptions: { + tabBarActiveTintColor: '#007AFF', + tabBarInactiveTintColor: '#8e8e93', + tabBarIcon: ({ color, size }) => ( + + ), + }, +}); + +export default function App() { + return ( + + + + + + ); +} +``` + +## Configuration + +### `router` (required) + +effector/router Router instance created with `createRouter`. + +```tsx +const router = createRouter({ + routes: [homeRoute, searchRoute, profileRoute], +}); + +const TabsNavigator = + createeffector / + routerBottomTabsNavigator({ + router, + routes: [HomeScreen, SearchScreen, ProfileScreen], + }); +``` + +### `routes` (required) + +Array of route views created with `createRouteView` or `createLazyRouteView`. + +```tsx +const HomeScreen = createRouteView({ + route: homeRoute, + view: () => Home, +}); + +const TabsNavigator = + createeffector / + routerBottomTabsNavigator({ + router, + routes: [HomeScreen, SearchScreen, ProfileScreen], + }); +``` + +### `screenOptions` + +Options applied to all tabs. Accepts all React Navigation Bottom Tabs Navigator options. + +```tsx +const TabsNavigator = + createeffector / + routerBottomTabsNavigator({ + router, + routes: [HomeScreen, SearchScreen, ProfileScreen], + screenOptions: { + // Colors + tabBarActiveTintColor: '#007AFF', + tabBarInactiveTintColor: '#8e8e93', + tabBarActiveBackgroundColor: '#fff', + tabBarInactiveBackgroundColor: '#f8f8f8', + + // Styles + tabBarStyle: { + backgroundColor: '#fff', + borderTopColor: '#e0e0e0', + }, + tabBarLabelStyle: { + fontSize: 12, + fontWeight: '600', + }, + tabBarIconStyle: { + marginBottom: 4, + }, + + // Icon + tabBarIcon: ({ color, size, focused }) => ( + + ), + + // Badge + tabBarBadge: 3, + tabBarBadgeStyle: { backgroundColor: 'red' }, + + // Visibility + tabBarShowLabel: true, + }, + }); +``` + +See [React Navigation Bottom Tabs documentation](https://reactnavigation.org/docs/bottom-tab-navigator) for all available options. + +### `initialRouteName` + +Name of the route to render on initial render. + +```tsx +const TabsNavigator = + createeffector / + routerBottomTabsNavigator({ + router, + routes: [HomeScreen, SearchScreen, ProfileScreen], + initialRouteName: '/home', + }); +``` + +## Custom Tab Icons + +Customize icons per screen using `screenOptions` function: + +```tsx +const TabsNavigator = + createeffector / + routerBottomTabsNavigator({ + router, + routes: [HomeScreen, SearchScreen, ProfileScreen], + screenOptions: ({ route }) => ({ + tabBarIcon: ({ focused, color, size }) => { + let iconName; + + if (route.name === '/home') { + iconName = focused ? 'home' : 'home-outline'; + } else if (route.name === '/search') { + iconName = focused ? 'search' : 'search-outline'; + } else if (route.name === '/profile') { + iconName = focused ? 'person' : 'person-outline'; + } + + return ; + }, + }), + }); +``` + +## Navigation + +Navigate using effector/router Router route methods. Tab press events automatically trigger route opening: + +```tsx +// Open tab programmatically +homeRoute.open(); +searchRoute.open(); + +// Tab bar handles user taps automatically +// User taps → Tab press → effector/router Router opens route → UI updates +``` + +## Type Safety + +Route parameters are type-safe: + +```tsx +const userRoute = createRoute({ path: '/user/:id' }); +// Type: Route<{ id: string }> + +userRoute.open({ + params: { id: '123' }, // ✅ Type-safe +}); +``` diff --git a/docs/react-native/index.md b/docs/react-native/index.md new file mode 100644 index 0000000..e83c677 --- /dev/null +++ b/docs/react-native/index.md @@ -0,0 +1,175 @@ +# React Native + +React Native bindings for effector router with React Navigation integration. + +## Overview + +`@effector/router-react-native` bridges [Effector Router](https://router.effector.dev/)'s state management with [React Navigation](https://reactnavigation.org/)'s native UI components. This package allows you to: + +- Manage navigation state with effector router +- Render UI with React Navigation's native navigators +- Access the full React Navigation API and styling options +- Navigate declaratively through route events + +## How It Works + +``` +┌─────────────────┐ +│ effector router │ ← Manages state +└────────┬────────┘ + │ syncs + ▼ +┌─────────────────┐ +│ React Navigation│ ← Renders UI +│ (Stack/Tabs) │ +└─────────────────┘ +``` + +1. **Effector router** manages which routes are open, their parameters, and navigation state +2. **React Navigation** handles UI rendering, animations, gestures, and platform-specific behavior +3. The adapters **sync state** between both systems + +## Installation + +```bash +npm install @effector/router-react-native @effector/router @effector/router-react \ + @react-navigation/native @react-navigation/stack @react-navigation/bottom-tabs + +# Also install React Navigation dependencies +npm install react-native-screens react-native-safe-area-context +``` + +## Quick Example + +```tsx +import { NavigationContainer } from '@react-navigation/native'; +import { createStackNavigator } from '@effector/router-react-native'; +import { createRouter, createRoute } from '@effector/router'; +import { createRouteView, RouterProvider } from '@effector/router-react'; + +// 1. Define routes +const homeRoute = createRoute({ path: '/home' }); +const detailsRoute = createRoute({ path: '/details/:id' }); + +// 2. Create router +const router = createRouter({ + routes: [homeRoute, detailsRoute], +}); + +// 3. Create screens +const HomeScreen = createRouteView({ + route: homeRoute, + view: () => Home Screen, +}); + +const DetailsScreen = createRouteView({ + route: detailsRoute, + view: () => Details Screen, +}); + +// 4. Create navigator +const StackNavigator = createStackNavigator({ + router, + routes: [HomeScreen, DetailsScreen], + screenOptions: { + headerStyle: { backgroundColor: '#f4511e' }, + }, +}); + +// 5. Use in app +export default function App() { + return ( + + + + + + ); +} + +// 6. Navigate programmatically +homeRoute.open(); +detailsRoute.open({ params: { id: '123' } }); +``` + +## Available Navigators + +### [Stack Navigator](/react-native/stack-navigator) + +Full-screen navigation with stack-based transitions. Perfect for hierarchical navigation patterns. + +```tsx +import { createStackNavigator } from '@effector/router-react-native'; + +const StackNavigator = createStackNavigator({ + router, + routes: [HomeScreen, DetailsScreen], +}); +``` + +### [Bottom Tabs Navigator](/react-native/bottom-tabs-navigator) + +Tab-based navigation with a bottom tab bar. Ideal for primary app navigation. + +```tsx +import { createBottomTabsNavigator } from '@effector/router-react-native'; + +const TabsNavigator = createBottomTabsNavigator({ + router, + routes: [HomeTab, SearchTab, ProfileTab], +}); +``` + +## Navigation Approach + +All navigation happens through **Effector Router**, not React Navigation directly: + +```tsx +// ✅ Navigate via effector router +homeRoute.open(); +detailsRoute.open({ params: { id: '123' } }); + +// ❌ Don't use React Navigation's navigation prop +navigation.navigate('Details'); // Avoid this +``` + +This approach provides: + +- Centralized navigation logic +- Easy testing (trigger events in tests) +- State persistence +- Time-travel debugging + +## React Navigation Features + +While navigation is managed by Effector Router, you still get all React Navigation features: + +- Native animations and transitions +- Gesture handling (swipe back, etc.) +- Header customization +- Tab bar customization +- Deep linking support +- Screen options and configuration +- Platform-specific behavior + +## Type Safety + +Route parameters are automatically inferred: + +```tsx +const userRoute = createRoute({ path: '/user/:id/:tab' }); +// Type: Route<{ id: string; tab: string }> + +// ✅ Type-safe +userRoute.open({ params: { id: '123', tab: 'posts' } }); + +// ❌ TypeScript error +userRoute.open({ params: { id: 123 } }); // id must be string +``` + +## Next Steps + +- [Stack Navigator API](/react-native/stack-navigator) - Full-screen navigation +- [Bottom Tabs Navigator API](/react-native/bottom-tabs-navigator) - Tab-based navigation +- [Core Package](/core/create-router) - Learn about effector router core concepts +- [React Package](/react/create-route-view) - React-specific utilities diff --git a/docs/react-native/stack-navigator.md b/docs/react-native/stack-navigator.md new file mode 100644 index 0000000..bb7898e --- /dev/null +++ b/docs/react-native/stack-navigator.md @@ -0,0 +1,184 @@ +# Stack Navigator + +Creates a Stack Navigator that integrates effector/router Router with React Navigation's Stack Navigator. + +## Import + +```ts +import { createStackNavigator } from '@effector/router-react-native'; +``` + +## Usage + +```tsx +import { NavigationContainer } from '@react-navigation/native'; +import { createStackNavigator } from '@effector/router-react-native'; +import { createRouter, createRoute } from '@effector/router'; +import { createRouteView, RouterProvider } from '@effector/router-react'; + +const homeRoute = createRoute({ path: '/home' }); +const detailsRoute = createRoute({ path: '/details/:id' }); + +const router = createRouter({ + routes: [homeRoute, detailsRoute], +}); + +const HomeScreen = createRouteView({ + route: homeRoute, + view: () => ( + + Home Screen + ; +} +``` + +## Multiple Routers + +You can use multiple routers in different parts of your app: + +```tsx +import { RouterProvider } from '@effector/router-react'; + +const mainRouter = createRouter({ routes: [homeRoute, aboutRoute] }); +const adminRouter = createRouter({ routes: [dashboardRoute, usersRoute] }); + +function App() { + return ( +
+ + + + + + + +
+ ); +} +``` + +## Error Handling + +Components that use router hooks will throw an error if used outside RouterProvider: + +```tsx +function Component() { + // ❌ Error: Router not found. Add RouterProvider in app root + const router = useRouter(); +} +``` + +Always wrap your app with RouterProvider: + +```tsx +// ✅ Correct + + + +``` + +## Example: Complete Setup + +```tsx +import { createRoot } from 'react-dom/client'; +import { allSettled, fork } from 'effector'; +import { Provider } from 'effector-react'; +import { createBrowserHistory } from 'history'; +import { RouterProvider } from '@effector/router-react'; +import { historyAdapter } from '@effector/router'; +import { router } from './router'; +import { App } from './app'; + +const root = createRoot(document.getElementById('root')!); + +async function render() { + const scope = fork(); + const history = createBrowserHistory(); + + // Initialize router with history + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + root.render( + + + + + , + ); +} + +render(); +``` + +## See Also + +- [useRouter](./use-router) - Access router in components +- [createRouter](/core/create-router) - Create router instance +- [createRoutesView](./create-routes-view) - Render active routes diff --git a/docs/react/use-is-opened.md b/docs/react/use-is-opened.md new file mode 100644 index 0000000..f65a717 --- /dev/null +++ b/docs/react/use-is-opened.md @@ -0,0 +1,170 @@ +# useIsOpened + +Check if a route or router is currently active. + +## API + +```typescript +function useIsOpened(route: Route | Router): boolean; +``` + +### Parameters + +| Parameter | Type | Description | +| --------- | ----------------- | ---------------------------- | +| `route` | `Route \| Router` | The route or router to check | + +### Returns + +`boolean` - `true` if the route is opened or router has active routes, `false` otherwise. + +## Usage + +### Check Route State + +```tsx +import { useIsOpened } from '@effector/router-react'; +import { homeRoute, settingsRoute } from './routes'; + +function Navigation() { + const isHomeActive = useIsOpened(homeRoute); + const isSettingsActive = useIsOpened(settingsRoute); + + return ( + + ); +} +``` + +### Check Router State + +```tsx +import { useIsOpened } from '@effector/router-react'; +import { adminRouter } from './routers'; + +function AdminPanel() { + const isAdminActive = useIsOpened(adminRouter); + + if (!isAdminActive) { + return null; + } + + return
Admin Panel Content
; +} +``` + +### Conditional Rendering + +```tsx +import { useIsOpened } from '@effector/router-react'; +import { profileRoute } from './routes'; + +function UserMenu() { + const isProfileOpen = useIsOpened(profileRoute); + + return ( +
+ + {isProfileOpen && ( + + )} +
+ ); +} +``` + +### Active Tab Indicator + +```tsx +import { useIsOpened } from '@effector/router-react'; +import { dashboardRoute, reportsRoute, analyticsRoute } from './routes'; + +function TabNavigation() { + const tabs = [ + { route: dashboardRoute, label: 'Dashboard' }, + { route: reportsRoute, label: 'Reports' }, + { route: analyticsRoute, label: 'Analytics' }, + ]; + + return ( +
+ {tabs.map(({ route, label }) => { + const isActive = useIsOpened(route); + return ( + + ); + })} +
+ ); +} +``` + +## How It Works + +- For **routes**: Returns the value of `route.$isOpened` store +- For **routers**: Returns `true` if `router.$activeRoutes` has any items + +## Best Practices + +### Use for UI State Only + +`useIsOpened` is perfect for visual indicators and conditional styling: + +```tsx +function Sidebar() { + const isSettingsOpen = useIsOpened(settingsRoute); + + return ( + + ); +} +``` + +### Combine with Other Hooks + +```tsx +import { useIsOpened, useRouter } from '@effector/router-react'; + +function NavItem({ route, children }) { + const isActive = useIsOpened(route); + const router = useRouter(); + + return ( + + ); +} +``` + +### Performance + +The hook subscribes to Effector stores and automatically updates when the route state changes. It's optimized and safe to use in multiple components. + +## See Also + +- [Link](/react/link) - Navigation component with automatic active state +- [useRouter](/react/use-router) - Access router instance +- [useOpenedViews](/react/use-opened-views) - Get all currently opened route views diff --git a/docs/react/use-link.md b/docs/react/use-link.md new file mode 100644 index 0000000..efeccdf --- /dev/null +++ b/docs/react/use-link.md @@ -0,0 +1,304 @@ +# useLink + +Get path and navigation handler for a route. Useful for building custom link components. + +## API + +```typescript +function useLink( + to: Route, + params: T, +): { + path: string; + onOpen: (payload?: RouteOpenedPayload) => void; +}; +``` + +### Parameters + +| Parameter | Type | Description | +| --------- | ---------- | -------------------------------------- | +| `to` | `Route` | The target route | +| `params` | `T` | Route parameters for building the path | + +### Returns + +| Property | Type | Description | +| -------- | ---------- | ------------------------------------------ | +| `path` | `string` | The built URL path for the route | +| `onOpen` | `function` | Handler to open the route programmatically | + +## Usage + +### Basic Custom Link + +```tsx +import { useLink } from '@effector/router-react'; +import { userRoute } from './routes'; + +function CustomLink({ userId, children }) { + const { path, onOpen } = useLink(userRoute, { userId }); + + return ( + { + e.preventDefault(); + onOpen(); + }} + > + {children} + + ); +} + +// Usage +View Profile; +``` + +### Custom Navigation Component + +```tsx +import { useLink } from '@effector/router-react'; + +function NavButton({ route, params, icon, children }) { + const { path, onOpen } = useLink(route, params); + + return ( + + ); +} + +// Usage +} +> + Settings +; +``` + +### Route Without Parameters + +```tsx +import { useLink } from '@effector/router-react'; +import { homeRoute } from './routes'; + +function HomeButton() { + // Pass undefined or empty object for routes without params + const { path, onOpen } = useLink(homeRoute, undefined); + + return ( + { + e.preventDefault(); + onOpen(); + }} + > + Home + + ); +} +``` + +### Context Menu + +```tsx +import { useLink } from '@effector/router-react'; +import { productRoute, editProductRoute } from './routes'; + +function ProductContextMenu({ productId }) { + const viewLink = useLink(productRoute, { productId }); + const editLink = useLink(editProductRoute, { productId }); + + return ( + + ); +} +``` + +### With Additional Navigation Options + +```tsx +import { useLink } from '@effector/router-react'; +import { articleRoute } from './routes'; + +function ArticleCard({ articleId }) { + const { path, onOpen } = useLink(articleRoute, { articleId }); + + const handleClick = (e) => { + e.preventDefault(); + // Pass additional options to onOpen + onOpen({ + params: { articleId }, + query: { highlight: 'true' }, + // force: true, + }); + }; + + return ( + + ); +} +``` + +### Mobile Touch Handler + +```tsx +import { useLink } from '@effector/router-react'; +import { pageRoute } from './routes'; + +function TouchableItem({ pageId, children }) { + const { path, onOpen } = useLink(pageRoute, { pageId }); + + return ( +
onOpen()} role="link" tabIndex={0} data-href={path}> + {children} +
+ ); +} +``` + +### Prefetching Data + +```tsx +import { useLink } from '@effector/router-react'; +import { productRoute } from './routes'; + +function ProductPreview({ productId }) { + const { path, onOpen } = useLink(productRoute, { productId }); + + const handleMouseEnter = () => { + // Prefetch product data on hover + fetchProductData(productId); + }; + + return ( + { + e.preventDefault(); + onOpen(); + }} + onMouseEnter={handleMouseEnter} + > + View Product + + ); +} +``` + +## Error Handling + +The hook will throw an error if the route is not registered in the router: + +```tsx +import { useLink } from '@effector/router-react'; +import { unknownRoute } from './routes'; + +function BrokenLink() { + try { + const { path, onOpen } = useLink(unknownRoute, {}); + return ( + + Link + + ); + } catch (error) { + // Error: Route not found. Maybe it is not passed into createRouter? + console.error(error); + return Invalid link; + } +} +``` + +## Best Practices + +### Prefer Built-in Link Component + +For most cases, use the built-in [`Link`](/react/link) component: + +```tsx +import { Link } from '@effector/router-react'; + +// Prefer this + + View Profile +; + +// Over this +function CustomLink({ userId }) { + const { path, onOpen } = useLink(userRoute, { userId }); + return ( + + View Profile + + ); +} +``` + +### Use useLink When + +- Building custom navigation components with special behavior +- Integrating with third-party UI libraries +- Creating non-standard interactive elements +- Need direct access to the built path + +### Type Safety + +The hook is fully type-safe with route parameters: + +```typescript +import { createRoute } from '@effector/router'; +import { useLink } from '@effector/router-react'; + +const userRoute = createRoute<{ userId: string; tab?: string }>({ + path: '/user/:userId', +}); + +function UserLink() { + // ✅ Type-safe + const link = useLink(userRoute, { userId: '123' }); + + // ❌ TypeScript error: missing required param + const broken = useLink(userRoute, {}); + + // ✅ Optional params work + const withTab = useLink(userRoute, { userId: '123', tab: 'posts' }); +} +``` + +## See Also + +- [Link](/react/link) - Built-in navigation component +- [useRouter](/react/use-router) - Access router instance +- [useIsOpened](/react/use-is-opened) - Check if route is active diff --git a/docs/react/use-opened-views.md b/docs/react/use-opened-views.md new file mode 100644 index 0000000..4185447 --- /dev/null +++ b/docs/react/use-opened-views.md @@ -0,0 +1,209 @@ +# useOpenedViews + +Hook that returns the currently opened route views, filtering out parent routes when their children are active. + +## Import + +```ts +import { useOpenedViews } from '@effector/router-react'; +``` + +## Usage + +```tsx +import { useOpenedViews } from '@effector/router-react'; + +function CustomRoutesRenderer() { + const routes = [HomeScreen, ProfileScreen, SettingsScreen]; + const openedViews = useOpenedViews(routes); + + return ( +
+ {openedViews.map((view, index) => ( +
{createElement(view.view)}
+ ))} +
+ ); +} +``` + +## Parameters + +### `routes` (required) + +Array of route views to track: + +```tsx +const routes = [ + createRouteView({ route: homeRoute, view: HomeComponent }), + createRouteView({ route: profileRoute, view: ProfileComponent }), +]; + +const openedViews = useOpenedViews(routes); +``` + +## Return Value + +Returns an array of currently opened route views: + +```tsx +type RouteView = { + route: Route | Router; + view: React.FC; + children?: RouteView[]; +}; +``` + +The hook: + +- Filters routes to only those that are currently opened (`$isOpened === true`) +- Removes parent routes when their child routes are active +- Updates automatically when route state changes + +## How It Works + +### Parent Filtering + +When a child route is active, its parent is filtered out: + +```tsx +const profileRoute = createRoute({ path: '/profile' }); +const settingsRoute = createRoute({ path: '/settings', parent: profileRoute }); + +const routes = [ + createRouteView({ route: profileRoute, view: ProfileComponent }), + createRouteView({ route: settingsRoute, view: SettingsComponent }), +]; + +// When settingsRoute.open() is called: +const openedViews = useOpenedViews(routes); +// Returns: [{ route: settingsRoute, view: SettingsComponent }] +// ProfileComponent is filtered out because its child is active +``` + +### Multiple Active Routes + +Multiple sibling routes can be active simultaneously: + +```tsx +const dialogRoute = createRoute(); // Pathless route +const modalRoute = createRoute(); // Pathless route + +const routes = [ + createRouteView({ route: homeRoute, view: HomeComponent }), + createRouteView({ route: dialogRoute, view: DialogComponent }), + createRouteView({ route: modalRoute, view: ModalComponent }), +]; + +// When multiple routes are open: +homeRoute.open(); +dialogRoute.open(); +modalRoute.open(); + +const openedViews = useOpenedViews(routes); +// Returns: [HomeComponent, DialogComponent, ModalComponent] +``` + +## Custom Implementation + +Use this hook to build custom route renderers: + +### Stack Renderer + +```tsx +function StackRenderer({ routes }) { + const openedViews = useOpenedViews(routes); + + return ( +
+ {openedViews.map((view, index) => { + const isTop = index === openedViews.length - 1; + return ( +
+ {createElement(view.view)} +
+ ); + })} +
+ ); +} +``` + +### Layered Renderer + +```tsx +function LayeredRenderer({ routes }) { + const openedViews = useOpenedViews(routes); + + return ( +
+ {openedViews.map((view, index) => ( +
+ {createElement(view.view)} +
+ ))} +
+ ); +} +``` + +### Animated Transitions + +```tsx +import { AnimatePresence, motion } from 'framer-motion'; + +function AnimatedRoutesRenderer({ routes }) { + const openedViews = useOpenedViews(routes); + const activeView = openedViews.at(-1); + + return ( + + {activeView && ( + + {createElement(activeView.view)} + + )} + + ); +} +``` + +## With Effector Scope + +The hook automatically works with Effector scope when used inside `Provider`: + +```tsx +import { Provider, fork } from 'effector-react'; + +function App() { + const scope = fork(); + + return ( + + + + + + ); +} +``` + +## Reactivity + +The hook re-renders when: + +- A route opens or closes +- A route's `$isOpened` store changes +- Router's `$activeRoutes` changes (for Router items) + +## See Also + +- [createRoutesView](./create-routes-view) - Built-in routes renderer (uses this hook) +- [Outlet](./outlet) - Render nested routes (uses this hook) +- [createRouteView](./create-route-view) - Create route views +- [RouterProvider](./router-provider) - Provide router to React tree diff --git a/docs/react/use-router.md b/docs/react/use-router.md new file mode 100644 index 0000000..8d5fb9e --- /dev/null +++ b/docs/react/use-router.md @@ -0,0 +1,189 @@ +# useRouter + +Hook to access the router instance from `RouterProvider` context. Returns the router with all stores bound to their current values using `useUnit`. + +## Import + +```ts +import { useRouter } from '@effector/router-react'; +``` + +## Usage + +```tsx +import { useRouter } from '@effector/router-react'; + +function Navigation() { + const router = useRouter(); + + return ; +} +``` + +## Return Value + +Returns the router instance with all store values automatically bound (not stores themselves): + +```tsx +function Component() { + const router = useRouter(); + + // Access current values (NOT stores) + router.$path; // string (current path value) + router.$query; // Query (current query value) + router.$activeRoutes; // Route[] (current active routes) + + // Navigation methods + router.back(); // Go back + router.forward(); // Go forward + router.navigate({ path: '/home' }); + + // ...other router properties +} +``` + +::: info +`useRouter()` uses `useUnit` internally to bind all stores to their values. The component will automatically re-render when any store value changes. +::: + +## Examples + +### Navigate Back/Forward + +```tsx +import { useRouter } from '@effector/router-react'; + +function HistoryControls() { + const router = useRouter(); + + return ( +
+ + +
+ ); +} +``` + +### Custom Navigation + +```tsx +import { useRouter } from '@effector/router-react'; + +function SearchForm() { + const router = useRouter(); + const [query, setQuery] = useState(''); + + const handleSubmit = () => { + router.navigate({ + path: '/search', + query: { q: query }, + }); + }; + + return ( +
+ setQuery(e.target.value)} /> + +
+ ); +} +``` + +### Track Active Routes + +```tsx +import { useRouter } from '@effector/router-react'; + +function Breadcrumbs() { + const router = useRouter(); + // router.$activeRoutes is already the value, not a store + const activeRoutes = router.$activeRoutes; + + return ( + + ); +} +``` + +### Read Current Path + +```tsx +import { useRouter } from '@effector/router-react'; + +function CurrentPath() { + const router = useRouter(); + // router.$path is already the value, not a store + const path = router.$path; + + return
Current path: {path}
; +} +``` + +## Error Handling + +The hook throws an error if used outside of `RouterProvider`: + +```tsx +function Component() { + // ❌ Error: Router not found. Add RouterProvider in app root + const router = useRouter(); +} +``` + +Always wrap your app with `RouterProvider`: + +```tsx +import { RouterProvider } from '@effector/router-react'; + +function App() { + return ( + + + + ); +} +``` + +## See Also + +- [useRouterContext](#userouter-context) - Access router without Effector binding +- [RouterProvider](./router-provider) - Provide router to React tree +- [Link](./link) - Navigation component +- [createRouter](/core/create-router) - Create router instance + +## useRouterContext + +Alternative hook that returns the raw router with stores (not values): + +```ts +import { useRouterContext } from '@effector/router-react'; +``` + +```tsx +import { useRouterContext } from '@effector/router-react'; +import { useUnit } from 'effector-react'; + +function Component() { + const router = useRouterContext(); + + // router.$path is a Store, must bind with useUnit + const path = useUnit(router.$path); + + // Or bind multiple stores at once + const { $path, $query, $activeRoutes } = useUnit({ + $path: router.$path, + $query: router.$query, + $activeRoutes: router.$activeRoutes, + }); +} +``` + +**When to use:** + +- Use `useRouter()` - for most cases (automatically bound values) +- Use `useRouterContext()` - when you need raw stores for custom Effector patterns or selective binding diff --git a/docs/react/with-layout.md b/docs/react/with-layout.md new file mode 100644 index 0000000..5b5d8d8 --- /dev/null +++ b/docs/react/with-layout.md @@ -0,0 +1,187 @@ +# withLayout + +Utility function to wrap multiple route views with a shared layout component. + +## Import + +```ts +import { withLayout } from '@effector/router-react'; +``` + +## Usage + +```tsx +import { + withLayout, + createRouteView, + createRoutesView, +} from '@effector/router-react'; +import { MainLayout } from './layouts'; + +function MainLayout({ children }) { + return ( +
+
Header
+
{children}
+
Footer
+
+ ); +} + +const RoutesView = createRoutesView({ + routes: [ + ...withLayout(MainLayout, [ + createRouteView({ route: homeRoute, view: HomeComponent }), + createRouteView({ route: aboutRoute, view: AboutComponent }), + createRouteView({ route: contactRoute, view: ContactComponent }), + ]), + // Routes without layout + createRouteView({ route: loginRoute, view: LoginComponent }), + ], +}); +``` + +## Multiple Layouts + +Apply different layouts to different route groups: + +```tsx +import { MainLayout, AdminLayout, AuthLayout } from './layouts'; + +const RoutesView = createRoutesView({ + routes: [ + ...withLayout(MainLayout, [ + createRouteView({ route: homeRoute, view: HomeComponent }), + createRouteView({ route: aboutRoute, view: AboutComponent }), + ]), + ...withLayout(AdminLayout, [ + createRouteView({ route: dashboardRoute, view: DashboardComponent }), + createRouteView({ route: usersRoute, view: UsersComponent }), + ]), + ...withLayout(AuthLayout, [ + createRouteView({ route: loginRoute, view: LoginComponent }), + createRouteView({ route: signupRoute, view: SignupComponent }), + ]), + ], +}); +``` + +## Parameters + +### `layout` (required) + +Layout component that accepts `children` prop: + +```tsx +import { type ReactNode } from 'react'; + +function MyLayout({ children }: { children: ReactNode }) { + return ( +
+ +
{children}
+
+ ); +} + +withLayout(MyLayout, routes); +``` + +### `views` (required) + +Array of route views created with `createRouteView` or `createLazyRouteView`: + +```tsx +withLayout(MainLayout, [ + createRouteView({ route: homeRoute, view: HomeComponent }), + createLazyRouteView({ route: profileRoute, view: () => import('./Profile') }), +]); +``` + +## Return Value + +Returns an array of route views with the layout applied: + +```tsx +const wrappedRoutes = withLayout(MainLayout, [ + createRouteView({ route: homeRoute, view: HomeComponent }), +]); + +// Returns: +// [ +// { +// route: homeRoute, +// view: () => +// } +// ] +``` + +## Nested Layouts + +Layouts can be nested by applying `withLayout` multiple times: + +```tsx +function OuterLayout({ children }) { + return ( +
+ + {children} +
+ ); +} + +function InnerLayout({ children }) { + return ( +
+ + {children} +
+ ); +} + +const RoutesView = createRoutesView({ + routes: withLayout(OuterLayout, [ + createRouteView({ route: homeRoute, view: HomeComponent }), + ...withLayout(InnerLayout, [ + createRouteView({ route: dashboardRoute, view: DashboardComponent }), + createRouteView({ route: settingsRoute, view: SettingsComponent }), + ]), + ]), +}); +``` + +## Alternative: Layout in Route View + +You can also specify layout directly in `createRouteView`: + +```tsx +// Using withLayout +withLayout(MainLayout, [ + createRouteView({ route: homeRoute, view: HomeComponent }), +]); + +// Same as: +createRouteView({ + route: homeRoute, + view: HomeComponent, + layout: MainLayout, +}); +``` + +**Use `withLayout` when:** + +- Multiple routes share the same layout +- You want to group routes visually in code +- You need to apply layouts conditionally + +**Use `layout` prop when:** + +- Only one route uses the layout +- Each route has a different layout +- Layout is closely tied to the specific route + +## See Also + +- [createRouteView](./create-route-view) - Create route views (with layout prop) +- [createLazyRouteView](./create-lazy-route-view) - Lazy-loaded route views +- [createRoutesView](./create-routes-view) - Render active routes diff --git a/docs/vite.config.ts b/docs/vite.config.ts new file mode 100644 index 0000000..ec13ef9 --- /dev/null +++ b/docs/vite.config.ts @@ -0,0 +1,27 @@ +import { defineConfig } from 'vite'; +import Unocss from 'unocss/vite'; +import { presetAttributify, presetIcons, presetUno } from 'unocss'; + +export default defineConfig({ + optimizeDeps: { + exclude: ['@vueuse/core', 'vitepress'], + }, + server: { + hmr: { + overlay: false, + }, + }, + plugins: [ + Unocss({ + presets: [ + presetUno({ + dark: 'media', + }), + presetAttributify(), + presetIcons({ + scale: 1.2, + }), + ], + }), + ], +}); diff --git a/eslint.config.mts b/eslint.config.mts new file mode 100644 index 0000000..ea2bf4f --- /dev/null +++ b/eslint.config.mts @@ -0,0 +1,55 @@ +import { defineConfig, globalIgnores } from 'eslint/config'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import js from '@eslint/js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export default defineConfig([ + globalIgnores(['**/dist/**', 'docs/.vitepress/cache/**', '.prettierrc.js']), + js.configs.recommended, + tseslint.configs.recommended, + tseslint.configs.recommendedTypeChecked, + eslintPluginPrettierRecommended, + { + languageOptions: { + globals: { + ...globals.node, + ...globals.jest, + }, + + parserOptions: { + projectService: { + allowDefaultProject: [ + 'docs/.vitepress/*.mts', + 'docs/.vitepress/theme/index.ts', + ], + }, + root: __dirname, + }, + }, + + rules: { + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': ['warn', {}], + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + + 'prettier/prettier': [ + 'error', + { + endOfLine: 'auto', + }, + ], + }, + }, +]); diff --git a/package.json b/package.json new file mode 100644 index 0000000..2a1d6f9 --- /dev/null +++ b/package.json @@ -0,0 +1,56 @@ +{ + "name": "@effect/router-root", + "private": true, + "devDependencies": { + "@babel/core": "7.29.0", + "@babel/preset-env": "7.29.0", + "@babel/preset-react": "7.28.5", + "@babel/preset-typescript": "7.28.5", + "@changesets/cli": "^2.30.0", + "@eslint/eslintrc": "^3.3.4", + "@eslint/js": "^10.0.1", + "@react-navigation/bottom-tabs": "7.15.5", + "@react-navigation/native": "7.1.33", + "@react-navigation/stack": "7.8.4", + "@testing-library/dom": "^10.4.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/react": "19.2.14", + "@typescript-eslint/parser": "8.56.1", + "@vitejs/plugin-react": "^4.3.4", + "effector": "^23.4.4", + "effector-react": "^23.3.0", + "eslint": "10.0.2", + "eslint-config-prettier": "10.1.8", + "eslint-plugin-prettier": "5.5.5", + "globals": "^17.4.0", + "happy-dom": "^20.8.3", + "history": "5.3.0", + "jiti": "^2.6.1", + "patronum": "^2.3.0", + "prettier": "3.8.1", + "query-string": "^9.3.1", + "react": "19.2.4", + "react-dom": "19.2.4", + "react-native": "0.84.1", + "typescript-eslint": "^8.56.1", + "vite": "7.3.1", + "vite-plugin-babel": "1.5.1", + "vite-plugin-dts": "4.5.4", + "vitepress": "1.6.4", + "vitest": "4.0.18", + "zod": "^4.3.6" + }, + "scripts": { + "lint": "eslint", + "build": "pnpm run -recursive -filter \"./packages/*\" build", + "test": "pnpm run -recursive -filter \"./packages/*\" test -- --silent", + "typecheck": "tsc --noEmit", + "release": "pnpm -r publish", + ":docs": "pnpm --filter @effector/router-docs", + ":react": "pnpm --filter @effector/router-react", + ":react-native": "pnpm --filter @effector/router-react-native", + ":paths": "pnpm --filter @effector/router-paths", + ":core": "pnpm --filter @effector/router" + } +} diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 0000000..68a0610 --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1 @@ +# ☄️ effector/router diff --git a/packages/core/babel.config.json b/packages/core/babel.config.json new file mode 100644 index 0000000..9d1ac08 --- /dev/null +++ b/packages/core/babel.config.json @@ -0,0 +1,21 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "modules": false, + "targets": "> 0.25%, not dead" + } + ], + "@babel/preset-typescript" + ], + "plugins": [ + [ + "effector/babel-plugin", + { + "factories": ["lib/create-route", "lib/create-router", "lib/form/form"], + "addNames": true + } + ] + ] +} diff --git a/packages/core/lib/adapters/history-adapter.ts b/packages/core/lib/adapters/history-adapter.ts new file mode 100644 index 0000000..00af4eb --- /dev/null +++ b/packages/core/lib/adapters/history-adapter.ts @@ -0,0 +1,22 @@ +import type { History } from 'history'; +import type { RouterAdapter } from './types'; + +export function historyAdapter(history: History): RouterAdapter { + return { + location: history.location, + + push: history.push.bind(history), + replace: history.replace.bind(history), + + goBack: history.back.bind(history), + goForward: history.forward.bind(history), + + listen: (callback) => { + const unlisten = history.listen(({ location }) => callback(location)); + + return Object.assign(unlisten, { + unsubscribe: unlisten, + }); + }, + }; +} diff --git a/packages/core/lib/adapters/index.ts b/packages/core/lib/adapters/index.ts new file mode 100644 index 0000000..87f14ec --- /dev/null +++ b/packages/core/lib/adapters/index.ts @@ -0,0 +1,3 @@ +export { historyAdapter } from './history-adapter'; +export { queryAdapter } from './query-adapter'; +export * from './types'; diff --git a/packages/core/lib/adapters/query-adapter.ts b/packages/core/lib/adapters/query-adapter.ts new file mode 100644 index 0000000..e762ade --- /dev/null +++ b/packages/core/lib/adapters/query-adapter.ts @@ -0,0 +1,60 @@ +import type { History } from 'history'; +import type { RouterAdapter, To } from './types'; + +function extractLocation(location: History['location']) { + const url = new URL(decodeURIComponent(location.search)); + + return { + pathname: url.pathname, + search: url.search, + hash: url.hash, + }; +} + +export function queryAdapter(history: History): RouterAdapter { + return { + location: extractLocation(history.location), + + push: (to: To) => { + if (typeof to === 'string') { + const url = new URL(history.location.pathname); + url.search = to; + history.push(url.toString()); + } else { + const url = new URL(history.location.pathname); + url.search = `${to.pathname ?? ''}${to.search ?? ''}${to.hash ?? ''}`; + history.push(url.toString()); + } + }, + + replace: (to: To) => { + if (typeof to === 'string') { + const url = new URL(history.location.pathname); + url.search = to; + history.replace(url.toString()); + } else { + const url = new URL(history.location.pathname); + url.search = `${to.pathname ?? ''}${to.search ?? ''}${to.hash ?? ''}`; + history.replace(url.toString()); + } + }, + + goBack: () => { + history.back(); + }, + + goForward: () => { + history.forward(); + }, + + listen: (callback) => { + const unlisten = history.listen(({ location }) => + callback(extractLocation(location)), + ); + + return Object.assign(unlisten, { + unsubscribe: unlisten, + }); + }, + }; +} diff --git a/packages/core/lib/adapters/types.ts b/packages/core/lib/adapters/types.ts new file mode 100644 index 0000000..0449c13 --- /dev/null +++ b/packages/core/lib/adapters/types.ts @@ -0,0 +1,23 @@ +import type { Subscription } from 'effector'; + +export interface RouterLocation { + pathname: string; + search: string; + hash: string; +} + +export type To = string | Partial; + +type ListenCallback = (location: RouterLocation) => void; + +export interface RouterAdapter { + location: RouterLocation; + + push: (to: To) => void; + replace: (to: To) => void; + + goBack: () => void; + goForward: () => void; + + listen: (callback: ListenCallback) => Subscription; +} diff --git a/packages/core/lib/chain-route.ts b/packages/core/lib/chain-route.ts new file mode 100644 index 0000000..8f7292e --- /dev/null +++ b/packages/core/lib/chain-route.ts @@ -0,0 +1,138 @@ +import { + createEffect, + sample, + type Effect, + type EventCallable, + type Unit, +} from 'effector'; +import type { + AsyncBundleImport, + OpenPayloadBase, + Route, + RouteOpenedPayload, + VirtualRoute, +} from './types'; +import { createVirtualRoute } from './create-virtual-route'; + +type BeforeOpenUnit = + | (T extends void + ? EventCallable | EventCallable + : EventCallable<{ params: T } & OpenPayloadBase>) + | Effect, any>; + +interface ChainRouteProps { + route: Route | VirtualRoute, T>; + beforeOpen: BeforeOpenUnit | BeforeOpenUnit[]; + openOn?: Unit | Unit[]; + cancelOn?: Unit | Unit[]; +} + +/** + * @link https://router.effector.dev/core/chain-route.html + * @param props Chain route props + * @returns `Virtual route` + * @example ```ts + * import { createRoute, chainRoute } from '@effector/router'; + * import { createEvent, createEffect } from 'effector'; + * + * // base example + * const route = createRoute({ path: '/profile' }); + * + * const authorized = createEvent(); + * const rejected = createEvent(); + * + * const checkAuthorizationFx = createEffect(async ({ params }) => { + * // some logic + * }); + * + * sample({ + * clock: checkAuthorizationFx.doneData, + * target: authorized, + * }); + * + * sample({ + * clock: checkAuthorizationFx.failData, + * target: rejected, + * }); + * + * const virtual = chainRoute({ + * route, + * beforeOpen: checkAuthorizationFx, + * openOn: authorized, + * cancelOn: rejected, + * }); + * + * // chain already chained routes + * const postRoute = createRoute({ path: '/post/:id' }); + * const authorizedRoute = chainRoute({ route: postRoute, ... }); + * const postLoadedRoute = chainRoute({ route: authorizedRoute, ... }); + * ``` + */ +export function chainRoute( + props: ChainRouteProps, +): VirtualRoute, T> { + const { route, beforeOpen, openOn, cancelOn } = props; + + let asyncImport: AsyncBundleImport; + + const waitForAsyncBundleFx = createEffect(() => asyncImport?.()); + + const openFx = createEffect(async (payload: RouteOpenedPayload) => { + await waitForAsyncBundleFx(); + + for (const trigger of ([]>[]).concat(beforeOpen)) { + // @ts-expect-error -- ts works very awful with this generics + await trigger(payload); + } + }); + + const transformer = (payload: RouteOpenedPayload): T => { + if (!payload) { + return {} as T; + } + + return 'params' in payload ? (payload.params as T) : ({} as T); + }; + + const virtualRoute = createVirtualRoute, T>({ + transformer, + }); + + sample({ + clock: route.opened, + target: openFx, + }); + + sample({ + clock: route.opened, + fn: transformer, + target: virtualRoute.$params, + }); + + if (openOn) { + sample({ + clock: openOn as Unit[], + source: virtualRoute.$params, + fn: (params) => ({ params }) as unknown as RouteOpenedPayload, + target: virtualRoute.open, + }); + } + + if (cancelOn) { + sample({ + clock: ([]>[route.closed]).concat(cancelOn), + target: virtualRoute.close, + }); + + sample({ + clock: ([]>[]).concat(cancelOn), + target: virtualRoute.cancelled as EventCallable, + }); + } + + return Object.assign(virtualRoute, { + internal: { + setAsyncImport: (value: AsyncBundleImport) => (asyncImport = value), + }, + }); +} diff --git a/packages/core/lib/create-route.ts b/packages/core/lib/create-route.ts new file mode 100644 index 0000000..396cd15 --- /dev/null +++ b/packages/core/lib/create-route.ts @@ -0,0 +1,238 @@ +import { + attach, + createEffect, + createEvent, + createStore, + sample, + type Effect, +} from 'effector'; +import type { + AsyncBundleImport, + InternalRoute, + PathlessRoute, + PathRoute, + Route, + RouteOpenedPayload, +} from './types'; + +import { ParseUrlParams, ValidatePath } from '@effector/router-paths'; +import { createAction } from 'effector-action'; + +type WithBaseRouteConfig = T & { + parent?: Route; + beforeOpen?: Effect[]; +}; + +export type CreateRouteConfig = + ValidatePath extends ['invalid', infer Template] + ? WithBaseRouteConfig<{ + path: Template; + }> + : WithBaseRouteConfig<{ + path: Path; + parent?: Route; + beforeOpen?: Effect[]; + }>; +/** + * @description Creates route + * @param config Route config + * @returns `Route\` + * @link https://router.effector.dev/core/create-route.html + * @example ```ts + * import { createRoute } from '@effector/router'; + * + * // basic + * const route = createRoute({ path: '/route' }); + * route.open(); + * + * // with params + * const postRoute = createRoute({ path: '/post/:id' }); + * // ^--- Route<{ id: string }> + * + * // with parent + * const profile = createRoute({ path: '/profile/:id' }); + * + * const friends = createRoute({ path: '/friends', parent: profile }); + * const posts = createRoute({ path: '/posts', parent: profile }); + * + * posts.open(); // profile.$isOpened -> true, posts.$isOpened -> true + * ``` + */ +export function createRoute< + T extends string, + Params extends object | void = ParseUrlParams, +>(config: CreateRouteConfig): PathRoute; +export function createRoute( + config?: WithBaseRouteConfig, +): PathlessRoute; +export function createRoute( + config: + | WithBaseRouteConfig + | CreateRouteConfig = {} as WithBaseRouteConfig, +): PathRoute | PathlessRoute { + let asyncImport: AsyncBundleImport; + + const beforeOpen = config.beforeOpen ?? []; + + const openFx = createEffect(async (payload) => { + await waitForAsyncBundleFx(); + await beforeOpenFx(); + + const parent = config.parent as InternalRoute | undefined; + + if (parent) { + await parent.internal.openFx({ + ...(payload ?? { params: {} }), + navigate: false, + }); + } + + return payload; + }); + + const forceOpenParentFx = createEffect( + async (payload) => { + const parent = config.parent as InternalRoute | undefined; + + if (parent) { + await parent.internal.forceOpenParentFx({ + ...(payload ?? { params: {} }), + navigate: false, + }); + } + + return payload; + }, + ); + + const navigatedFx = attach({ effect: openFx }); + + type OpenPayload = RouteOpenedPayload; + + const $params = createStore({} as Params); + + const $isOpened = createStore(false); + const $isPending = openFx.pending; + + const open = createEvent(); + const close = createEvent(); + + const opened = createEvent(); + const openedOnServer = createEvent(); + const openedOnClient = createEvent(); + + const navigated = createEvent(); + + const closed = createEvent(); + + const waitForAsyncBundleFx = createEffect(() => asyncImport?.()); + + const beforeOpenFx = createEffect(async () => { + for (const fx of beforeOpen) { + await fx(); + } + }); + + const defaultParams = {} as Params; + + sample({ + clock: open, + target: openFx, + }); + + sample({ + clock: navigated, + fn: (payload) => ({ navigate: false, ...payload }), + target: navigatedFx, + }); + + sample({ + clock: navigatedFx.done, + fn: ({ params }) => params, + target: forceOpenParentFx, + }); + + createAction({ + clock: forceOpenParentFx.doneData, + target: { $params }, + fn: (target, payload) => { + if (!payload) { + return target.$params(defaultParams); + } + + return target.$params( + 'params' in payload ? { ...payload.params } : defaultParams, + ); + }, + }); + + sample({ + clock: navigatedFx.failData, + fn: () => defaultParams, + target: $params, + }); + + createAction({ + clock: forceOpenParentFx.doneData, + target: { + openedOnServer, + openedOnClient, + }, + fn: (target, payload) => { + if (typeof window === 'undefined') { + return target.openedOnServer(payload); + } + + return target.openedOnClient(payload); + }, + }); + + // @ts-expect-error TS is very stupid + sample({ + clock: [openedOnClient, openedOnServer], + target: opened, + }); + + sample({ + clock: close, + target: closed, + }); + + sample({ + clock: [opened.map(() => true), closed.map(() => false)], + target: $isOpened, + }); + + return { + $params, + $isOpened, + $isPending, + + // @ts-expect-error :(( + open, + closed, + opened, + openedOnClient, + openedOnServer, + + ...config, + + internal: { + navigated, + close, + openFx, + forceOpenParentFx, + + setAsyncImport: (value: AsyncBundleImport) => (asyncImport = value), + }, + + '@@unitShape': () => ({ + params: $params, + isPending: $isPending, + isOpened: $isOpened, + + // @ts-expect-error :(( + onOpen: open, + }), + }; +} diff --git a/packages/core/lib/create-router-controls.ts b/packages/core/lib/create-router-controls.ts new file mode 100644 index 0000000..bd105fa --- /dev/null +++ b/packages/core/lib/create-router-controls.ts @@ -0,0 +1,205 @@ +import { + attach, + createEvent, + createStore, + sample, + type Subscription, +} from 'effector'; + +import queryString from 'query-string'; + +import type { + LocationState, + NavigatePayload, + Query, + RouterControls, +} from './types'; +import { trackQueryControlsFactory } from './track-query'; +import type { RouterAdapter } from './adapters'; + +import { createAsyncAction } from 'effector-action'; + +/** + * @description Creates router controls + * @returns `RouterControls` + * @link https://router.effector.dev/core/create-router-controls.html + * + * `be careful! router controls need to be initialzed with setHistory event, + * which requires memory or browser history from history package.` + * + * @example ```ts + * import { createRouterControls } from '@effector/router'; + * + * // create controls + * const controls = createRouterControls(); + * + * // override path or query + * sample({ + * clock: goToPage, + * fn: () => ({ path: '/page' }), + * target: controls.navigate, + * }); + * + * sample({ + * clock: addQuery, + * fn: () => ({ query: { param1: 'hello', params2: [1, 2] } }), + * target: controls.navigate, + * }); + * + * ``` + */ +export function createRouterControls(): RouterControls { + const $history = createStore(null, { + serialize: 'ignore', + }); + + const $locationState = createStore({ + query: {}, + path: null as unknown as string, + }); + + const $subscription = createStore(null); + + const $query = $locationState.map((state) => state.query); + const $path = $locationState.map((state) => state.path); + + const setHistory = createEvent(); + const navigate = createEvent(); + + const back = createEvent(); + const forward = createEvent(); + + const locationUpdated = createEvent<{ + pathname: string; + query: Query; + }>(); + + const navigateFx = attach({ + source: $history, + effect: (history, { path, query, replace }: NavigatePayload) => { + if (!history) { + throw new Error('history not found'); + } + + const payload = { + pathname: path, + search: `?${queryString.stringify(query)}`, + }; + + if (replace) { + history.replace(payload); + } else { + history.push(payload); + } + }, + }); + + const subscribeHistoryFx = createAsyncAction({ + target: { + locationUpdated, + $subscription, + }, + source: { $subscription }, + fn: async (target, getSource, history: RouterAdapter | null) => { + if (!history) { + throw Error( + 'Cannot initialize router controls with empty history adapter. Please provide some provider or check your code for passing of nullable value', + ); + } + + const source = await getSource(); + + if (source.subscription) { + source.subscription.unsubscribe(); + } + + target.locationUpdated({ + pathname: history.location.pathname, + query: { ...queryString.parse(history.location.search) }, + }); + + target.$subscription( + history.listen((location) => { + target.locationUpdated({ + pathname: location.pathname, + query: { ...queryString.parse(location.search) }, + }); + }), + ); + }, + }); + + const goBackFx = attach({ + source: $history, + effect: (history) => { + if (!history) { + throw new Error('history not found'); + } + + history.goBack(); + }, + }); + + const goForwardFx = attach({ + source: $history, + effect: (history) => { + if (!history) { + throw new Error('history not found'); + } + + history.goForward(); + }, + }); + + sample({ + clock: setHistory, + target: $history, + }); + + sample({ + clock: $history, + filter: Boolean, + target: subscribeHistoryFx, + }); + + sample({ + clock: locationUpdated, + fn: (location) => ({ + path: location.pathname, + query: location.query, + }), + target: $locationState, + }); + + sample({ + clock: navigate, + source: $path, + fn: (path, payload) => ({ path, ...payload }), + target: navigateFx, + }); + + sample({ + clock: back, + target: goBackFx, + }); + + sample({ + clock: forward, + target: goForwardFx, + }); + + return { + $history, + $locationState, + $query, + $path, + + setHistory, + navigate, + back, + forward, + locationUpdated, + + trackQuery: trackQueryControlsFactory({ $query, navigate }), + }; +} diff --git a/packages/core/lib/create-router.ts b/packages/core/lib/create-router.ts new file mode 100644 index 0000000..9c61642 --- /dev/null +++ b/packages/core/lib/create-router.ts @@ -0,0 +1,313 @@ +import { attach, createEvent, sample, scopeBind } from 'effector'; +import type { + InternalPathlessRoute, + InternalPathRoute, + InternalRoute, + InternalRouter, + MappedRoute, + PathlessRoute, + PathRoute, + Route, + Router, + RouterControls, +} from './types'; +import { trackQueryFactory } from './track-query'; + +import { compile } from '@effector/router-paths'; +import { createRouterControls } from './create-router-controls'; +import { createAction } from 'effector-action'; +import { is } from './utils'; + +type InputRoute = + | PathRoute + | { path: string; route: PathlessRoute } + | Router; + +interface RouterConfig { + base?: string; + routes: InputRoute[]; + controls?: RouterControls; +} + +const inputIs = { + pathlessRoute( + route: InputRoute, + ): route is { path: string; route: PathlessRoute } { + return 'route' in route; + }, + + pathRoute(route: InputRoute): route is PathRoute { + return !this.pathlessRoute(route) && !this.router(route); + }, + + router(route: InputRoute): route is Router { + return is.router(route); + }, +}; + +/** + * @description Creates router + * @param config Router config + * @returns `Router` + * @link https://router.effector.dev/core/create-router.html + * + * `be careful! router need to be initialzed with setHistory event, + * which requires memory or browser history from history package.` + * + * @example ```ts + * import { createRouter } from '@effector/router'; + * import { routes } from './routes'; + * + * // create router + * const router = createRouter({ + * routes: [routes.route1, routes.route2], + * }); + * + * // override path or query + * sample({ + * clock: goToPage, + * fn: () => ({ path: '/page' }), + * target: router.navigate, + * }); + * + * sample({ + * clock: addQuery, + * fn: () => ({ query: { param1: 'hello', params2: [1, 2] } }), + * target: router.navigate, + * }); + * + * ``` + */ +export function createRouter(config: RouterConfig): Router { + const { base = '/', routes } = config; + const { + $path, + $query, + $history, + back, + forward, + navigate, + setHistory, + locationUpdated, + } = config.controls ?? createRouterControls(); + + function getPathWithBase(path: string) { + if (base === '/') { + return path; + } + + return path === '/' ? base : `${base}${path}`; + } + + const connectToParentRouter = createEvent(); + + let parent: Router | null = null; + + const knownRoutes: MappedRoute[] = []; + + function mapRoute(inputRoute: InputRoute): MappedRoute | null { + if (inputIs.pathlessRoute(inputRoute)) { + const { build, parse } = compile( + getPathWithBase(inputRoute.path), + ); + + const route = { + route: inputRoute.route as InternalPathlessRoute, + path: inputRoute.path, + build, + parse, + }; + + return route; + } + + if (inputIs.router(inputRoute)) { + sample({ + clock: setHistory, + target: inputRoute.setHistory, + }); + + return null; + } + + let internalRoute = inputRoute as InternalPathRoute; + const path: string[] = []; + + path.unshift(internalRoute.path); + + while (internalRoute.parent) { + if (is.pathlessRoute(internalRoute.parent)) { + break; + } + + internalRoute = internalRoute.parent as InternalPathRoute; + + if (internalRoute.path !== '/') { + path.unshift(internalRoute.path); + } + } + + const joinedPath = getPathWithBase(path.join('')); + + const { build, parse } = compile(joinedPath); + + const route = { + route: inputRoute as InternalRoute, + path: joinedPath, + build, + parse, + }; + + return route; + } + + const ownRoutes = routes.reduce((acc, inputRoute) => { + const mappedRoute = mapRoute(inputRoute); + + if (mappedRoute) { + knownRoutes.push(mappedRoute); + acc.push(mappedRoute); + } + + if (inputIs.router(inputRoute)) { + knownRoutes.push(...inputRoute.knownRoutes); + } + + return acc; + }, []); + + const $activeRoutes = $path.map((path) => { + const result: Route[] = []; + + if (!path) { + return result; + } + + for (const { route, parse } of ownRoutes) { + if (parse(path)) { + result.push(route); + } + } + + return result; + }); + + const openRoutesByPathFx = attach({ + source: { query: $query, path: $path }, + effect: ({ query, path }) => { + for (const { route, parse } of ownRoutes) { + const matchResult = parse(path); + const [routeClosed, routeNavigated] = [ + scopeBind(route.internal.close), + scopeBind(route.internal.navigated), + ]; + + if (!matchResult) { + routeClosed(); + } else { + routeNavigated({ + query, + params: matchResult.params, + }); + } + } + }, + }); + + function registerRouteApi({ route, build }: MappedRoute) { + createAction({ + clock: route.internal.openFx.doneData, + target: { navigate }, + fn: (target, payload) => { + if (payload?.navigate === false) { + return; + } + + const navigateParams = { + path: build( + payload && 'params' in payload ? payload.params : undefined, + ), + query: payload?.query ?? {}, + replace: payload?.replace, + }; + + return target.navigate(navigateParams); + }, + }); + } + + for (const route of ownRoutes) { + registerRouteApi(route); + } + + sample({ + clock: locationUpdated, + fn: (location) => ({ + path: location.pathname, + query: location.query, + }), + target: openRoutesByPathFx, + }); + + const router = { + '@@type': 'router', + + $query, + $path, + $history, + + $activeRoutes, + + back, + forward, + + navigate, + + setHistory, + ownRoutes, + knownRoutes, + + internal: { + connectToParentRouter, + + get parent() { + return parent; + }, + + set parent(router: Router | null) { + parent = router; + }, + + base, + }, + + trackQuery: trackQueryFactory({ $activeRoutes, $query, navigate }), + + registerRoute: (route: InputRoute) => { + const mappedRoute = mapRoute(route); + + if (mappedRoute) { + knownRoutes.push(mappedRoute); + ownRoutes.push(mappedRoute); + + registerRouteApi(mappedRoute); + } + + if (inputIs.router(route)) { + knownRoutes.push(...route.knownRoutes); + } + }, + + '@@unitShape': () => ({ + query: $query, + path: $path, + activeRoutes: $activeRoutes, + + onBack: back, + onForward: forward, + onNavigate: navigate, + }), + } as InternalRouter; + + return router; +} diff --git a/packages/core/lib/create-virtual-route.ts b/packages/core/lib/create-virtual-route.ts new file mode 100644 index 0000000..59af86d --- /dev/null +++ b/packages/core/lib/create-virtual-route.ts @@ -0,0 +1,91 @@ +import { createEvent, createStore, sample, split, type Store } from 'effector'; + +import type { VirtualRoute } from './types'; + +interface VirtualRouteOptions { + $isPending?: Store; + transformer?: (payload: T) => TransformerResult; +} + +export function createVirtualRoute( + options: VirtualRouteOptions = {}, +): VirtualRoute { + const { + $isPending = createStore(false), + transformer = (payload) => (payload ?? null) as TransformerResult, + } = options; + + const $params = createStore(null as TransformerResult); + const $isOpened = createStore(false); + + const open = createEvent(); + const opened = createEvent(); + + const openedOnServer = createEvent(); + const openedOnClient = createEvent(); + + const close = createEvent(); + const closed = createEvent(); + + const cancelled = createEvent(); + + sample({ + clock: open, + target: [opened], + }); + + sample({ + clock: open, + fn: transformer, + target: $params, + }); + + split({ + source: opened, + match: () => (typeof window === 'undefined' ? 'server' : 'client'), + cases: { + server: openedOnServer, + client: openedOnClient, + }, + }); + + sample({ + clock: close, + target: closed, + }); + + sample({ + clock: [opened.map(() => true), closed.map(() => false)], + target: $isOpened, + }); + + return { + '@@type': 'pathless-route', + + $params, + $isOpened, + $isPending, + + open, + opened, + + openedOnClient, + openedOnServer, + + close, + closed, + + cancelled, + + path: '', + + '@@unitShape': () => ({ + params: $params, + isOpened: $isOpened, + isPending: $isPending, + + onOpen: open, + onClose: close, + }), + }; +} diff --git a/packages/core/lib/group.ts b/packages/core/lib/group.ts new file mode 100644 index 0000000..85983d0 --- /dev/null +++ b/packages/core/lib/group.ts @@ -0,0 +1,46 @@ +import { sample } from 'effector'; +import { createVirtualRoute } from './create-virtual-route'; +import type { Route, VirtualRoute } from './types'; +import { not, or } from 'patronum'; + +/** + * @description Create virtual route which opens when some passed routes is opened. Closes if all passed routes are closed. + * @link https://router.effector.dev/core/group.html + * @returns VirtualRoute + * @example ```ts + * import { group, createRoute } from '@effector/router'; + * import { createEvent, createEffect } from 'effector'; + * + * const signInRoute = createRoute({ path: '/auth/sign-in' }); + * const signUpRoute = createRoute({ path: '/auth/sign-up' }); + * const authorizationRoute = group([signInRoute, signUpRoute]); + * + * signInRoute.open(); // authorizationRoute.$isOpened —> true + * signUpRoute.open(); // authorizationRoute.$isOpened —> true + * signInRoute.close(); // authorizationRoute.$isOpened —> true + * signUpRoute.close(); // authorizationRoute.$isOpened —> false + * ``` + */ +export function group(routes: (Route | VirtualRoute)[]) { + const virtual = createVirtualRoute({ + $isPending: or(...routes.map((route) => route.$isPending)), + }); + + const $hasOpenedRoutes = or(...routes.map((route) => route.$isOpened)); + + sample({ + clock: routes.map((route) => route.$isOpened), + filter: $hasOpenedRoutes, + fn: () => undefined, + target: virtual.open, + }); + + sample({ + clock: routes.map((route) => route.$isOpened), + filter: not($hasOpenedRoutes), + fn: () => undefined, + target: virtual.close, + }); + + return virtual; +} diff --git a/packages/core/lib/index.ts b/packages/core/lib/index.ts new file mode 100644 index 0000000..35e709e --- /dev/null +++ b/packages/core/lib/index.ts @@ -0,0 +1,29 @@ +export { createRoute } from './create-route'; +export { createRouter } from './create-router'; +export { chainRoute } from './chain-route'; +export { createRouterControls } from './create-router-controls'; +export { group } from './group'; +export { createVirtualRoute } from './create-virtual-route'; + +export type { + Route, + Router, + Query, + OpenPayloadBase, + RouteOpenedPayload, + NavigatePayload, + QueryTracker, + QueryTrackerConfig, + VirtualRoute, + MappedRoute, + InternalRoute, +} from './types'; + +export { + historyAdapter, + queryAdapter, + type RouterAdapter, + type RouterLocation, +} from './adapters'; + +export { is } from './utils'; diff --git a/packages/core/lib/track-query.ts b/packages/core/lib/track-query.ts new file mode 100644 index 0000000..9096acc --- /dev/null +++ b/packages/core/lib/track-query.ts @@ -0,0 +1,150 @@ +import { + createEvent, + createStore, + EventCallable, + sample, + type Store, +} from 'effector'; + +import type { + NavigatePayload, + Query, + QueryTracker, + QueryTrackerConfig, + Route, +} from './types'; + +import type { z, ZodType } from 'zod/v4'; + +function isForRouteActive(forRoutes: Route[], activeRoutes: Route[]) { + for (const route of forRoutes) { + if (activeRoutes.includes(route)) { + return true; + } + } + + return false; +} + +type FactoryPayload = { + $activeRoutes: Store[]>; + $query: Store; + navigate: EventCallable; +}; + +type ControlsFactory = ( + config: Omit, 'forRoutes'>, +) => QueryTracker; + +export function trackQueryControlsFactory({ + $query, + navigate, +}: Omit): ControlsFactory { + return trackQueryFactory({ + $activeRoutes: createStore([]), + $query, + navigate, + }); +} + +export function trackQueryFactory({ + $activeRoutes, + $query, + navigate, +}: FactoryPayload) { + return ( + config: QueryTrackerConfig, + ): QueryTracker => { + const { check, parameters, forRoutes } = config; + + const $entered = createStore(false); + + const entered = createEvent>(); + const exited = createEvent(); + + const enter = createEvent>(); + const exit = createEvent<{ ignoreParams: string[] } | void>(); + + const changeEntered = createEvent(); + + sample({ + clock: changeEntered, + target: $entered, + }); + + if (check) { + sample({ + clock: check, + source: { activeRoutes: $activeRoutes, query: $query }, + filter: ({ activeRoutes, query }) => + (!forRoutes || isForRouteActive(forRoutes, activeRoutes)) && + parameters.safeParse(query).success, + fn: ({ query }) => parameters.safeParse(query).data, + target: [entered, changeEntered.prepend(() => true)], + }); + } else { + sample({ + source: { activeRoutes: $activeRoutes, query: $query }, + filter: ({ activeRoutes, query }) => + (!forRoutes || isForRouteActive(forRoutes, activeRoutes)) && + parameters.safeParse(query).success, + fn: ({ query }) => parameters.safeParse(query).data, + target: [entered, changeEntered.prepend(() => true)], + }); + } + + sample({ + source: { activeRoutes: $activeRoutes, query: $query, entered: $entered }, + filter: ({ activeRoutes, query, entered }) => + entered && + !( + (!forRoutes || isForRouteActive(forRoutes, activeRoutes)) && + parameters.safeParse(query).success + ), + target: [ + exited.prepend(() => undefined), + changeEntered.prepend(() => false), + ], + }); + + // @ts-expect-error TS is wrong + sample({ + clock: enter, + source: $query, + fn: (query, payload) => { + // @ts-expect-error TS is also wrong + return { query: { ...query, ...payload } }; + }, + target: navigate, + }); + + sample({ + clock: exit, + source: $query, + fn: (query, payload) => { + if (payload && payload.ignoreParams) { + const copy: Query = {}; + + for (const key of payload.ignoreParams) { + if (query[key]) { + copy[key] = query[key]; + } + } + + return { query: copy }; + } + + return { query: {} }; + }, + target: navigate, + }); + + return { + enter, + entered, + + exited, + exit, + }; + }; +} diff --git a/packages/core/lib/types.ts b/packages/core/lib/types.ts new file mode 100644 index 0000000..4cdcfd4 --- /dev/null +++ b/packages/core/lib/types.ts @@ -0,0 +1,282 @@ +import type { + Effect, + Event, + EventCallable, + Store, + StoreWritable, +} from 'effector'; + +import type { z, ZodType } from 'zod/v4'; + +import type { Builder, Parser } from '@effector/router-paths'; +import type { RouterAdapter } from './adapters'; + +export type AsyncBundleImport = () => Promise<{ default: any }>; + +export type Query = Record>; + +export interface PathlessRoute { + '@@type': 'pathless-route'; + + $params: Store; + + $isOpened: Store; + $isPending: Store; + + open: EventCallable>; + + opened: Event>; + openedOnServer: Event>; + openedOnClient: Event>; + + closed: Event; + + parent?: PathRoute | PathlessRoute; + beforeOpen?: Effect[]; + + '@@unitShape': () => { + params: Store; + isOpened: Store; + isPending: Store; + + onOpen: EventCallable>; + }; +} + +export interface PathRoute extends Omit< + PathlessRoute, + '@@type' +> { + '@@type': 'path-route'; + + path: string; +} + +export type Route = + | PathRoute + | PathlessRoute; + +export type QueryTrackerConfig = { + forRoutes?: Route[]; + check?: Event; + parameters: ParametersConfig; +}; + +export interface QueryTracker { + entered: Event>; + exited: Event; + + enter: EventCallable>; + exit: EventCallable<{ ignoreParams: string[] } | void>; +} + +export type OpenPayloadBase = { + query?: Query; + replace?: boolean; +}; + +export type RouteOpenedPayload = T extends void + ? void | OpenPayloadBase + : { params: T } & OpenPayloadBase; + +export type NavigatePayload = { + query: Query; + path?: string; + replace?: boolean; +}; + +export type MappedRoute = { + route: InternalRoute; + path: string; + build: Builder; + parse: Parser; +}; + +export interface Router { + '@@type': 'router'; + + $query: Store; + $path: Store; + $history: Store; + $activeRoutes: Store[]>; + + back: EventCallable; + forward: EventCallable; + navigate: EventCallable; + + setHistory: EventCallable; + + /** + * @description Creates query params tracker + * @param config Query tacker config + * @link https://router.effector.dev/core/track-query.html + * @example ```ts + * import { parameters } from '@effector/router'; + * import { router } from '@shared/router'; + * import { createDialog } from '...'; + * + * const dialog = createDialog(); + * const tracker = router.trackQuery({ + * dialog: 'team-member', + * id: parameters.number, + * }); + * + * // triggered for: + * // /team?dialog=team-member&id=1 + * // /team?dialog=team-member&id=10000 + * + * // not triggered for: + * // /team?dialog=team&id=1 + * // /team?id=10000 + * // /team?dialog=team&id=not_number + * ``` + */ + trackQuery: ( + config: QueryTrackerConfig, + ) => QueryTracker; + + ownRoutes: MappedRoute[]; + knownRoutes: MappedRoute[]; + + registerRoute: ( + route: + | PathRoute + | { path: string; route: PathlessRoute } + | Router, + ) => void; + + '@@unitShape': () => { + query: Store; + path: Store; + activeRoutes: Store[]>; + + onBack: EventCallable; + onForward: EventCallable; + onNavigate: EventCallable; + }; +} + +export interface InternalRouterProps { + parent: Router | null; + base?: string; +} + +export interface InternalRouter extends Router { + internal: InternalRouterProps; +} + +type InternalOpenedPayload = RouteOpenedPayload & { navigate?: boolean }; + +export interface InternalRouteParams { + close: EventCallable; + navigated: EventCallable>; + openFx: Effect, InternalOpenedPayload, Error>; + forceOpenParentFx: Effect< + InternalOpenedPayload, + InternalOpenedPayload, + Error + >; + + setAsyncImport: (value: AsyncBundleImport) => void; +} + +export interface InternalPathlessRoute< + T extends object | void = any, +> extends PathlessRoute { + internal: InternalRouteParams; +} + +export interface InternalPathRoute< + T extends object | void = any, +> extends PathRoute { + internal: InternalRouteParams; +} + +export type InternalRoute = + | InternalPathRoute + | InternalPathlessRoute; + +export interface VirtualRoute { + '@@type': 'pathless-route'; + + $params: StoreWritable; + + $isOpened: StoreWritable; + $isPending: Store; + + open: EventCallable; + opened: Event; + + openedOnServer: Event; + openedOnClient: Event; + + close: EventCallable; + closed: Event; + + cancelled: Event; + + path: string; + beforeOpen?: Effect[]; + + '@@unitShape': () => { + params: Store; + isOpened: Store; + isPending: Store; + + onOpen: EventCallable; + onClose: EventCallable; + }; +} + +export type LocationState = { path: string; query: Query }; + +export interface RouterControls { + $history: StoreWritable; + $locationState: StoreWritable; + + $query: Store; + $path: Store; + + setHistory: EventCallable; + + navigate: EventCallable; + + back: EventCallable; + forward: EventCallable; + + locationUpdated: EventCallable<{ + pathname: string; + query: Query; + }>; + + /** + * @description Creates query params tracker + * @param config Query tacker config + * @link https://router.effector.dev/core/track-query.html + * @example ```ts + * import { z } from 'zod/v4'; + * import { router } from '@shared/router'; + * import { createDialog } from '...'; + * + * const dialog = createDialog(); + * const tracker = router.trackQuery({ + * parameters: { + * dialog: z.literal('team-member'), + * id: z.cource.number(), + * }, + * }); + * + * // triggered for: + * // /team?dialog=team-member&id=1 + * // /team?dialog=team-member&id=10000 + * + * // not triggered for: + * // /team?dialog=team&id=1 + * // /team?id=10000 + * // /team?dialog=team&id=not_number + * ``` + */ + trackQuery: ( + config: Omit, 'forRoutes'>, + ) => QueryTracker; +} diff --git a/packages/core/lib/utils.ts b/packages/core/lib/utils.ts new file mode 100644 index 0000000..1881d39 --- /dev/null +++ b/packages/core/lib/utils.ts @@ -0,0 +1,38 @@ +import type { PathlessRoute, PathRoute, Route, Router } from './types'; + +export const is = { + route(input: unknown): input is Route { + return is.pathRoute(input) || is.pathlessRoute(input); + }, + + pathRoute( + input: unknown, + ): input is PathRoute { + return ( + typeof input === 'object' && + input !== null && + '@@type' in input && + input['@@type'] === 'path-route' + ); + }, + + pathlessRoute( + input: unknown, + ): input is PathlessRoute { + return ( + typeof input === 'object' && + input !== null && + '@@type' in input && + input['@@type'] === 'pathless-route' + ); + }, + + router(input: unknown): input is Router { + return ( + typeof input === 'object' && + input !== null && + '@@type' in input && + input['@@type'] === 'router' + ); + }, +}; diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..daf9eae --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,63 @@ +{ + "name": "@effector/router", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "private": false, + "version": "1.0.0", + "description": "Router with power of effector", + "keywords": [ + "effector", + "router", + "argon", + "atomic", + "effector/router" + ], + "author": "movpushmov", + "contributors": [ + "Sergey Sova ", + "Anton Kosykh", + "Zero Bias " + ], + "homepage": "https://router.effector.dev/core", + "license": "MIT", + "directories": { + "lib": "lib" + }, + "files": [ + "dist" + ], + "type": "module", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "repository": { + "type": "git", + "url": "git+https://github.com/effector/router.git", + "directory": "packages/core" + }, + "scripts": { + "build": "vite build", + "test": "vitest run" + }, + "bugs": { + "url": "https://github.com/effector/router/issues" + }, + "peerDependencies": { + "effector": ">=23", + "effector-action": ">=1.2.2", + "patronum": ">=2.2", + "query-string": "^9.1.1", + "zod": ">=4" + }, + "dependencies": { + "@effector/router-paths": "workspace:*" + }, + "gitHead": "9832d6cd282d6abd33c26ba4d03b9a1176110fc4" +} diff --git a/packages/core/tests/__fixtures__/dynamic-module.ts b/packages/core/tests/__fixtures__/dynamic-module.ts new file mode 100644 index 0000000..b3c446f --- /dev/null +++ b/packages/core/tests/__fixtures__/dynamic-module.ts @@ -0,0 +1,13 @@ +import { createRoute, createRouter } from '../../lib'; + +const settingsModalRoutes = { + general: createRoute({ path: '/' }), + security: createRoute({ path: '/security' }), +}; + +const settingsModalRouter = createRouter({ + base: '/settings', + routes: [settingsModalRoutes.general, settingsModalRoutes.security], +}); + +export { settingsModalRoutes, settingsModalRouter }; diff --git a/packages/core/tests/chained-routes.test.ts b/packages/core/tests/chained-routes.test.ts new file mode 100644 index 0000000..ed78290 --- /dev/null +++ b/packages/core/tests/chained-routes.test.ts @@ -0,0 +1,102 @@ +import { describe, expect, test } from 'vitest'; +import { + chainRoute, + createRoute, + RouteOpenedPayload, + createRouter, + historyAdapter, + createVirtualRoute, +} from '../lib'; +import { allSettled, createEffect, createEvent, fork, sample } from 'effector'; +import { createMemoryHistory } from 'history'; +import { watchCalls } from './utils'; + +describe('chained routes', () => { + test('authorized route', async () => { + const scope = fork(); + + const route = createRoute({ path: '/profile/:id' }); + const router = createRouter({ routes: [route] }); + + await allSettled(router.setHistory, { + params: historyAdapter(createMemoryHistory()), + scope, + }); + + const authorized = createEvent(); + const rejected = createEvent(); + + const checkAuthorizationFx = createEffect< + RouteOpenedPayload<{ id: string }>, + boolean + >(({ params }) => params.id !== '0'); + + sample({ + clock: checkAuthorizationFx.doneData, + filter: (isAuthorized) => isAuthorized, + target: authorized, + }); + + sample({ + clock: checkAuthorizationFx.doneData, + filter: (isAuthorized) => !isAuthorized, + target: rejected, + }); + + const virtual = chainRoute({ + route, + beforeOpen: checkAuthorizationFx, + openOn: authorized, + cancelOn: rejected, + }); + + await allSettled(route.open, { + scope, + params: { params: { id: '0' } }, + }); + + expect(scope.getState(virtual.$isOpened)).toBeFalsy(); + + await allSettled(route.open, { + scope, + params: { params: { id: '1' } }, + }); + + expect(scope.getState(virtual.$isOpened)).toBeTruthy(); + expect(scope.getState(virtual.$params)).toStrictEqual({ id: '1' }); + }); + + test('virtual route groupping', async () => { + const scope = fork(); + const virtualRoute = createVirtualRoute>(); + + const fx = createEffect((params: RouteOpenedPayload) => params); + + const counter = watchCalls(fx, scope); + + const chainedRoute = chainRoute({ + route: virtualRoute, + beforeOpen: [fx], + openOn: fx.doneData, + }); + + expect(counter).not.toBeCalled(); + + await allSettled(virtualRoute.open, { + scope, + params: { query: { test: 'abc' } }, + }); + + expect(counter).toBeCalled(); + expect(counter.mock.calls[0]).toStrictEqual([ + { + query: { + test: 'abc', + }, + }, + ]); + expect(scope.getState(chainedRoute.$isOpened)).toBeTrueWithMessage( + 'Chained route is must be opened', + ); + }); +}); diff --git a/packages/core/tests/group.test.ts b/packages/core/tests/group.test.ts new file mode 100644 index 0000000..f65cba5 --- /dev/null +++ b/packages/core/tests/group.test.ts @@ -0,0 +1,73 @@ +import { allSettled, fork } from 'effector'; +import { describe, expect, test } from 'vitest'; +import { createVirtualRoute, group, RouteOpenedPayload } from '../lib'; + +describe('routes groupping', () => { + test('groupped route opened when one of passed routes is opened', async () => { + const scope = fork(); + + const route1 = createVirtualRoute, void>(); + const route2 = createVirtualRoute, void>(); + + const groupped = group([route1, route2]); + + expect(scope.getState(groupped.$isOpened)).toBeFalsy(); + + await allSettled(route1.open, { scope, params: undefined }); + + expect(scope.getState(groupped.$isOpened)).toBeTruthy(); + + await allSettled(route1.close, { scope, params: undefined }); + await allSettled(route2.open, { scope, params: undefined }); + + expect(scope.getState(route1.$isOpened)).toBeFalsy(); + expect(scope.getState(groupped.$isOpened)).toBeTruthy(); + }); + + test('groupped route closed when all of passed routes is closed', async () => { + const scope = fork(); + + const route1 = createVirtualRoute, void>(); + const route2 = createVirtualRoute, void>(); + + const groupped = group([route1, route2]); + + await allSettled(route1.open, { scope, params: undefined }); + await allSettled(route2.open, { scope, params: undefined }); + + expect(scope.getState(groupped.$isOpened)).toBeTruthy(); + + await allSettled(route1.close, { scope, params: undefined }); + + expect(scope.getState(groupped.$isOpened)).toBeTruthy(); + + await allSettled(route2.close, { scope, params: undefined }); + + expect(scope.getState(groupped.$isOpened)).toBeFalsy(); + }); + + test('virtual route groupping works correctly', async () => { + const scope = fork(); + const virtualRoute = createVirtualRoute({ + transformer: (_: RouteOpenedPayload) => null, + }); + + const routesGroup = group([virtualRoute]); + + expect(scope.getState(routesGroup.$isOpened)).toBeFalseWithMessage( + '[1] Routes group must be false cause virtual route is closed', + ); + + await allSettled(virtualRoute.open, { scope, params: {} }); + + expect(scope.getState(routesGroup.$isOpened)).toBeTrueWithMessage( + '[2] Routes group must be true cause virtual route is opened', + ); + + await allSettled(virtualRoute.close, { scope }); + + expect(scope.getState(routesGroup.$isOpened)).toBeFalseWithMessage( + '[3] Routes group must be false cause virtual route is closed', + ); + }); +}); diff --git a/packages/core/tests/paths.test.ts b/packages/core/tests/paths.test.ts new file mode 100644 index 0000000..fbfb4f9 --- /dev/null +++ b/packages/core/tests/paths.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, test } from 'vitest'; +import { createRoute, createRouter } from '../lib'; + +describe('paths generation', () => { + test('without base', () => { + const route1 = createRoute({ path: '/hi' }); + const route2 = createRoute({ path: '/hello' }); + const nested1 = createRoute({ path: '/ff', parent: route1 }); + const nested2 = createRoute({ path: '/ss', parent: route2 }); + const nested3 = createRoute({ path: '/ss', parent: nested1 }); + + const { knownRoutes } = createRouter({ + routes: [route1, route2, nested1, nested2, nested3], + }); + + expect(knownRoutes.map((route) => route.path)).toStrictEqual([ + '/hi', + '/hello', + '/hi/ff', + '/hello/ss', + '/hi/ff/ss', + ]); + }); + + test('with base', () => { + const route1 = createRoute({ path: '/hi' }); + const route2 = createRoute({ path: '/hello' }); + const nested1 = createRoute({ path: '/ff', parent: route1 }); + const nested2 = createRoute({ path: '/ss', parent: route2 }); + const nested3 = createRoute({ path: '/ss', parent: nested1 }); + + const { knownRoutes } = createRouter({ + base: '/movpushmov', + routes: [route1, route2, nested1, nested2, nested3], + }); + + expect(knownRoutes.map((route) => route.path)).toStrictEqual([ + '/movpushmov/hi', + '/movpushmov/hello', + '/movpushmov/hi/ff', + '/movpushmov/hello/ss', + '/movpushmov/hi/ff/ss', + ]); + }); +}); diff --git a/packages/core/tests/router.test.ts b/packages/core/tests/router.test.ts new file mode 100644 index 0000000..038c697 --- /dev/null +++ b/packages/core/tests/router.test.ts @@ -0,0 +1,313 @@ +import { allSettled, createEffect, createEvent, fork, sample } from 'effector'; +import { describe, expect, test, vi } from 'vitest'; +import { createRoute, createRouter, historyAdapter } from '../lib'; +import { createMemoryHistory } from 'history'; +import { watchCalls } from './utils'; + +describe('router', () => { + test('routes opened when path changed', async () => { + const route1 = createRoute({ path: '/one' }); + const route2 = createRoute({ path: '/two' }); + + const scope = fork(); + const history = createMemoryHistory(); + + const router = createRouter({ + routes: [route1, route2], + }); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + history.push('/one'); + + await allSettled(scope); + + expect(scope.getState(router.$activeRoutes)[0]).toEqual(route1); + expect(scope.getState(route1.$isOpened)).toBeTruthy(); + }); + + test('routes closed when path changed', async () => { + const route1 = createRoute({ path: '/one' }); + const route2 = createRoute({ path: '/two' }); + + const scope = fork(); + const history = createMemoryHistory(); + + const router = createRouter({ + routes: [route1, route2], + }); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + history.push('/one'); + + await allSettled(scope); + + expect(scope.getState(router.$activeRoutes)[0]).toEqual(route1); + expect(scope.getState(route1.$isOpened)).toBeTruthy(); + + history.push('/two'); + + await allSettled(scope); + + expect(scope.getState(router.$activeRoutes)[0]).toEqual(route2); + expect(scope.getState(route2.$isOpened)).toBeTruthy(); + }); + + test('routes changed path when opened', async () => { + const route1 = createRoute({ path: '/one' }); + const route2 = createRoute({ path: '/two/:id' }); + const nested = createRoute({ parent: route1, path: '/nested/:id' }); + + const scope = fork(); + const history = createMemoryHistory(); + + const router = createRouter({ + routes: [route1, route2, nested], + }); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + await allSettled(route1.open, { scope, params: {} }); + + expect(history.location.pathname).toBe('/one'); + + await allSettled(route2.open, { + scope, + params: { params: { id: 'hello' } }, + }); + + expect(history.location.pathname).toBe('/two/hello'); + + await allSettled(nested.open, { + scope, + params: { params: { id: 'hello' } }, + }); + + expect(history.location.pathname).toBe('/one/nested/hello'); + }); + + test('navigate with query', async () => { + const scope = fork(); + const route = createRoute({ path: '/auth' }); + const router = createRouter({ + routes: [route], + }); + + const history = createMemoryHistory(); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + history.push('/auth?login=movpushmov&password=123&retry=1&retry=1'); + + await vi.waitFor( + () => expect(scope.getState(router.$activeRoutes)[0]).toEqual(route), + { timeout: 100 }, + ); + + expect(scope.getState(router.$query)).toStrictEqual({ + login: 'movpushmov', + password: '123', + retry: ['1', '1'], + }); + }); + + test('route.open with query', async () => { + const scope = fork(); + const route = createRoute({ path: '/auth' }); + const router = createRouter({ + routes: [route], + }); + + const history = createMemoryHistory(); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + await allSettled(route.open, { + scope, + params: { + query: { login: 'movpushmov', password: '123', retry: ['1', '1'] }, + }, + }); + + expect(history.location.pathname).toBe('/auth'); + expect(history.location.search).toBe( + '?login=movpushmov&password=123&retry=1&retry=1', + ); + }); + + test('navigate with params', async () => {}); + + test('route.open with params', async () => {}); + + test('route not opened when history blocked', async () => { + const scope = fork(); + const route1 = createRoute({ path: '/step1' }); + const route2 = createRoute({ path: '/step2' }); + + const router = createRouter({ routes: [route1, route2] }); + const history = createMemoryHistory({ initialEntries: ['/step1'] }); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + history.block(() => false); + await allSettled(route2.open, { scope, params: {} }); + + expect(scope.getState(router.$activeRoutes)[0]).toEqual(route1); + expect(scope.getState(route1.$isOpened)).toBeTruthy(); + expect(scope.getState(route2.$isOpened)).toBeFalsy(); + }); + + test('beforeOpen on route', async () => { + const fn1 = vi.fn(); + const fn2 = vi.fn(); + + const scope = fork(); + + const route1 = createRoute({ + path: '/step1', + beforeOpen: [createEffect(fn1)], + }); + + const route2 = createRoute({ + path: '/step2', + beforeOpen: [createEffect(fn2)], + }); + + const router = createRouter({ routes: [route1, route2] }); + const history = createMemoryHistory({ initialEntries: ['/step1'] }); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + expect(fn1).toBeCalled(); + + history.push('/step2'); + await allSettled(scope); + + expect(fn2).toBeCalled(); + }); + + test('parent route is opened', async () => { + const scope = fork(); + + const parent = createRoute({ path: '/parent' }); + const child = createRoute({ path: '/child', parent }); + + const router = createRouter({ routes: [parent, child] }); + const history = createMemoryHistory(); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + history.push('/parent/child'); + await allSettled(scope); + + expect(scope.getState(parent.$isOpened)).toBeTruthy(); + expect(scope.getState(child.$isOpened)).toBeTruthy(); + }); + + test('subrouter', async () => { + const scope = fork(); + + const settingsModalRoutes = { + general: createRoute({ path: '/' }), + security: createRoute({ path: '/security' }), + }; + + const settingsModalRouter = createRouter({ + base: '/settings', + routes: [settingsModalRoutes.general, settingsModalRoutes.security], + }); + + const mainRoutes = { + home: createRoute({ path: '/' }), + }; + + const mainRouter = createRouter({ + routes: [mainRoutes.home, settingsModalRouter], + }); + + await allSettled(mainRouter.setHistory, { + scope, + params: historyAdapter(createMemoryHistory()), + }); + + await allSettled(mainRoutes.home.open, { scope, params: {} }); + + expect(scope.getState(mainRoutes.home.$isOpened)).toBeTrueWithMessage( + 'home route should be opened', + ); + expect( + scope.getState(settingsModalRoutes.general.$isOpened), + ).toBeFalseWithMessage('settings modal general route should be closed'); + + await allSettled(settingsModalRoutes.general.open, { scope, params: {} }); + + expect(scope.getState(mainRoutes.home.$isOpened)).toBeFalseWithMessage( + 'home route should be closed', + ); + expect( + scope.getState(settingsModalRoutes.general.$isOpened), + ).toBeTrueWithMessage('settings modal general route should be opened'); + + await allSettled(settingsModalRoutes.security.open, { scope, params: {} }); + + expect(scope.getState(mainRoutes.home.$isOpened)).toBeFalseWithMessage( + 'home route should be closed', + ); + expect( + scope.getState(settingsModalRoutes.general.$isOpened), + ).toBeFalseWithMessage('settings modal general route should be closed'); + expect( + scope.getState(settingsModalRoutes.security.$isOpened), + ).toBeTrueWithMessage('settings modal security route should be opened'); + }); + + test('route opened only once', async () => { + const scope = fork(); + const appStarted = createEvent(); + + const routes = { + example: createRoute({ + path: '/', + }), + }; + + const router = createRouter({ + routes: [routes.example], + }); + + sample({ + clock: appStarted, + fn: () => historyAdapter(createMemoryHistory()), + target: router.setHistory, + }); + + const calls = watchCalls(routes.example.opened, scope); + + await allSettled(appStarted, { scope }); + + expect(calls).toBeCalledTimes(1); + }); +}); diff --git a/packages/core/tests/setup.ts b/packages/core/tests/setup.ts new file mode 100644 index 0000000..87626d7 --- /dev/null +++ b/packages/core/tests/setup.ts @@ -0,0 +1,29 @@ +import { expect } from 'vitest'; + +expect.extend({ + toBeFalseWithMessage(received: boolean, message: string) { + return { + message: () => message, + pass: received === false, + }; + }, + + toBeTrueWithMessage(received: boolean, message: string) { + return { + message: () => message, + pass: received === true, + }; + }, +}); + +interface CustomMatchers { + toBeFalseWithMessage: (message: string) => R; + toBeTrueWithMessage: (message: string) => R; +} + +declare module 'vitest' { + interface Matchers extends CustomMatchers { + toBeFalseWithMessage: (message: string) => T; + toBeTrueWithMessage: (message: string) => T; + } +} diff --git a/packages/core/tests/track-query.test.ts b/packages/core/tests/track-query.test.ts new file mode 100644 index 0000000..e5760c6 --- /dev/null +++ b/packages/core/tests/track-query.test.ts @@ -0,0 +1,396 @@ +import { allSettled, createEvent, fork } from 'effector'; +import { createMemoryHistory } from 'history'; +import { describe, expect, test } from 'vitest'; +import { createRouter, createRoute, historyAdapter } from '../lib'; +import { watchCalls } from './utils'; +import z from 'zod/v4'; + +async function prepare() { + const routes = { + home: createRoute({ path: '/' }), + app: createRoute({ path: '/app' }), + }; + + const scope = fork(); + const history = createMemoryHistory({ initialEntries: ['/'] }); + const router = createRouter({ + routes: [routes.home, routes.app], + }); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + return { routes, scope, history, router }; +} + +describe('trackQuery', () => { + test('number parameter', async () => { + const { router, scope } = await prepare(); + const tracker = router.trackQuery({ + parameters: z.object({ + num: z.coerce.number(), + }), + }); + + const enteredCalls = watchCalls(tracker.entered, scope); + const exitedCalls = watchCalls(tracker.exited, scope); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: { num: '1200' } }, + }); + + expect(enteredCalls).toBeCalledWith({ num: 1200 }); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: { num: 'hello' } }, + }); + + expect(enteredCalls).toBeCalledTimes(1); + expect(exitedCalls).toBeCalledTimes(1); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: { num: ['hello', '1200'] } }, + }); + + expect(enteredCalls).toBeCalledTimes(1); + expect(exitedCalls).toBeCalledTimes(1); + }); + + test('string parameter', async () => { + const { router, scope } = await prepare(); + const tracker = router.trackQuery({ + parameters: z.object({ + str: z.string(), + }), + }); + + const enteredCalls = watchCalls(tracker.entered, scope); + const exitedCalls = watchCalls(tracker.exited, scope); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: { str: '1200' } }, + }); + + expect(enteredCalls).toBeCalledWith({ str: '1200' }); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: { str: 'hello' } }, + }); + + expect(enteredCalls).toBeCalledWith({ str: 'hello' }); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: { str: ['hello', '1200'] } }, + }); + + expect(enteredCalls).toBeCalledTimes(2); + expect(exitedCalls).toBeCalledTimes(1); + }); + + test('any parameter', async () => { + const { router, scope } = await prepare(); + const tracker = router.trackQuery({ + parameters: z.object({ + any: z.any().refine((value) => value !== undefined), + }), + }); + + const enteredCalls = watchCalls(tracker.entered, scope); + const exitedCalls = watchCalls(tracker.exited, scope); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: { any: '1200' } }, + }); + + expect(enteredCalls).toBeCalledWith({ any: '1200' }); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: { any: 'hello' } }, + }); + + expect(enteredCalls).toBeCalledWith({ any: 'hello' }); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: { any: ['hello', '1200'] } }, + }); + + expect(enteredCalls).toBeCalledWith({ any: ['hello', '1200'] }); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: {} }, + }); + + expect(enteredCalls).toBeCalledTimes(3); + expect(exitedCalls).toBeCalledTimes(1); + }); + + test('array parameter', async () => { + const { router, scope } = await prepare(); + const tracker = router.trackQuery({ + parameters: z.object({ + any: z.any().refine((value) => value !== undefined), + }), + }); + + const enteredCalls = watchCalls(tracker.entered, scope); + const exitedCalls = watchCalls(tracker.exited, scope); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: { any: ['hello', '1200'] } }, + }); + + expect(enteredCalls).toBeCalledWith({ any: ['hello', '1200'] }); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: {} }, + }); + + expect(enteredCalls).toBeCalledTimes(1); + expect(exitedCalls).toBeCalledTimes(1); + }); + + test('boolean parameter', async () => { + const { router, scope } = await prepare(); + const tracker = router.trackQuery({ + parameters: z.object({ + bool: z + .string() + .refine((bool) => ['0', '1', 'false', 'true'].includes(bool)) + .transform((schema) => ['1', 'true'].includes(schema)), + }), + }); + + const enteredCalls = watchCalls(tracker.entered, scope); + const exitedCalls = watchCalls(tracker.exited, scope); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: { bool: '0' } }, + }); + + expect(enteredCalls).toBeCalledWith({ bool: false }); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: { bool: '1' } }, + }); + + expect(enteredCalls).toBeCalledWith({ bool: true }); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: { bool: 'false' } }, + }); + + expect(enteredCalls).toBeCalledWith({ bool: false }); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: { bool: 'true' } }, + }); + + expect(enteredCalls).toBeCalledWith({ bool: true }); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: { bool: '123' } }, + }); + + expect(enteredCalls).toBeCalledTimes(4); + expect(exitedCalls).toBeCalledTimes(1); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: { bool: 'hello' } }, + }); + + expect(enteredCalls).toBeCalledTimes(4); + expect(exitedCalls).toBeCalledTimes(1); + + await allSettled(router.navigate, { + scope, + params: { path: '/', query: { bool: ['0', 'hello'] } }, + }); + + expect(enteredCalls).toBeCalledTimes(4); + expect(exitedCalls).toBeCalledTimes(1); + }); + + test('for routes', async () => { + const { router, routes, scope } = await prepare(); + const tracker = router.trackQuery({ + parameters: z.object({ + any: z.any().refine((value) => value !== undefined), + }), + forRoutes: [routes.app, routes.home], + }); + + const enteredCalls = watchCalls(tracker.entered, scope); + const exitedCalls = watchCalls(tracker.exited, scope); + + await allSettled(router.navigate, { + params: { path: '/not-found', query: { any: '123' } }, + scope, + }); + + expect(enteredCalls).not.toBeCalled(); + + await allSettled(router.navigate, { + params: { path: '/app', query: { any: '123' } }, + scope, + }); + + expect(enteredCalls).toBeCalledTimes(1); + + await allSettled(router.navigate, { + params: { path: '/', query: { any: '123' } }, + scope, + }); + + expect(enteredCalls).toBeCalledTimes(2); + + await allSettled(router.navigate, { + params: { path: '/not-found', query: { any: '123' } }, + scope, + }); + + expect(exitedCalls).toBeCalledTimes(1); + }); + + test('exit', async () => { + const { router, routes, scope } = await prepare(); + const tracker = router.trackQuery({ + parameters: z.object({ + any: z.any().refine((value) => value !== undefined), + }), + forRoutes: [routes.app, routes.home], + }); + + const exitedCalls = watchCalls(tracker.exited, scope); + + await allSettled(router.navigate, { + params: { path: '/not-found', query: { any: '123' } }, + scope, + }); + + await allSettled(tracker.exit, { scope, params: undefined }); + + expect(exitedCalls).not.toBeCalled(); + + await allSettled(router.navigate, { + params: { path: '/', query: { any: '123', uid: 'hi!' } }, + scope, + }); + + await allSettled(tracker.exit, { scope, params: undefined }); + + expect(exitedCalls).toBeCalled(); + expect(scope.getState(router.$query)).toStrictEqual({}); + }); + + test('ignore parameters', async () => { + const { router, routes, scope } = await prepare(); + const tracker = router.trackQuery({ + parameters: z.object({ + any: z.any().refine((value) => value !== undefined), + }), + forRoutes: [routes.app, routes.home], + }); + + const exitedCalls = watchCalls(tracker.exited, scope); + + await allSettled(router.navigate, { + params: { path: '/not-found', query: { any: '123' } }, + scope, + }); + + await allSettled(tracker.exit, { scope, params: undefined }); + + expect(exitedCalls).not.toBeCalled(); + + await allSettled(router.navigate, { + params: { path: '/', query: { any: '123', uid: 'hi!' } }, + scope, + }); + + await allSettled(tracker.exit, { + scope, + params: { ignoreParams: ['uid'] }, + }); + + expect(exitedCalls).toBeCalled(); + expect(scope.getState(router.$query)).toStrictEqual({ uid: 'hi!' }); + }); + + test('enter', async () => { + const { router, routes, scope, history } = await prepare(); + const tracker = router.trackQuery({ + parameters: z.object({ + id: z.coerce.number(), + role: z.enum(['user', 'admin']), + }), + forRoutes: [routes.app, routes.home], + }); + + await allSettled(tracker.enter, { params: { id: 0, role: 'user' }, scope }); + + expect(scope.getState(router.$query)).toStrictEqual({ + id: '0', + role: 'user', + }); + expect(history.location.search).toBe('?id=0&role=user'); + + await allSettled(tracker.enter, { + params: { id: 1, role: 'admin' }, + scope, + }); + + expect(scope.getState(router.$query)).toStrictEqual({ + id: '1', + role: 'admin', + }); + expect(history.location.search).toBe('?id=1&role=admin'); + }); + + test('check by clock', async () => { + const check = createEvent(); + + const { router, routes, scope } = await prepare(); + const tracker = router.trackQuery({ + check, + parameters: z.object({ + id: z.string(), + }), + }); + + const enteredCalls = watchCalls(tracker.entered, scope); + + await allSettled(routes.app.open, { scope, params: {} }); + await allSettled(routes.home.open, { scope, params: {} }); + + await allSettled(routes.home.open, { + scope, + params: { query: { id: '123' } }, + }); + + expect(enteredCalls).not.toBeCalled(); + + await allSettled(check, { scope }); + + expect(enteredCalls).toBeCalledWith({ id: '123' }); + }); +}); diff --git a/packages/core/tests/utils.ts b/packages/core/tests/utils.ts new file mode 100644 index 0000000..ecc7084 --- /dev/null +++ b/packages/core/tests/utils.ts @@ -0,0 +1,18 @@ +import { createWatch, Scope, Unit } from 'effector'; +import { vi } from 'vitest'; + +export function watchCalls(unit: Unit, scope: Scope) { + const mockedFn = vi.fn<(payload: T) => void>(); + + createWatch({ + unit, + scope, + fn: mockedFn, + }); + + return mockedFn; +} + +export function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/packages/core/vite.config.ts b/packages/core/vite.config.ts new file mode 100644 index 0000000..a14ed4e --- /dev/null +++ b/packages/core/vite.config.ts @@ -0,0 +1,52 @@ +/// +import { defineConfig } from 'vite'; +import { resolve } from 'path'; +import dts from 'vite-plugin-dts'; +import babel from 'vite-plugin-babel'; + +export default defineConfig({ + mode: 'production', + build: { + outDir: resolve(__dirname, 'dist'), + lib: { + entry: resolve(__dirname, 'lib/index.ts'), + fileName: 'index', + formats: ['es', 'cjs'], + }, + rollupOptions: { + external: [ + 'effector', + 'patronum', + 'effector-action', + '@effector/router-paths', + 'query-string', + ], + output: { + globals: { + effector: 'effector', + patronum: 'patronum', + 'query-string': 'query-string', + }, + }, + }, + }, + plugins: [ + babel({ filter: /.[jt]sx?/ }), + dts({ + outDir: resolve(__dirname, 'dist'), + entryRoot: resolve(__dirname, 'lib'), + exclude: [ + resolve(__dirname, 'tests'), + resolve(__dirname, '../effector/router-paths'), + resolve(__dirname, '../effector/router-react'), + resolve(__dirname, '../effector/router-react-native'), + ], + staticImport: true, + insertTypesEntry: true, + rollupTypes: true, + }), + ], + test: { + setupFiles: [resolve(__dirname, 'tests/setup.ts')], + }, +}); diff --git a/packages/paths/README.md b/packages/paths/README.md new file mode 100644 index 0000000..0390818 --- /dev/null +++ b/packages/paths/README.md @@ -0,0 +1 @@ +# ☄️ effector/router-paths diff --git a/packages/paths/babel.config.json b/packages/paths/babel.config.json new file mode 100644 index 0000000..9d1ac08 --- /dev/null +++ b/packages/paths/babel.config.json @@ -0,0 +1,21 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "modules": false, + "targets": "> 0.25%, not dead" + } + ], + "@babel/preset-typescript" + ], + "plugins": [ + [ + "effector/babel-plugin", + { + "factories": ["lib/create-route", "lib/create-router", "lib/form/form"], + "addNames": true + } + ] + ] +} diff --git a/packages/paths/lib/compile.ts b/packages/paths/lib/compile.ts new file mode 100644 index 0000000..cea98fc --- /dev/null +++ b/packages/paths/lib/compile.ts @@ -0,0 +1,115 @@ +import { prepareParser } from './prepare-parser'; +import { getTokenParameters } from './get-token-parameters'; +import { ParameterToken, ParseUrlParams, Token } from './types'; +import { prepareBuilder } from './prepare-builder'; + +/** + * @param path Route path + * @description compiles route and give two functions: build (from params to string) & parse (validate from string and get params) + * @returns { build: Builder; parse: Parser; } + * @example ```ts + * import { compile } from '@effector/router-paths'; + * + * // without params + * const { parse, build } = compile('/profile'); + * + * console.log(parse('/profile')) // { path: '/profile', params: null } + * console.log(parse('/test')) // null + * + * console.log(build()) // '/profile' + * + * // with params + * const { parse, build } = compile('/:id'); + * + * console.log(parse('/movpushmov')) // { path: '/profile', params: { id: 'movpushmov' } } + * console.log(parse('/')) // null + * + * console.log(build({ id: 'movpushmov' })) // '/movpushmov' + * ``` + */ +export function compile>(path: T) { + const tokens: Token[] = []; + + const regexp = /:(\w+)(<[\s?\w|]+>)?({\d+,\d+})?([+*?])?/; + const parsedTokens = path.split('/').filter(Boolean); + + for (let i = 0; i < parsedTokens.length; i++) { + const parsedToken = parsedTokens[i]; + + if (!parsedToken) { + continue; + } + + const parameters = getTokenParameters(parsedToken.match(regexp)); + + if (!parameters) { + tokens.push({ type: 'const', name: parsedToken, payload: undefined }); + continue; + } + + const { arrayProps, genericProps, modificator, name } = parameters; + + if (!name) { + throw new Error( + `Invalid path: "${path}". Name for argument must be provided`, + ); + } + + const token: ParameterToken = { + type: 'parameter', + name, + payload: { + required: true, + }, + }; + + if (genericProps && genericProps === 'number') { + token.payload.genericProps = { type: 'number' }; + } + + if (genericProps && genericProps.includes('|')) { + token.payload.genericProps = { + type: 'union', + items: genericProps.split('|'), + }; + } + + switch (modificator) { + case '*': { + token.payload.arrayProps = {}; + break; + } + case '+': { + token.payload.arrayProps = { min: 1 }; + break; + } + case '?': { + token.payload.required = false; + break; + } + } + + if (arrayProps) { + token.payload.arrayProps = { + ...token.payload.arrayProps, + min: arrayProps[0], + max: arrayProps[1], + }; + } + + tokens.push(token); + } + + return { + /** + * @param input Input path + * @returns `{ path: string; params: Params }` | `null` + */ + parse: prepareParser(tokens), + /** + * @param params Route parameters + * @returns string + */ + build: prepareBuilder(tokens), + }; +} diff --git a/packages/paths/lib/convert-path.ts b/packages/paths/lib/convert-path.ts new file mode 100644 index 0000000..d6d54c8 --- /dev/null +++ b/packages/paths/lib/convert-path.ts @@ -0,0 +1,32 @@ +type CompabilityMode = 'express'; + +const cases = { + express: [ + [/:id<.+>/g, ':id'], + [/:id\+/g, '*id'], + [/:id\*/g, '*id'], + [/:id\{.+\}/g, '*id'], + [/([a-zA-Z0-9:/_.]+)\/([*:])id\?/g, '$1{/$2id}'], + [/([*:])id\?/g, '{$1id}'], + ], +} as const; + +export function convertPath(path: string, mode: CompabilityMode): string { + switch (mode) { + case 'express': { + let newPath = path; + + for (const [regex, replacement] of cases.express) { + const match = newPath.match(regex); + + if (!match) { + continue; + } + + newPath = newPath.replace(regex, replacement); + } + + return newPath; + } + } +} diff --git a/packages/paths/lib/get-token-parameters.ts b/packages/paths/lib/get-token-parameters.ts new file mode 100644 index 0000000..5089f8c --- /dev/null +++ b/packages/paths/lib/get-token-parameters.ts @@ -0,0 +1,39 @@ +export function getTokenParameters(params: RegExpMatchArray | null) { + if (!params) { + return null; + } + + const name = params[1]; + let genericProps; + let arrayProps; + let modificator; + + for (const parameter of params.slice(2)) { + if (!parameter) { + continue; + } + + if (parameter.includes('<')) { + genericProps = parameter + .replaceAll(/\s/g, '') + .replace('<', '') + .replace('>', ''); + continue; + } + + if (parameter.includes('{')) { + arrayProps = parameter + .replace('{', '') + .replace('}', '') + .split(',') + .map((item) => parseInt(item)); + } + + if (['*', '?', '+'].includes(parameter)) { + modificator = parameter; + continue; + } + } + + return { name, genericProps, arrayProps, modificator }; +} diff --git a/packages/paths/lib/index.ts b/packages/paths/lib/index.ts new file mode 100644 index 0000000..1349757 --- /dev/null +++ b/packages/paths/lib/index.ts @@ -0,0 +1,4 @@ +export { compile } from './compile'; +export { convertPath } from './convert-path'; +export type { ParseUrlParams, Builder, Parser } from './types'; +export type { ValidatePath } from './validate-path'; diff --git a/packages/paths/lib/prepare-builder.ts b/packages/paths/lib/prepare-builder.ts new file mode 100644 index 0000000..53e8ba4 --- /dev/null +++ b/packages/paths/lib/prepare-builder.ts @@ -0,0 +1,37 @@ +import { Builder, Token } from './types'; + +export function prepareBuilder(tokens: Token[]): Builder { + return (params: any) => { + const result: string[] = []; + + if (tokens.length === 0) { + return '/'; + } + + for (const token of tokens) { + switch (token.type) { + case 'const': { + result.push(token.name); + break; + } + case 'parameter': { + if (!params[token.name]) { + continue; + } + + if (Array.isArray(params[token.name])) { + for (const param of params[token.name]) { + result.push(param.toString()); + } + } else { + result.push(params[token.name].toString()); + } + + break; + } + } + } + + return `/${result.join('/')}`; + }; +} diff --git a/packages/paths/lib/prepare-parser.ts b/packages/paths/lib/prepare-parser.ts new file mode 100644 index 0000000..a62974b --- /dev/null +++ b/packages/paths/lib/prepare-parser.ts @@ -0,0 +1,135 @@ +import { Parser, Token } from './types'; + +export function prepareParser(tokens: Token[]): Parser { + return (input) => { + const rawTokens = input + .split('/') + .map((part) => part.trim()) + .filter((part) => part !== ''); + + let params: any = null; + + function setKey(key: string, value: any) { + if (!params) { + params = {}; + } + + params[key] = value; + } + + if (tokens.length === 0) { + return rawTokens.length === 0 ? { path: input, params: null } : null; + } + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + + switch (token.type) { + case 'const': { + if (token.name !== rawTokens.shift()) { + return null; + } + + continue; + } + case 'parameter': { + const { arrayProps, genericProps, required } = token.payload; + + if (arrayProps) { + const array: any[] = []; + let rawToken; + + while (true) { + rawToken = rawTokens.shift(); + + if (!rawToken) { + break; + } + + switch (genericProps?.type) { + case 'number': { + if (isNaN(+rawToken)) { + return null; + } + + array.push(+rawToken); + break; + } + + case 'union': { + if (!genericProps.items.includes(rawToken)) { + return null; + } + + array.push(rawToken); + break; + } + default: { + array.push(rawToken); + break; + } + } + + if (array.length >= (arrayProps.max ?? Infinity)) { + break; + } + } + + if (array.length < (arrayProps.min ?? 0)) { + return null; + } + + if (rawTokens.length > 0 && !tokens[i + 1]) { + return null; + } + + setKey(token.name, array); + break; + } + + const rawToken = rawTokens.shift(); + + if (required && !rawToken) { + return null; + } + + if (!rawToken) { + setKey(token.name, undefined); + + continue; + } + + switch (genericProps?.type) { + case 'number': { + if (isNaN(+rawToken)) { + return null; + } + + setKey(token.name, +rawToken); + break; + } + + case 'union': { + if (!genericProps.items.includes(rawToken)) { + return null; + } + + setKey(token.name, rawToken); + break; + } + default: { + setKey(token.name, rawToken); + break; + } + } + } + } + } + + if (rawTokens.length > 0) { + return null; + } + + return { path: input, params }; + }; +} diff --git a/packages/paths/lib/types.ts b/packages/paths/lib/types.ts new file mode 100644 index 0000000..805fd48 --- /dev/null +++ b/packages/paths/lib/types.ts @@ -0,0 +1,221 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ + +export type ReplaceAll< + S, + From extends string, + To extends string, +> = From extends '' + ? S + : S extends `${infer R1}${From}${infer R2}` + ? `${R1}${To}${ReplaceAll}` + : S; + +type Parameter = { + [k in Name]: Payload; +}; + +type WithModificator< + Type, + T extends string, +> = T extends `${infer K}{${infer Start},${infer End}}+` + ? Type[] + : T extends `${infer K}{${infer Start},${infer End}}*` + ? Type[] + : T extends `${infer K}{${infer Start},${infer End}}?` + ? Type[] | undefined + : T extends `${infer K}{${infer Start},${infer End}}` + ? Type[] + : T extends `${infer K}+` + ? Type[] + : T extends `${infer K}*` + ? Type[] + : T extends `${infer K}?` + ? Type | undefined + : Type; + +type WithoutModificator = + T extends `${infer K}{${infer Start},${infer End}}${infer Modificator}` + ? K + : T extends `${infer K}{${infer Start},${infer End}}` + ? K + : T extends `${infer K}?` + ? K + : T extends `${infer K}*` + ? K + : T extends `${infer K}+` + ? K + : T; + +type Union< + T extends string, + Result = void, +> = T extends `${infer Start}|${infer Type}` + ? Union + : Result extends void + ? T + : Result | T; + +type GenericType = + ReplaceAll extends infer Trimmed + ? Trimmed extends `number` + ? number + : Trimmed extends `${infer A}|${infer B}` + ? Union + : Trimmed + : never; + +export type UrlParameter = + T extends `:${infer Name}<${infer Type}>${infer Modificator}` + ? Parameter, WithModificator, T>> + : T extends `:${infer Name}<${infer Type}>` + ? Parameter> + : T extends `:${infer Name}` + ? Parameter, WithModificator> + : never; + +type UrlParams< + T extends string, + Result = void, +> = T extends `/:${infer Parameter}/${infer Route}` + ? Result extends void + ? UrlParams<`/${Route}`, UrlParameter<`:${Parameter}`>> + : UrlParams<`/${Route}`, Result & UrlParameter<`:${Parameter}`>> + : T extends `/:${infer Parameter}` + ? Result extends void + ? UrlParameter<`:${Parameter}`> + : Result & UrlParameter<`:${Parameter}`> + : T extends `/${infer Static}/${infer Next}` + ? UrlParams<`/${Next}`, Result> + : Result; + +type Unwrap> = { + [k in keyof Result]: Result[k]; +}; + +/** + * @description Extracts the parameters from a URL string. + * @example ```ts + * type Params = ParseUrlParams<'/:id/:name'>; + * // ^----- { id: string, name: string } + * + * type Params = ParseUrlParams<'/:id+'>; + * // ^----- { id: string[] } + * + * type Params = ParseUrlParams<'/:id*'>; + * // ^----- { id: string[] } + * + * type Params = ParseUrlParams<'/:id?'>; + * // ^----- { id?: string } + * + * type Params = ParseUrlParams<'/:id+'>; + * // ^----- { id: number[] } + * + * type Params = ParseUrlParams<'/:id*'>; + * // ^----- { id: number[] } + * + * type Params = ParseUrlParams<'/:id'>; + * // ^----- { id: number } + * + * type Params = ParseUrlParams<'/:id?'>; + * // ^----- { id?: 'hello' | 'world' } + * + * type Params = ParseUrlParams<'/:id+'>; + * // ^----- { id: ('hello' | 'world')[] } + * + * type Params = ParseUrlParams<'/'>; + * // ^----- void + * ``` + */ +export type ParseUrlParams = Unwrap>; + +export type Builder = (params: T) => string; +export type Parser = (path: string) => { path: string; params: T } | null; + +interface BaseToken { + type: Type; + name: string; + payload: T; +} + +export type ConstToken = BaseToken<'const'>; +export type ParameterToken = BaseToken< + 'parameter', + { + required: boolean; + genericProps?: { type: 'union'; items: string[] } | { type: 'number' }; + arrayProps?: { min?: number; max?: number }; + } +>; + +export type Token = ConstToken | ParameterToken; + +type IsEqual = T extends Payload ? true : false; +type Assert = T; + +type TestsUnion = 'hello' | 'world'; + +type Case1 = ParseUrlParams<'/:id'>; +type Case2 = ParseUrlParams<'/:id?'>; +type Case3 = ParseUrlParams<'/:id*'>; +type Case4 = ParseUrlParams<'/:id+'>; +type Case5 = ParseUrlParams<'/:id'>; +type Case6 = ParseUrlParams<'/:id'>; +type Case7 = ParseUrlParams<'/:id'>; +type Case8 = ParseUrlParams<'/:id?'>; +type Case9 = ParseUrlParams<'/:id?'>; +type Case10 = ParseUrlParams<'/:id?'>; +type Case11 = ParseUrlParams<'/:id*'>; +type Case12 = ParseUrlParams<'/:id*'>; +type Case13 = ParseUrlParams<'/:id*'>; +type Case14 = ParseUrlParams<'/:id+'>; +type Case15 = ParseUrlParams<'/:id+'>; +type Case16 = ParseUrlParams<'/:id+'>; +type Case17 = ParseUrlParams<'/:id{1,2}'>; +type Case18 = ParseUrlParams<'/:id{1,2}'>; +type Case19 = ParseUrlParams<'/:id{1,2}'>; +type Case20 = ParseUrlParams<'/:id{1,2}'>; +type Case21 = ParseUrlParams<'/:id{1,2}?'>; +type Case22 = ParseUrlParams<'/:id{1,2}?'>; +type Case23 = ParseUrlParams<'/:id{1,2}?'>; +type Case24 = ParseUrlParams<'/:id{1,2}*'>; +type Case25 = ParseUrlParams<'/:id{1,2}*'>; +type Case26 = ParseUrlParams<'/:id{1,2}*'>; +type Case27 = ParseUrlParams<'/:id{1,2}+'>; +type Case28 = ParseUrlParams<'/:id{1,2}+'>; +type Case29 = ParseUrlParams<'/:id{1,2}+'>; +type Case30 = ParseUrlParams<'/:id'>; +type Case31 = ParseUrlParams<'/:id'>; + +type Tests = [ + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, + Assert>, +]; diff --git a/packages/paths/lib/validate-path.ts b/packages/paths/lib/validate-path.ts new file mode 100644 index 0000000..29d50e4 --- /dev/null +++ b/packages/paths/lib/validate-path.ts @@ -0,0 +1,166 @@ +import { ReplaceAll } from './types'; + +type SplitPath = string extends S + ? string[] + : S extends `${infer Head}/${infer Tail}` + ? Head extends '' + ? SplitPath + : Tail extends '' + ? [Head] + : [Head, ...SplitPath] + : [S]; + +type JoinPath = `/${Join}`; + +type Join = T['length'] extends 0 + ? never + : T extends [infer F, ...infer Rest] + ? Join extends infer Tail + ? [Tail] extends [never] + ? `${F & string}` + : `${F & string}/${Tail & string}` + : never + : never; + +type ValidateRange = Range extends `${infer L},${infer R}` + ? L extends `${number}` + ? R extends `${number}` + ? ['valid', `{${Range}`] + : ['invalid', `{${L},number}`] + : R extends `${number}` + ? ['invalid', `{number,${R}}`] + : ['invalid', `{number,number}`] + : ['invalid', `{number,number}`]; + +type ValidateTypes = GenTypes extends 'number' + ? 'valid' + : GenTypes extends '' + ? ['invalid', ``] + : // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents + GenTypes extends `${string}|${string}` | string + ? 'valid' + : ['invalid', ``]; + +type ValidateTokenBase< + Token extends string, + PostFix extends string = '', +> = Token extends `${infer Param}<${infer Types}>{${infer Range}}` // has Types, Range + ? ValidateTypes extends ['invalid', infer TypesReplacer] + ? ['invalid', `:${Param}${TypesReplacer & string}{${Range}}${PostFix}`] + : ValidateRange extends ['invalid', infer RangeReplacer] + ? ['invalid', `:${Param}<${Types}>${RangeReplacer & string}${PostFix}`] + : 'valid' + : Token extends `${infer Param}<${infer Types}>` // has only Types + ? ValidateTypes extends ['invalid', infer TypesReplacer] + ? ['invalid', `:${Param}${TypesReplacer & string}${PostFix}`] + : 'valid' + : Token extends `${infer Param}{${infer Range}}` // has only Range + ? ValidateRange extends ['invalid', infer RangeReplacer] + ? ['invalid', `:${Param}${RangeReplacer & string}${PostFix}`] + : 'valid' + : 'valid'; // base param + +type ValidateToken = Token extends `:${infer RawParam}` + ? RawParam extends `${infer WithoutModifier}${'*' | '?' | '+'}` + ? RawParam extends `${WithoutModifier}${infer Modifier}` + ? ValidateTokenBase + : never + : ValidateTokenBase + : 'valid'; + +type ValidateTokens< + Path, + Current, + Res extends string[] = [], +> = Current extends [infer Token, ...infer Rest] + ? ValidateToken> extends [ + 'invalid', + infer TokenReplacer, + ] + ? ['invalid', JoinPath<[...Res, TokenReplacer, ...Rest]>] + : ValidateTokens + : Path; + +export type ValidatePath = ValidateTokens>; + +type IsEqual = T extends Payload ? true : false; +type Assert = T; + +type Case1 = '/:id'; +type Case2 = '/:id?'; +type Case3 = '/:id*'; +type Case4 = '/:id+'; +type Case5 = '/:id'; +type Case6 = '/:id'; +type Case7 = '/:id'; +type Case8 = '/:id?'; +type Case9 = '/:id?'; +type Case10 = '/:id?'; +type Case11 = '/:id*'; +type Case12 = '/:id*'; +type Case13 = '/:id*'; +type Case14 = '/:id+'; +type Case15 = '/:id+'; +type Case16 = '/:id+'; +type Case17 = '/:id{1,2}'; +type Case18 = '/:id{1,2}'; +type Case19 = '/:id{1,2}'; +type Case20 = '/:id{1,2}'; +type Case21 = '/:id{1,2}?'; +type Case22 = '/:id{1,2}?'; +type Case23 = '/:id{1,2}?'; +type Case24 = '/:id{1,2}*'; +type Case25 = '/:id{1,2}*'; +type Case26 = '/:id{1,2}*'; +type Case27 = '/:id{1,2}+'; +type Case28 = '/:id{1,2}+'; +type Case29 = '/:id{1,2}+'; + +type Case30 = '/:id{err,err}+'; +type Case31 = '/:id{1,err}'; +type Case32 = '/:id{err,1}+'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +type Tests = [ + Assert, Case1>>, + Assert, Case2>>, + Assert, Case3>>, + Assert, Case4>>, + Assert, Case5>>, + Assert, Case6>>, + Assert, Case7>>, + Assert, Case8>>, + Assert, Case9>>, + Assert, Case10>>, + Assert, Case11>>, + Assert, Case12>>, + Assert, Case13>>, + Assert, Case14>>, + Assert, Case15>>, + Assert, Case16>>, + Assert, Case17>>, + Assert, Case18>>, + Assert, Case19>>, + Assert, Case20>>, + Assert, Case21>>, + Assert, Case22>>, + Assert, Case23>>, + Assert, Case24>>, + Assert, Case25>>, + Assert, Case26>>, + Assert, Case27>>, + Assert, Case28>>, + Assert, Case29>>, + Assert< + IsEqual< + ValidatePath, + ['invalid', '/:id{number,number}+'] + > + >, + Assert< + IsEqual, ['invalid', '/:id{1,number}']> + >, + Assert< + IsEqual, ['invalid', '/:id{number,1}+']> + >, +]; diff --git a/packages/paths/package.json b/packages/paths/package.json new file mode 100644 index 0000000..e4607a1 --- /dev/null +++ b/packages/paths/package.json @@ -0,0 +1,51 @@ +{ + "name": "@effector/router-paths", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "private": false, + "version": "1.0.0", + "description": "Router paths with power of effector", + "keywords": [ + "paths", + "argon", + "effector/router" + ], + "author": "movpushmov", + "contributors": [ + "Sergey Sova ", + "Anton Kosykh", + "Zero Bias " + ], + "homepage": "https://router.effector.dev/paths", + "license": "MIT", + "directories": { + "lib": "lib" + }, + "files": [ + "dist" + ], + "type": "module", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "repository": { + "type": "git", + "url": "git+https://github.com/effector/router.git", + "directory": "packages/paths" + }, + "scripts": { + "build": "vite build", + "test": "vitest run" + }, + "bugs": { + "url": "https://github.com/effector/router/issues" + }, + "gitHead": "9832d6cd282d6abd33c26ba4d03b9a1176110fc4" +} diff --git a/packages/paths/tests/convert.test.ts b/packages/paths/tests/convert.test.ts new file mode 100644 index 0000000..757ac06 --- /dev/null +++ b/packages/paths/tests/convert.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, test } from 'vitest'; +import { convertPath } from '../lib'; + +describe('convert paths', () => { + test('const path', () => { + expect(convertPath('/', 'express')).toStrictEqual('/'); + }); + + test('path with params', () => { + expect(convertPath('/:id', 'express')).toStrictEqual('/:id'); + }); + + test('path with wildcard (+)', () => { + expect(convertPath('/:id+', 'express')).toStrictEqual('/*id'); + }); + + test('path with wildcard (*)', () => { + expect(convertPath('/:id*', 'express')).toStrictEqual('/*id'); + }); + + test('path with nullable params', () => { + expect(convertPath('/:id?', 'express')).toStrictEqual('/{:id}'); + }); + + test('path with nullable params', () => { + expect(convertPath('/files/:id?', 'express')).toStrictEqual('/files{/:id}'); + }); + + test('path with generics', () => { + expect(convertPath('/files/:id', 'express')).toStrictEqual( + '/files/:id', + ); + }); + + test('path with nullable generics', () => { + expect(convertPath('/files/:id?', 'express')).toStrictEqual( + '/files{/:id}', + ); + }); + + test('path with ranges', () => { + expect(convertPath('/files/:id{1,2}', 'express')).toStrictEqual( + '/files/*id', + ); + }); + + test('path with nullable ranges', () => { + expect(convertPath('/files/:id{1,2}?', 'express')).toMatch('/files{/*id}'); + }); +}); diff --git a/packages/paths/tests/index.test.ts b/packages/paths/tests/index.test.ts new file mode 100644 index 0000000..7553ebf --- /dev/null +++ b/packages/paths/tests/index.test.ts @@ -0,0 +1,439 @@ +import { describe, expect, test } from 'vitest'; +import { compile } from '../lib/compile'; + +describe('parse path', () => { + test('parse root path', () => { + const { parse } = compile('/'); + + expect(parse('/not-found')).toStrictEqual(null); + }); + + test('parse path without parameters', () => { + const { parse } = compile('/profile'); + + expect(parse('/profile')).toStrictEqual({ + path: '/profile', + params: null, + }); + }); + + test('parse path with default string parameter', () => { + const { parse } = compile('/profile/:id'); + + expect(parse('/profile/12323')).toStrictEqual({ + path: '/profile/12323', + params: { id: '12323' }, + }); + }); + + test('parse path with generic parameter (number)', () => { + const { parse } = compile('/profile/:id'); + + expect(parse('/profile/12323')).toStrictEqual({ + path: '/profile/12323', + params: { id: 12323 }, + }); + }); + + test('parse path with generic parameter with dummy spaces (number)', () => { + const { parse } = compile('/profile/:id< number >'); + + expect(parse('/profile/12323')).toStrictEqual({ + path: '/profile/12323', + params: { id: 12323 }, + }); + }); + + test('parse path with generic parameter (union)', () => { + const { parse } = compile('/profile/:id'); + + expect(parse('/profile/hello')).toStrictEqual({ + path: '/profile/hello', + params: { id: 'hello' }, + }); + + expect(parse('/profile/world')).toStrictEqual({ + path: '/profile/world', + params: { id: 'world' }, + }); + + expect(parse('/profile/test')).toStrictEqual(null); + }); + + test('parse path with generic parameter with dummy spaces (union)', () => { + const { parse } = compile('/profile/:id'); + + expect(parse('/profile/hello')).toStrictEqual({ + path: '/profile/hello', + params: { id: 'hello' }, + }); + + expect(parse('/profile/world')).toStrictEqual({ + path: '/profile/world', + params: { id: 'world' }, + }); + + expect(parse('/profile/test')).toStrictEqual(null); + }); + + test('parse path with range string parameter', () => { + const { parse } = compile('/profile/:id{3,4}'); + + expect(parse('/profile/1/2')).toStrictEqual(null); + + expect(parse('/profile/1/2/3')).toStrictEqual({ + path: '/profile/1/2/3', + params: { id: ['1', '2', '3'] }, + }); + + expect(parse('/profile/1/2/3/4')).toStrictEqual({ + path: '/profile/1/2/3/4', + params: { id: ['1', '2', '3', '4'] }, + }); + + expect(parse('/profile/1/2/3/4/5')).toStrictEqual(null); + }); + + test('parse path with range number parameter', () => { + const { parse } = compile('/profile/:id{3,4}'); + + expect(parse('/profile/1/2')).toStrictEqual(null); + + expect(parse('/profile/1/2/3')).toStrictEqual({ + path: '/profile/1/2/3', + params: { id: [1, 2, 3] }, + }); + + expect(parse('/profile/1/2/3/4')).toStrictEqual({ + path: '/profile/1/2/3/4', + params: { id: [1, 2, 3, 4] }, + }); + + expect(parse('/profile/1/2/3/4/5')).toStrictEqual(null); + }); + + test('parse path with range union parameter', () => { + const { parse } = compile('/profile/:id{3,4}'); + + expect(parse('/profile/test/hello/router')).toStrictEqual(null); + + expect(parse('/profile/hello/argon/router')).toStrictEqual({ + path: '/profile/hello/argon/router', + params: { id: ['hello', 'argon', 'router'] }, + }); + + expect(parse('/profile/hello/hello/argon/router')).toStrictEqual({ + path: '/profile/hello/hello/argon/router', + params: { id: ['hello', 'hello', 'argon', 'router'] }, + }); + + expect(parse('/profile/hello/hello/argon/argon/router')).toStrictEqual( + null, + ); + }); + + test('parse path with range string parameter with modificator ?', () => { + const { parse } = compile('/profile/:id{3,4}?'); + + expect(parse('/profile/1/2')).toStrictEqual(null); + + expect(parse('/profile/1/2/3')).toStrictEqual({ + path: '/profile/1/2/3', + params: { id: ['1', '2', '3'] }, + }); + + expect(parse('/profile/1/2/3/4')).toStrictEqual({ + path: '/profile/1/2/3/4', + params: { id: ['1', '2', '3', '4'] }, + }); + + expect(parse('/profile/1/2/3/4/5')).toStrictEqual(null); + }); + + test('parse path with default string parameter and modificator +', () => { + const { parse } = compile('/profile/:id+'); + + expect(parse('/profile/1')).toStrictEqual({ + path: '/profile/1', + params: { id: ['1'] }, + }); + + expect(parse('/profile/1/2')).toStrictEqual({ + path: '/profile/1/2', + params: { id: ['1', '2'] }, + }); + + expect(parse('/profile')).toStrictEqual(null); + }); + + test('parse path with default string parameter and modificator *', () => { + const { parse } = compile('/profile/:id*'); + + expect(parse('/profile/1')).toStrictEqual({ + path: '/profile/1', + params: { id: ['1'] }, + }); + + expect(parse('/profile/1/2')).toStrictEqual({ + path: '/profile/1/2', + params: { id: ['1', '2'] }, + }); + + expect(parse('/profile')).toStrictEqual({ + path: '/profile', + params: { id: [] }, + }); + }); + + test('parse path with default string parameter and modificator ?', () => { + const { parse } = compile('/profile/:id?'); + + expect(parse('/profile/1')).toStrictEqual({ + path: '/profile/1', + params: { id: '1' }, + }); + + expect(parse('/profile')).toStrictEqual({ + path: '/profile', + params: { id: undefined }, + }); + }); + + test('parse path with generic parameter (number) and modificator +', () => { + const { parse } = compile('/profile/:id+'); + + expect(parse('/profile/1')).toStrictEqual({ + path: '/profile/1', + params: { id: [1] }, + }); + + expect(parse('/profile/1/2')).toStrictEqual({ + path: '/profile/1/2', + params: { id: [1, 2] }, + }); + + expect(parse('/profile')).toStrictEqual(null); + }); + + test('parse path with generic parameter (number) and modificator *', () => { + const { parse } = compile('/profile/:id*'); + + expect(parse('/profile/1')).toStrictEqual({ + path: '/profile/1', + params: { id: [1] }, + }); + + expect(parse('/profile/1/2')).toStrictEqual({ + path: '/profile/1/2', + params: { id: [1, 2] }, + }); + + expect(parse('/profile')).toStrictEqual({ + path: '/profile', + params: { id: [] }, + }); + }); + + test('parse path with generic parameter (number) and modificator ?', () => { + const { parse } = compile('/profile/:id?'); + + expect(parse('/profile/1')).toStrictEqual({ + path: '/profile/1', + params: { id: 1 }, + }); + + expect(parse('/profile')).toStrictEqual({ + path: '/profile', + params: { id: undefined }, + }); + }); + + test('parse path with generic parameter (union) and modificator +', () => { + const { parse } = compile('/profile/:id+'); + + expect(parse('/profile/hello/world')).toStrictEqual({ + path: '/profile/hello/world', + params: { id: ['hello', 'world'] }, + }); + + expect(parse('/profile/world/hello')).toStrictEqual({ + path: '/profile/world/hello', + params: { id: ['world', 'hello'] }, + }); + + expect(parse('/profile/world')).toStrictEqual({ + path: '/profile/world', + params: { id: ['world'] }, + }); + + expect(parse('/profile/test')).toStrictEqual(null); + }); + + test('parse path with generic parameter (union) and modificator *', () => { + const { parse } = compile('/profile/:id*'); + + expect(parse('/profile/hello/world')).toStrictEqual({ + path: '/profile/hello/world', + params: { id: ['hello', 'world'] }, + }); + + expect(parse('/profile/world/hello')).toStrictEqual({ + path: '/profile/world/hello', + params: { id: ['world', 'hello'] }, + }); + + expect(parse('/profile/world')).toStrictEqual({ + path: '/profile/world', + params: { id: ['world'] }, + }); + + expect(parse('/profile')).toStrictEqual({ + path: '/profile', + params: { id: [] }, + }); + + expect(parse('/profile/test')).toStrictEqual(null); + }); + + test('parse path with generic parameter (union) and modificator ?', () => { + const { parse } = compile('/profile/:id?'); + + expect(parse('/profile/hello')).toStrictEqual({ + path: '/profile/hello', + params: { id: 'hello' }, + }); + + expect(parse('/profile/world')).toStrictEqual({ + path: '/profile/world', + params: { id: 'world' }, + }); + + expect(parse('/profile')).toStrictEqual({ + path: '/profile', + params: { id: undefined }, + }); + }); + + test('parse path without parameter but with extra parts', () => { + const fp = compile('/profile'); + const sp = compile('/profile/:id'); + + expect(fp.parse('/profile/1')).toStrictEqual(null); + expect(sp.parse('/profile/1')).toStrictEqual({ + path: '/profile/1', + params: { id: '1' }, + }); + }); + + test('parse path with array parameter and extra parts', () => { + const fp = compile('/items/:id{2,2}/:hi'); + + expect(fp.parse('/items/1/2/hello')).toStrictEqual({ + path: '/items/1/2/hello', + params: { id: ['1', '2'], hi: 'hello' }, + }); + }); +}); + +describe('build path', () => { + test('build root path', () => { + const { build } = compile('/'); + + expect(build()).toStrictEqual('/'); + }); + + test('build path without parameters', () => { + const { build } = compile('/profile'); + + expect(build()).toBe('/profile'); + }); + + test('build path with default string parameter', () => { + const { build } = compile('/profile/:id'); + + expect(build({ id: '123' })).toBe('/profile/123'); + }); + + test('build path with generic parameter (number)', () => { + const { build } = compile('/profile/:id'); + + expect(build({ id: 123 })).toBe('/profile/123'); + }); + + test('build path with generic parameter (union)', () => { + const { build } = compile('/profile/:id'); + + expect(build({ id: 'hello' })).toBe('/profile/hello'); + expect(build({ id: 'world' })).toBe('/profile/world'); + }); + + test('build path with default string parameter and modificator +', () => { + const { build } = compile('/profile/:id+'); + + expect(build({ id: ['123', '321'] })).toBe('/profile/123/321'); + expect(build({ id: ['123'] })).toBe('/profile/123'); + }); + + test('build path with default string parameter and modificator *', () => { + const { build } = compile('/profile/:id*'); + + expect(build({ id: ['123', '321'] })).toBe('/profile/123/321'); + expect(build({ id: ['123'] })).toBe('/profile/123'); + expect(build({ id: [] })).toBe('/profile'); + }); + + test('build path with default string parameter and modificator ?', () => { + const { build } = compile('/profile/:id?'); + + expect(build({ id: 'world' })).toBe('/profile/world'); + expect(build({ id: undefined })).toBe('/profile'); + }); + + test('build path with generic parameter (number) and modificator +', () => { + const { build } = compile('/profile/:id+'); + + expect(build({ id: [123, 321] })).toBe('/profile/123/321'); + expect(build({ id: [123] })).toBe('/profile/123'); + }); + + test('build path with generic parameter (number) and modificator *', () => { + const { build } = compile('/profile/:id*'); + + expect(build({ id: [123, 321] })).toBe('/profile/123/321'); + expect(build({ id: [123] })).toBe('/profile/123'); + expect(build({ id: [] })).toBe('/profile'); + }); + + test('build path with generic parameter (number) and modificator ?', () => { + const { build } = compile('/profile/:id?'); + + expect(build({ id: 123 })).toBe('/profile/123'); + expect(build({ id: undefined })).toBe('/profile'); + }); + + test('build path with generic parameter (union) and modificator +', () => { + const { build } = compile('/profile/:id+'); + + expect(build({ id: ['hello', 'world'] })).toBe('/profile/hello/world'); + expect(build({ id: ['hello', 'hello'] })).toBe('/profile/hello/hello'); + expect(build({ id: ['hello'] })).toBe('/profile/hello'); + expect(build({ id: [] })).toBe('/profile'); + }); + + test('build path with generic parameter (union) and modificator *', () => { + const { build } = compile('/profile/:id*'); + + expect(build({ id: ['hello', 'world'] })).toBe('/profile/hello/world'); + expect(build({ id: ['hello', 'hello'] })).toBe('/profile/hello/hello'); + expect(build({ id: ['hello'] })).toBe('/profile/hello'); + expect(build({ id: [] })).toBe('/profile'); + }); + + test('build path with generic parameter (union) and modificator ?', () => { + const { build } = compile('/profile/:id?'); + + expect(build({ id: 'hello' })).toBe('/profile/hello'); + expect(build({ id: 'world' })).toBe('/profile/world'); + expect(build({ id: undefined })).toBe('/profile'); + }); +}); diff --git a/packages/paths/vite.config.ts b/packages/paths/vite.config.ts new file mode 100644 index 0000000..aee9f84 --- /dev/null +++ b/packages/paths/vite.config.ts @@ -0,0 +1,32 @@ +import { defineConfig } from 'vite'; +import { resolve } from 'path'; +import dts from 'vite-plugin-dts'; +import babel from 'vite-plugin-babel'; + +export default defineConfig({ + mode: 'production', + build: { + outDir: resolve(__dirname, 'dist'), + lib: { + entry: resolve(__dirname, 'lib/index.ts'), + fileName: 'index', + formats: ['es', 'cjs'], + }, + }, + plugins: [ + babel({ filter: /.[jt]sx?/ }), + dts({ + outDir: resolve(__dirname, 'dist'), + entryRoot: resolve(__dirname, 'lib'), + exclude: [ + resolve(__dirname, '../effector/router-core'), + resolve(__dirname, '../effector/router-react'), + resolve(__dirname, '../effector/router-react-native'), + ], + staticImport: true, + insertTypesEntry: true, + rollupTypes: true, + }), + ], + test: {}, +}); diff --git a/packages/react-native/README.md b/packages/react-native/README.md new file mode 100644 index 0000000..6bd6094 --- /dev/null +++ b/packages/react-native/README.md @@ -0,0 +1 @@ +# ☄️ effector/router-react-native diff --git a/packages/react-native/babel.config.json b/packages/react-native/babel.config.json new file mode 100644 index 0000000..ff856b1 --- /dev/null +++ b/packages/react-native/babel.config.json @@ -0,0 +1,22 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "modules": false, + "targets": "> 0.25%, not dead" + } + ], + "@babel/preset-typescript", + "@babel/preset-react" + ], + "plugins": [ + [ + "effector/babel-plugin", + { + "factories": ["@effector/router"], + "addNames": true + } + ] + ] +} diff --git a/packages/react-native/lib/bottom-tabs-navigator.tsx b/packages/react-native/lib/bottom-tabs-navigator.tsx new file mode 100644 index 0000000..90fd8c7 --- /dev/null +++ b/packages/react-native/lib/bottom-tabs-navigator.tsx @@ -0,0 +1,162 @@ +import * as React from 'react'; +import { useEffect } from 'react'; +import { + createBottomTabNavigator, + BottomTabNavigationOptions, +} from '@react-navigation/bottom-tabs'; +import type { Router, Route } from '@effector/router'; +import type { RouteView } from '@effector/router-react'; +import { createWatch } from 'effector'; +import { useProvidedScope } from 'effector-react'; + +export type BottomTabsNavigatorConfig = { + router: Router; + routes: RouteView[]; + screenOptions?: BottomTabNavigationOptions; + initialRouteName?: string; +}; + +export type { BottomTabNavigationOptions }; + +const Tab = createBottomTabNavigator(); + +function getRouteKey(route: Route | Router, index: number): string { + if ('path' in route && route.path) { + return route.path; + } + return `tab-${index}`; +} + +function getRouteName(route: Route | Router, index: number): string { + if ('path' in route && route.path) { + return route.path.replace(/\//g, '') || `Tab${index}`; + } + return `Tab${index}`; +} + +function getRouteTitle(route: Route | Router, index: number): string { + if ('path' in route && route.path) { + const pathParts = route.path.split('/').filter(Boolean); + return pathParts[pathParts.length - 1] || `Tab ${index + 1}`; + } + return `Tab ${index + 1}`; +} + +/** + * Creates an Bottom Tabs Navigator that integrates with React Navigation + * + * @example + * ```tsx + * import { createBottomTabsNavigator } from '@effector/router-react-native'; + * import { router } from './router'; + * import { HomeScreen, SearchScreen, ProfileScreen } from './screens'; + * + * const TabsNavigator = createBottomTabsNavigator({ + * router, + * routes: [HomeScreen, SearchScreen, ProfileScreen], + * screenOptions: { + * tabBarActiveTintColor: '#007AFF', + * tabBarInactiveTintColor: '#8e8e93', + * }, + * }); + * + * function App() { + * return ( + * + * + * + * ); + * } + * ``` + */ +export function createBottomTabsNavigator(config: BottomTabsNavigatorConfig): { + Navigator: React.ComponentType; +} { + const { router: Router, routes, screenOptions, initialRouteName } = config; + + const BottomTabsNavigator = function BottomTabsNavigator() { + const scope = useProvidedScope(); + const navigationRef = React.useRef(null); + + // Sync Router state with React Navigation + useEffect(() => { + const subscription = createWatch({ + unit: Router.$activeRoutes, + scope: scope ?? undefined, + fn: (activeRoutes) => { + if (!navigationRef.current || activeRoutes.length === 0) return; + + // Find the last active route + const lastActiveRoute = activeRoutes[activeRoutes.length - 1]; + const matchingIndex = routes.findIndex( + (r) => r.route === lastActiveRoute, + ); + + if (matchingIndex !== -1) { + const routeName = getRouteName( + routes[matchingIndex].route, + matchingIndex, + ); + + // Navigate to the route in React Navigation + try { + navigationRef.current.navigate(routeName); + } catch (error) { + console.error(error); + // Route might not be mounted yet + } + } + }, + }); + + return () => subscription.unsubscribe(); + }, [scope]); + + // Handle tab press to open route in Router + const createTabPressHandler = React.useCallback((routeView: RouteView) => { + return () => { + if ( + 'open' in routeView.route && + typeof routeView.route.open === 'function' + ) { + routeView.route.open(); + } + }; + }, []); + + return ( + + {routes.map((routeView, index) => { + const routeName = getRouteName(routeView.route, index); + const routeKey = getRouteKey(routeView.route, index); + const title = getRouteTitle(routeView.route, index); + + return ( + { + // Prevent default navigation + e.preventDefault(); + // Open route via Router + createTabPressHandler(routeView)(); + }, + }} + /> + ); + })} + + ); + }; + + return { Navigator: BottomTabsNavigator }; +} diff --git a/packages/react-native/lib/index.ts b/packages/react-native/lib/index.ts new file mode 100644 index 0000000..89c1d39 --- /dev/null +++ b/packages/react-native/lib/index.ts @@ -0,0 +1,12 @@ +export { createStackNavigator } from './stack-navigator'; +export { createBottomTabsNavigator } from './bottom-tabs-navigator'; + +export type { + StackNavigatorConfig, + StackNavigatorOptions, +} from './stack-navigator'; + +export type { + BottomTabsNavigatorConfig, + BottomTabNavigationOptions, +} from './bottom-tabs-navigator'; diff --git a/packages/react-native/lib/stack-navigator.tsx b/packages/react-native/lib/stack-navigator.tsx new file mode 100644 index 0000000..8905e77 --- /dev/null +++ b/packages/react-native/lib/stack-navigator.tsx @@ -0,0 +1,127 @@ +import * as React from 'react'; +import { useEffect } from 'react'; +import { + createStackNavigator as createReactNavigationStackNavigator, + StackNavigationOptions, +} from '@react-navigation/stack'; +import type { Router, Route } from '@effector/router'; +import type { RouteView } from '@effector/router-react'; +import { useOpenedViews } from '@effector/router-react'; +import { createWatch } from 'effector'; +import { useProvidedScope } from 'effector-react'; + +export type StackNavigatorConfig = { + router: Router; + routes: RouteView[]; + screenOptions?: StackNavigationOptions; + initialRouteName?: string; +}; + +export type { StackNavigationOptions as StackNavigatorOptions }; + +const Stack = createReactNavigationStackNavigator(); + +function getRouteKey(route: Route | Router, index: number): string { + if ('path' in route && route.path) { + return route.path; + } + return `route-${index}`; +} + +function getRouteName(route: Route | Router, index: number): string { + if ('path' in route && route.path) { + return route.path; + } + return `Route${index}`; +} + +/** + * Creates an Stack Navigator that integrates with React Navigation + * + * @example + * ```tsx + * import { createStackNavigator } from '@effector/router-react-native'; + * import { router } from './router'; + * import { HomeScreen, ProfileScreen } from './screens'; + * + * const StackNavigator = createStackNavigator({ + * router, + * routes: [HomeScreen, ProfileScreen], + * screenOptions: { + * headerStyle: { backgroundColor: '#f4511e' }, + * headerTintColor: '#fff', + * }, + * }); + * + * function App() { + * return ( + * + * + * + * ); + * } + * ``` + */ +export function createStackNavigator(config: StackNavigatorConfig): { + Navigator: React.ComponentType; +} { + const { router: Router, routes, screenOptions, initialRouteName } = config; + + const StackNavigator = function StackNavigator() { + const scope = useProvidedScope(); + const openedViews = useOpenedViews(routes); + const navigationRef = React.useRef(null); + + // Sync Router state with React Navigation + useEffect(() => { + const subscription = createWatch({ + unit: Router.$path, + scope: scope ?? undefined, + fn: (path) => { + if (!navigationRef.current || !path) return; + + // Find the matching route for the current path + const matchingView = openedViews[openedViews.length - 1]; + if (matchingView) { + const routeName = getRouteName( + matchingView.route, + routes.findIndex((r) => r.route === matchingView.route), + ); + + // Navigate to the route in React Navigation + try { + navigationRef.current.navigate(routeName); + } catch (error) { + console.error(error); + } + } + }, + }); + + return () => subscription.unsubscribe(); + }, [openedViews, scope]); + + return ( + + {routes.map((routeView, index) => { + const routeName = getRouteName(routeView.route, index); + const routeKey = getRouteKey(routeView.route, index); + + return ( + + ); + })} + + ); + }; + + return { Navigator: StackNavigator }; +} diff --git a/packages/react-native/package.json b/packages/react-native/package.json new file mode 100644 index 0000000..a1a04c5 --- /dev/null +++ b/packages/react-native/package.json @@ -0,0 +1,68 @@ +{ + "name": "@effector/router-react-native", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "private": false, + "version": "1.0.0", + "description": "React Native bindings for effector router with React Navigation integration", + "keywords": [ + "effector", + "argon", + "atomic", + "router", + "effector/router", + "react-native", + "react-navigation" + ], + "author": "movpushmov", + "contributors": [ + "Sergey Sova ", + "Anton Kosykh", + "Zero Bias " + ], + "homepage": "https://router.effector.dev/react-native", + "license": "MIT", + "type": "module", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "directories": { + "lib": "lib" + }, + "files": [ + "dist" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/effector/router.git", + "directory": "packages/react-native" + }, + "scripts": { + "build": "vite build", + "test": "" + }, + "bugs": { + "url": "https://github.com/effector/router/issues" + }, + "dependencies": { + "@effector/router": "workspace:*", + "@effector/router-react": "workspace:*" + }, + "peerDependencies": { + "@react-navigation/bottom-tabs": ">=6", + "@react-navigation/native": ">=6", + "@react-navigation/stack": ">=6", + "effector": ">=23", + "effector-react": ">=23", + "react": "18 || 19", + "react-native": ">=0.70" + }, + "gitHead": "9832d6cd282d6abd33c26ba4d03b9a1176110fc4" +} diff --git a/packages/react-native/vite.config.ts b/packages/react-native/vite.config.ts new file mode 100644 index 0000000..cb09376 --- /dev/null +++ b/packages/react-native/vite.config.ts @@ -0,0 +1,56 @@ +import { defineConfig } from 'vite'; +import { resolve } from 'path'; +import dts from 'vite-plugin-dts'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + mode: 'production', + build: { + lib: { + entry: resolve(__dirname, 'lib/index.ts'), + fileName: 'index', + formats: ['es', 'cjs'], + }, + rollupOptions: { + external: [ + 'effector', + 'effector-react', + '@effector/router', + '@effector/router-react', + '@react-navigation/native', + '@react-navigation/bottom-tabs', + '@react-navigation/stack', + 'react', + 'react-native', + 'react/jsx-runtime', + ], + output: { + globals: { + react: 'react', + effector: 'effector', + 'effector-react': 'effector-react', + '@effector/router': '@effector/router', + 'react/jsx-runtime': 'react/jsx-runtime', + }, + }, + }, + }, + plugins: [ + react({ + jsxRuntime: 'automatic', + }), + dts({ + outDir: resolve(__dirname, 'dist'), + entryRoot: resolve(__dirname, 'lib'), + exclude: [ + resolve(__dirname, 'tests'), + resolve(__dirname, '../effector/router-paths'), + resolve(__dirname, '../effector/router-core'), + resolve(__dirname, '../effector/router-react'), + ], + staticImport: true, + insertTypesEntry: true, + rollupTypes: true, + }), + ], +}); diff --git a/packages/react/README.md b/packages/react/README.md new file mode 100644 index 0000000..71c2fa8 --- /dev/null +++ b/packages/react/README.md @@ -0,0 +1,7 @@ +# ☄️ effector/router-react + +## Polyfills + +May require polyfills for: + +- `Array.prototype.at()` diff --git a/packages/react/babel.config.json b/packages/react/babel.config.json new file mode 100644 index 0000000..ff856b1 --- /dev/null +++ b/packages/react/babel.config.json @@ -0,0 +1,22 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "modules": false, + "targets": "> 0.25%, not dead" + } + ], + "@babel/preset-typescript", + "@babel/preset-react" + ], + "plugins": [ + [ + "effector/babel-plugin", + { + "factories": ["@effector/router"], + "addNames": true + } + ] + ] +} diff --git a/packages/react/lib/context.ts b/packages/react/lib/context.ts new file mode 100644 index 0000000..6d9cb9f --- /dev/null +++ b/packages/react/lib/context.ts @@ -0,0 +1,8 @@ +import type { Router } from '@effector/router'; +import { createContext } from 'react'; +import type { RouteView } from './types'; + +export const RouterProviderContext = createContext(null); +export const OutletContext = createContext<{ children: RouteView[] } | null>( + null, +); diff --git a/packages/react/lib/create-lazy-route-view.tsx b/packages/react/lib/create-lazy-route-view.tsx new file mode 100644 index 0000000..40804d0 --- /dev/null +++ b/packages/react/lib/create-lazy-route-view.tsx @@ -0,0 +1,54 @@ +import { lazy, Suspense } from 'react'; +import type { CreateLazyRouteViewProps, RouteView } from './types'; +import type { InternalRoute } from '@effector/router'; + +/** + * @description Creates Lazy route view with async bundle load + * @link https://router.effector.dev/react/create-lazy-route-view.html + * @param props Lazy route view props + * @returns RouteView + * @example ```ts + * // profile.tsx + * export default function () { + * return <>...; + * } + * + * // index.ts + * import { createLazyRouteView } from '@effector/router-react'; + * import { routes } from '@shared/routing'; + * import { MainLayout } from '@layouts'; + * + * export const ProfileScreen = createLazyRouteView({ + * route: routes.profile, + * view: () => import('./profile'), + * fallback: () => ':(', + * layout: MainLayout, + * }); + * ``` + */ +export function createLazyRouteView( + props: CreateLazyRouteViewProps, +): RouteView { + (props.route as InternalRoute).internal.setAsyncImport(props.view); + const View = lazy(props.view); + const { layout: Layout, fallback: Fallback = () => <> } = props; + + const view = Layout + ? () => ( + + }> + + + + ) + : () => ( + }> + + + ); + + return { + route: props.route, + view, + }; +} diff --git a/packages/react/lib/create-route-view.tsx b/packages/react/lib/create-route-view.tsx new file mode 100644 index 0000000..24c095c --- /dev/null +++ b/packages/react/lib/create-route-view.tsx @@ -0,0 +1,42 @@ +import type { CreateRouteViewProps, RouteView } from './types'; + +/** + * @description Creates Route view without async bundle load + * @link https://router.effector.dev/react/create-route-view.html + * @param props Route view props + * @returns RouteView + * @example ```ts + * import { createRouteView } from '@effector/router-react'; + * import { routes } from '@shared/routing'; + * import { MainLayout } from '@layouts'; + * + * function Profile() { + * return <>...; + * } + * + * export const ProfileScreen = createRouteView({ + * route: routes.profile, + * view: Profile, + * layout: MainLayout, + * }); + * ``` + */ +export function createRouteView( + props: CreateRouteViewProps, +): RouteView { + const { layout: Layout, view: View, children } = props; + + const view = Layout + ? () => ( + + + + ) + : () => ; + + return { + route: props.route, + view, + children, + }; +} diff --git a/packages/react/lib/create-routes-view.tsx b/packages/react/lib/create-routes-view.tsx new file mode 100644 index 0000000..ba95d7d --- /dev/null +++ b/packages/react/lib/create-routes-view.tsx @@ -0,0 +1,50 @@ +import { type ComponentType, createElement } from 'react'; +import { OutletContext } from './context'; +import { useOpenedViews } from './use-opened-views'; +import type { RouteView } from './types'; + +interface CreateRoutesViewProps { + routes: RouteView[]; + otherwise?: ComponentType; +} + +/** + * @description Create routes view which renders current opened route. `Don't forget add `! + * @param props Routes view config + * @link https://router.effector.dev/react/create-routes-view.html + * @returns RoutesView + * @example ```tsx + * import { createRoutesView } from '@effector/router-react'; + * import { router } from './router'; + * // feed screen & profile screen must be created with createRouteView! + * import { FeedScreen, ProfileScreen } from './screens'; + * + * const RoutesView = createRoutesView({ routes: [FeedScreen, ProfileScreen] }); + * + * // then you can use it like react component: + * function App() { + * return ( + * + * + * + * ); + * } + * ``` + */ +export const createRoutesView = (props: CreateRoutesViewProps) => { + const { routes, otherwise: NotFound } = props; + + return () => { + const openedView = useOpenedViews(routes).at(-1); + + if (!openedView) { + return NotFound ? : null; + } + + return ( + + {createElement(openedView.view)} + + ); + }; +}; diff --git a/packages/react/lib/index.ts b/packages/react/lib/index.ts new file mode 100644 index 0000000..7500cc0 --- /dev/null +++ b/packages/react/lib/index.ts @@ -0,0 +1,17 @@ +export { RouterProvider } from './router-provider'; +export { Link } from './link'; +export { createRouteView } from './create-route-view'; +export { createRoutesView } from './create-routes-view'; +export { createLazyRouteView } from './create-lazy-route-view'; +export { useRouter, useRouterContext } from './use-router'; +export { withLayout } from './with-layout'; +export type { + LinkProps, + CreateLazyRouteViewProps, + CreateRouteViewProps, + RouteView, +} from './types'; +export { Outlet } from './outlet'; +export { useOpenedViews } from './use-opened-views'; +export { useIsOpened } from './use-is-opened'; +export { useLink } from './use-link'; diff --git a/packages/react/lib/link.tsx b/packages/react/lib/link.tsx new file mode 100644 index 0000000..2701886 --- /dev/null +++ b/packages/react/lib/link.tsx @@ -0,0 +1,73 @@ +import type { RouteOpenedPayload } from '@effector/router'; +import { type ForwardedRef, forwardRef, type ReactNode } from 'react'; +import type { LinkProps } from './types'; +import { useLink } from './use-link'; + +type ForwardedLink = ( + props: LinkProps & { ref?: ForwardedRef }, +) => ReactNode; + +/** + * @description Navigates user to provided route on click + * @link https://router.effector.dev/react/link.html + * @example ```tsx + * import { Link } from '@effector/router-react'; + * import { routes } from '@shared/routing'; + * + * function Profile({ user }) { + * return ( + * <> + * Settings + * + * {user.posts.map((post) => ( + * + * Edit post + * + * ))} + * + * ); + * } + * ``` + */ +export const Link: ForwardedLink = forwardRef< + HTMLAnchorElement, + LinkProps +>((props, ref) => { + const { to, params, onClick, replace, query, ...anchorProps } = props; + + const { path, onOpen } = useLink(to, params); + + return ( + { + onClick?.(e); + + // allow user to prevent navigation + if (e.defaultPrevented) { + return; + } + + // let browser handle "_blank" target and etc + if (anchorProps.target && anchorProps.target !== '_self') { + return; + } + + // skip modified events (like cmd + click to open the link in new tab) + if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) { + return; + } + + e.preventDefault(); + + onOpen({ + params: params || {}, + replace, + query, + } as RouteOpenedPayload); + }} + /> + ); +}); diff --git a/packages/react/lib/outlet.tsx b/packages/react/lib/outlet.tsx new file mode 100644 index 0000000..6daa2b6 --- /dev/null +++ b/packages/react/lib/outlet.tsx @@ -0,0 +1,41 @@ +import { OutletContext } from './context'; +import { createElement, useContext } from 'react'; +import { useOpenedViews } from './use-opened-views'; + +/** + * @description Outlet component for nested routes + * @link https://router.effector.dev/react/outlet.html + * @example ```ts + * export const RoutesView = createRoutesView([ + * createRouteView({ + * route: routes.profile, + * view: ProfileScreen, + * children: [ + * createRouteView({ route: routes.settings, view: SettingsScreen }), + * ] + * }), + * ]); + * + * // profile.tsx + * export const ProfileScreen = () => { + * // will render settings screen when profile route is opened + * // and settings route is active + * return ( + * <> + *
Profile
+ * + * + * ); + * }; + * ``` + */ +export const Outlet = () => { + const { children } = useContext(OutletContext) ?? { children: [] }; + const openedView = useOpenedViews(children).at(-1); + + if (!openedView) { + return null; + } + + return createElement(openedView.view); +}; diff --git a/packages/react/lib/router-provider.tsx b/packages/react/lib/router-provider.tsx new file mode 100644 index 0000000..04c05fb --- /dev/null +++ b/packages/react/lib/router-provider.tsx @@ -0,0 +1,20 @@ +import type { ReactNode } from 'react'; +import type { Router } from '@effector/router'; +import { RouterProviderContext } from './context'; + +interface RouterProviderProps { + children?: ReactNode; + router: Router; +} + +/** + * @description Provides router in React tree + * @param props Router provider config + */ +export const RouterProvider = (props: RouterProviderProps) => { + return ( + + {props.children} + + ); +}; diff --git a/packages/react/lib/types.ts b/packages/react/lib/types.ts new file mode 100644 index 0000000..1045945 --- /dev/null +++ b/packages/react/lib/types.ts @@ -0,0 +1,45 @@ +import type { Route, OpenPayloadBase, Router } from '@effector/router'; +import type { AnchorHTMLAttributes, ComponentType, FC, ReactNode } from 'react'; + +type LayoutComponent = ComponentType<{ children: ReactNode }>; +type RouteViewWithLayout = RouteView & { layout?: LayoutComponent }; + +interface CreateBaseRouteViewProps { + route: Route | Router; + layout?: LayoutComponent; + children?: RouteViewWithLayout[]; +} + +export interface CreateRouteViewProps< + T extends object | void = void, +> extends CreateBaseRouteViewProps { + view: ComponentType; +} + +export interface CreateLazyRouteViewProps< + T extends object | void = void, +> extends CreateBaseRouteViewProps { + view: () => Promise<{ default: ComponentType }>; + fallback?: ComponentType; +} + +export interface RouteView { + route: Route | Router; + view: FC; + children?: RouteView[]; +} + +type AnchorProps = Omit, 'href'>; + +type BaseLinkProps = { + to: Route; + children?: ReactNode; +} & AnchorProps & + OpenPayloadBase; + +export type LinkProps = Params extends + | Record + | void + | undefined + ? BaseLinkProps & { params?: Params } + : BaseLinkProps & { params: Params }; diff --git a/packages/react/lib/use-is-opened.ts b/packages/react/lib/use-is-opened.ts new file mode 100644 index 0000000..e2f173a --- /dev/null +++ b/packages/react/lib/use-is-opened.ts @@ -0,0 +1,8 @@ +import { is, type Route, type Router } from '@effector/router'; +import { useUnit } from 'effector-react'; + +export function useIsOpened(route: Route | Router) { + return is.router(route) + ? useUnit(route.$activeRoutes).length > 0 + : useUnit(route.$isOpened); +} diff --git a/packages/react/lib/use-link.ts b/packages/react/lib/use-link.ts new file mode 100644 index 0000000..7097a15 --- /dev/null +++ b/packages/react/lib/use-link.ts @@ -0,0 +1,28 @@ +import type { Route } from '@effector/router'; +import type { InternalRoute } from '@effector/router'; +import { useRouterContext } from './use-router'; +import { useUnit } from 'effector-react'; + +export function useLink( + to: Route, + params: T, +) { + const { knownRoutes } = useRouterContext(); + const target = knownRoutes.find( + ({ route }) => route === (to as unknown as InternalRoute), + ); + + const { onOpen } = useUnit(to); + + if (!target) { + console.error(`[useLink route log]`, to); + throw new Error( + `[useLink] Route not found. Maybe it is not passed into createRouter?`, + ); + } + + return { + path: target.build(params ?? undefined), + onOpen, + }; +} diff --git a/packages/react/lib/use-opened-views.ts b/packages/react/lib/use-opened-views.ts new file mode 100644 index 0000000..f41a1c2 --- /dev/null +++ b/packages/react/lib/use-opened-views.ts @@ -0,0 +1,84 @@ +import { useEffect, useMemo, useState } from 'react'; +import type { RouteView } from './types'; +import type { InternalRoute } from '@effector/router'; +import { useProvidedScope } from 'effector-react'; +import { is } from '@effector/router'; +import { createWatch, Subscription, type Scope, type Store } from 'effector'; + +function getStoreValue(store: Store, scope?: Scope | null) { + return scope ? scope.getState(store) : store.getState(); +} + +function getVisibilities(routes: RouteView[], scope?: Scope | null) { + return routes.map((view) => { + if (is.router(view.route)) { + return getStoreValue(view.route.$activeRoutes, scope).length > 0; + } + + return getStoreValue(view.route.$isOpened, scope); + }); +} + +export function useOpenedViews(routes: RouteView[]) { + const scope = useProvidedScope(); + const [visibilities, setVisibilities] = useState( + getVisibilities(routes, scope), + ); + + useEffect(() => { + const subscriptions: Subscription[] = []; + + for (const [index, view] of routes.entries()) { + if (is.router(view.route)) { + const router = view.route; + + const subscription = createWatch({ + unit: router.$activeRoutes, + scope: scope ?? undefined, + fn: (routes) => { + setVisibilities((prev) => { + const newVisibilities = [...prev]; + newVisibilities[index] = routes.length > 0; + + return newVisibilities; + }); + }, + }); + + subscriptions.push(subscription); + } else { + subscriptions.push( + createWatch({ + unit: view.route.$isOpened, + scope: scope ?? undefined, + fn: (isOpened) => { + setVisibilities((prev) => { + const newVisibilities = [...prev]; + newVisibilities[index] = isOpened; + return newVisibilities; + }); + }, + }), + ); + } + } + + return () => { + for (const subscription of subscriptions) { + subscription.unsubscribe(); + } + }; + }, [routes, scope]); + + return useMemo(() => { + const filtered = routes.filter((_, i) => visibilities[i]); + + return filtered.reduce( + (filtered, view) => + filtered.filter( + (r) => r.route !== (view.route as InternalRoute).parent, + ), + filtered, + ); + }, [routes, visibilities]); +} diff --git a/packages/react/lib/use-router.ts b/packages/react/lib/use-router.ts new file mode 100644 index 0000000..860e201 --- /dev/null +++ b/packages/react/lib/use-router.ts @@ -0,0 +1,24 @@ +import { useContext } from 'react'; +import { RouterProviderContext } from './context'; +import { useUnit } from 'effector-react'; + +export function useRouterContext() { + const context = useContext(RouterProviderContext); + + if (!context) { + throw new Error( + '[useRouter] Router not found. Add RouterProvider in app root', + ); + } + + return context; +} + +/** + * @description Use router from provider + * @returns Router + * @link https://router.effector.dev/react/use-router.html + */ +export function useRouter() { + return useUnit(useRouterContext()); +} diff --git a/packages/react/lib/with-layout.tsx b/packages/react/lib/with-layout.tsx new file mode 100644 index 0000000..d73a99e --- /dev/null +++ b/packages/react/lib/with-layout.tsx @@ -0,0 +1,41 @@ +import { createElement, type ComponentType, type ReactNode } from 'react'; +import type { RouteView } from './types'; + +/** + * @description Group routes by layout, so you don't need to pass `layout` property manually in all routes. Works for `createRouteView` and `createLazyRouteView`. + * @link https://router.effector.dev/react/with-layout.html + * @example ```tsx + * import { + * createRoutesView, + * createRouteView, + * withLayout, + * } from '@effector/router-react'; + * + * import { ProfileScreen } from './profile'; + * import { SignInScreen } from './sign-in'; + * import { SignUpScreen } from './sign-up'; + * + * import { routes } from '@shared/routing'; + * + * import { AuthLayout } from '@layouts/auth'; + * + * export const RoutesView = createRoutesView([ + * ...withLayout(AuthLayout, [ + * createRouteView({ route: routes.signIn, view: SignInScreen }), + * createRouteView({ route: routes.signUp, view: SignUpScreen }), + * ]), + * createRouteView({ route: routes.profile, view: ProfileScreen }), + * ]); + * ``` + */ +export function withLayout( + layout: ComponentType<{ children: ReactNode }>, + views: RouteView[], +) { + const Layout = layout; + + return views.map(({ route, view }) => ({ + route, + view: () => {createElement(view)}, + })); +} diff --git a/packages/react/package.json b/packages/react/package.json new file mode 100644 index 0000000..cc78bb0 --- /dev/null +++ b/packages/react/package.json @@ -0,0 +1,62 @@ +{ + "name": "@effector/router-react", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "private": false, + "version": "1.0.0", + "description": "React bindings for effector router", + "keywords": [ + "effector", + "argon", + "atomic", + "router", + "effector/router", + "react" + ], + "author": "movpushmov", + "contributors": [ + "Sergey Sova ", + "Anton Kosykh", + "Zero Bias " + ], + "homepage": "https://router.effector.dev/react", + "license": "MIT", + "type": "module", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "directories": { + "lib": "lib" + }, + "files": [ + "dist" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/effector/router.git", + "directory": "packages/react" + }, + "scripts": { + "build": "vite build", + "test": "vitest run" + }, + "bugs": { + "url": "https://github.com/effector/router/issues" + }, + "dependencies": { + "@effector/router": "workspace:*" + }, + "peerDependencies": { + "effector": ">=23", + "effector-react": ">=23", + "react": "18 || 19" + }, + "gitHead": "9832d6cd282d6abd33c26ba4d03b9a1176110fc4" +} diff --git a/packages/react/tests/complex-example.test.tsx b/packages/react/tests/complex-example.test.tsx new file mode 100644 index 0000000..a3e3ce7 --- /dev/null +++ b/packages/react/tests/complex-example.test.tsx @@ -0,0 +1,253 @@ +import { allSettled, fork } from 'effector'; +import { Provider } from 'effector-react'; +import { createRoutesView, Outlet, RouterProvider } from '../lib'; +import { describe, expect, test } from 'vitest'; +import { createRoute, createRouter, historyAdapter } from '@effector/router'; +import { createMemoryHistory } from 'history'; +import { act, render } from '@testing-library/react'; + +describe('Complex Example: E-commerce App with Nested Routers and Outlets', () => { + /** + * This example demonstrates a realistic e-commerce application structure: + * + * Main Router: + * - / (Home) + * - /auth/* (Auth Router - Login/Register) + * - /shop/* (Shop Router - Products/Categories) + * - /account/* (Account Routes with nested children using Outlet) + */ + test('complete e-commerce app structure', async () => { + const scope = fork(); + + const authRoutes = { + login: createRoute({ path: '/login' }), + register: createRoute({ path: '/register' }), + }; + + const authRouter = createRouter({ + routes: [authRoutes.login, authRoutes.register], + }); + + const shopRoutes = { + products: createRoute({ path: '/products' }), + categories: createRoute({ path: '/categories' }), + }; + + const shopRouter = createRouter({ + routes: [shopRoutes.products, shopRoutes.categories], + }); + + const accountRoutes = { + root: createRoute({ path: '/account' }), + profile: createRoute({ path: '/profile', parent: undefined }), + orders: createRoute({ path: '/orders', parent: undefined }), + }; + + accountRoutes.profile = createRoute({ + path: '/profile', + parent: accountRoutes.root, + }); + accountRoutes.orders = createRoute({ + path: '/orders', + parent: accountRoutes.root, + }); + + const mainRoutes = { + home: createRoute({ path: '/' }), + }; + + const mainRouter = createRouter({ + routes: [ + mainRoutes.home, + authRouter, + shopRouter, + accountRoutes.root, + accountRoutes.profile, + accountRoutes.orders, + ], + }); + + const history = createMemoryHistory(); + history.push('/'); + + await allSettled(mainRouter.setHistory, { + scope, + params: historyAdapter(history), + }); + + const AuthRoutesView = createRoutesView({ + routes: [ + { + route: authRoutes.login, + view: () => ( +
+

Login

+
+ ), + }, + { + route: authRoutes.register, + view: () => ( +
+

Register

+
+ ), + }, + ], + }); + + const ShopRoutesView = createRoutesView({ + routes: [ + { + route: shopRoutes.products, + view: () => ( +
+

Products List

+
+ ), + }, + { + route: shopRoutes.categories, + view: () => ( +
+

Categories

+
+ ), + }, + ], + }); + + const AccountLayout = () => ( +
+
+

My Account

+
+
+ +
+
+ ); + + const MainRoutesView = createRoutesView({ + routes: [ + { + route: mainRoutes.home, + view: () => ( +
+

Home

+
+ ), + }, + { + route: authRouter, + view: () => ( +
+

Authentication

+ +
+ ), + }, + { + route: shopRouter, + view: () => ( +
+

Shop

+ +
+ ), + }, + { + route: accountRoutes.root, + view: AccountLayout, + children: [ + { + route: accountRoutes.profile, + view: () => ( +
+

Profile Settings

+

Edit your profile information here

+
+ ), + }, + { + route: accountRoutes.orders, + view: () => ( +
+

Order History

+

View your past orders

+
+ ), + }, + ], + }, + ], + }); + + const { getByTestId } = render( + + + + + , + ); + + expect(getByTestId('page-title').textContent).toBe('Home'); + + await act(() => + allSettled(authRoutes.login.open, { scope, params: undefined }), + ); + + expect(getByTestId('page-title').textContent).toBe('Authentication'); + expect(getByTestId('auth-title').textContent).toBe('Login'); + + await act(() => + allSettled(authRoutes.register.open, { scope, params: undefined }), + ); + + expect(getByTestId('auth-title').textContent).toBe('Register'); + + await act(() => + allSettled(shopRoutes.products.open, { scope, params: undefined }), + ); + + expect(getByTestId('page-title').textContent).toBe('Shop'); + expect(getByTestId('shop-content').textContent).toBe('Products List'); + + await act(() => + allSettled(shopRoutes.categories.open, { scope, params: undefined }), + ); + + expect(getByTestId('shop-content').textContent).toBe('Categories'); + + await act(() => + allSettled(accountRoutes.profile.open, { scope, params: undefined }), + ); + + expect(getByTestId('account-header')).toBeTruthy(); + expect(getByTestId('account-header').textContent).toContain('My Account'); + expect(getByTestId('account-page').textContent).toContain( + 'Profile Settings', + ); + + await act(() => + allSettled(accountRoutes.orders.open, { scope, params: undefined }), + ); + + expect(getByTestId('account-page').textContent).toContain('Order History'); + + await act(() => + allSettled(accountRoutes.profile.open, { scope, params: undefined }), + ); + + expect(getByTestId('account-page').textContent).toContain( + 'Profile Settings', + ); + + await act(() => + allSettled(accountRoutes.root.open, { scope, params: undefined }), + ); + + expect(getByTestId('account-header')).toBeTruthy(); + expect(getByTestId('account-content').children.length).toBe(0); + }); +}); diff --git a/packages/react/tests/index.test.tsx b/packages/react/tests/index.test.tsx new file mode 100644 index 0000000..f008ff0 --- /dev/null +++ b/packages/react/tests/index.test.tsx @@ -0,0 +1,348 @@ +import { allSettled, createEvent, createStore, fork, sample } from 'effector'; +import { Provider } from 'effector-react'; +import { + createRoutesView, + createRouteView, + Link, + RouterProvider, + withLayout, +} from '../lib'; +import { act, ReactNode } from 'react'; +import { describe, expect, test } from 'vitest'; +import { + chainRoute, + createRoute, + createRouter, + historyAdapter, +} from '@effector/router'; +import { createMemoryHistory } from 'history'; +import { render } from '@testing-library/react'; +import { userEvent } from '@testing-library/user-event'; + +describe('react bindings', () => { + test('component changed when path changed', async () => { + const route1 = createRoute({ path: '/app' }); + const route2 = createRoute({ path: '/faq' }); + + const scope = fork(); + const router = createRouter({ routes: [route1, route2] }); + + const history = createMemoryHistory(); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + const RoutesView = createRoutesView({ + routes: [ + { route: route1, view: () =>

route1

}, + { route: route2, view: () =>

route2

}, + ], + otherwise: () =>

not found

, + }); + + const { container } = render( + + + + + , + ); + + await act(() => allSettled(route1.open, { scope, params: undefined })); + + expect(container.querySelector('#message')?.textContent).toBe('route1'); + + await act(() => allSettled(route2.open, { scope, params: undefined })); + + expect(container.querySelector('#message')?.textContent).toBe('route2'); + + act(() => history.push('/not-found')); + await act(() => allSettled(scope)); + + expect(container.querySelector('#message')?.textContent).toBe('not found'); + }); + + test('link', async () => { + const route1 = createRoute({ path: '/app' }); + const route2 = createRoute({ path: '/faq/:id' }); + + const scope = fork(); + const router = createRouter({ routes: [route1, route2] }); + + const history = createMemoryHistory(); + + history.push('/app'); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + const RoutesView = createRoutesView({ + routes: [ + { + route: route1, + view: () => ( + + route1 + + ), + }, + { + route: route2, + view: () => ( + + route2 + + ), + }, + ], + otherwise: () =>

not found

, + }); + + const { container } = render( + + + + + , + ); + + await userEvent.click(container.querySelector('#link')!); + + await act(() => allSettled(scope)); + + expect(scope.getState(route2.$isOpened)).toBeTruthy(); + expect(scope.getState(route2.$params)).toStrictEqual({ id: '123' }); + + await userEvent.click(container.querySelector('#link')!); + + await act(() => allSettled(scope)); + + expect(scope.getState(route1.$isOpened)).toBeTruthy(); + }); + + test('chained route', async () => { + interface User { + id: number; + name: string; + } + + const authRoute = createRoute({ path: '/auth' }); + const profileRoute = createRoute({ path: '/profile' }); + + const $user = createStore({ id: 1, name: 'edward' }); + + const authorizationCheckStarted = createEvent('check started'); + + const authorized = createEvent('authorized'); + const rejected = createEvent('rejected'); + + sample({ + clock: authorizationCheckStarted, + source: $user, + filter: Boolean, + target: authorized, + }); + + sample({ + clock: authorizationCheckStarted, + source: $user, + filter: (user) => !user, + target: rejected, + }); + + const chainedRoute = chainRoute({ + route: authRoute, + beforeOpen: authorizationCheckStarted, + openOn: rejected, + cancelOn: authorized, + }); + + sample({ + clock: chainedRoute.cancelled, + target: profileRoute.open, + }); + + const scope = fork(); + const router = createRouter({ routes: [authRoute, profileRoute] }); + + const history = createMemoryHistory(); + + history.push('/app'); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + const RoutesView = createRoutesView({ + routes: [ + { + route: chainedRoute, + view: () =>

auth

, + }, + { + route: profileRoute, + view: () =>

profile

, + }, + ], + otherwise: () =>

not found

, + }); + + const { getByTestId } = render( + + + + + , + ); + + await act(() => allSettled(authRoute.open, { scope, params: undefined })); + + expect(getByTestId('message').textContent).toBe('profile'); + + await act(async () => { + await allSettled($user, { scope, params: null }); + await allSettled(authRoute.open, { scope, params: undefined }); + }); + + expect(getByTestId('message').textContent).toBe('auth'); + }); + + test('nested routes', async () => { + const profileRoute = createRoute({ path: '/profile' }); + const friendsRoute = createRoute({ + path: '/friends', + parent: profileRoute, + }); + + const scope = fork(); + const router = createRouter({ routes: [friendsRoute, profileRoute] }); + + const history = createMemoryHistory(); + + history.push('/app'); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + const RoutesView = createRoutesView({ + routes: [ + { + route: friendsRoute, + view: () =>

friends

, + }, + { + route: profileRoute, + view: () =>

profile

, + }, + ], + otherwise: () =>

not found

, + }); + + const { getByTestId } = render( + + + + + , + ); + + await act(() => + allSettled(friendsRoute.open, { scope, params: undefined }), + ); + + expect(getByTestId('message').textContent).toBe('friends'); + + await act(() => + allSettled(profileRoute.open, { scope, params: undefined }), + ); + + expect(getByTestId('message').textContent).toBe('profile'); + }); + + test('with layout', async () => { + const profileRoute = createRoute({ path: '/profile' }); + const friendsRoute = createRoute({ + path: '/friends', + parent: profileRoute, + }); + + const authRoute = createRoute({ path: '/auth' }); + + const scope = fork(); + const router = createRouter({ + routes: [friendsRoute, profileRoute, authRoute], + }); + + const history = createMemoryHistory(); + + history.push('/auth'); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + const ProfileLayout = (props: { children: ReactNode }) => { + return ( + <> +

layout!

+ {props.children} + + ); + }; + + const RoutesView = createRoutesView({ + routes: [ + ...withLayout(ProfileLayout, [ + createRouteView({ + route: friendsRoute, + view: () =>

friends

, + }), + createRouteView({ + route: profileRoute, + view: () =>

profile

, + }), + ]), + createRouteView({ + route: authRoute, + view: () =>

auth

, + }), + ], + otherwise: () =>

not found

, + }); + + const { getByTestId, queryByTestId } = render( + + + + + , + ); + + await act(() => + allSettled(friendsRoute.open, { scope, params: undefined }), + ); + + expect(getByTestId('layout').textContent).toBe('layout!'); + expect(getByTestId('message').textContent).toBe('friends'); + + await act(() => + allSettled(profileRoute.open, { scope, params: undefined }), + ); + + expect(getByTestId('layout').textContent).toBe('layout!'); + expect(getByTestId('message').textContent).toBe('profile'); + + await act(() => allSettled(authRoute.open, { scope, params: undefined })); + + expect(queryByTestId('layout')).toBeFalsy(); + expect(getByTestId('message').textContent).toBe('auth'); + }); +}); diff --git a/packages/react/tests/nested-routers.test.tsx b/packages/react/tests/nested-routers.test.tsx new file mode 100644 index 0000000..cc6fd01 --- /dev/null +++ b/packages/react/tests/nested-routers.test.tsx @@ -0,0 +1,473 @@ +import { allSettled, fork } from 'effector'; +import { Provider } from 'effector-react'; +import { createRoutesView, RouterProvider } from '../lib'; +import { describe, expect, test } from 'vitest'; +import { createRoute, createRouter, historyAdapter } from '@effector/router'; +import { createMemoryHistory } from 'history'; +import { act, render } from '@testing-library/react'; + +describe('Nested Routers', () => { + test('basic nested router functionality', async () => { + const scope = fork(); + + const shopRoutes = { + products: createRoute({ path: '/products' }), + cart: createRoute({ path: '/cart' }), + }; + + const shopRouter = createRouter({ + routes: [shopRoutes.products, shopRoutes.cart], + }); + + const mainRoutes = { + home: createRoute({ path: '/' }), + settings: createRoute({ path: '/settings' }), + }; + + const mainRouter = createRouter({ + routes: [mainRoutes.home, mainRoutes.settings, shopRouter], + }); + + const history = createMemoryHistory(); + history.push('/'); + + await allSettled(mainRouter.setHistory, { + scope, + params: historyAdapter(history), + }); + + const ShopRoutesView = createRoutesView({ + routes: [ + { + route: shopRoutes.products, + view: () =>

products

, + }, + { + route: shopRoutes.cart, + view: () =>

cart

, + }, + ], + }); + + const MainRoutesView = createRoutesView({ + routes: [ + { + route: mainRoutes.home, + view: () =>

home

, + }, + { + route: mainRoutes.settings, + view: () =>

settings

, + }, + { + route: shopRouter, + view: ShopRoutesView, + }, + ], + }); + + const { getByTestId } = render( + + + + + , + ); + + expect(getByTestId('message').textContent).toBe('home'); + + await act(() => + allSettled(shopRoutes.products.open, { scope, params: undefined }), + ); + + expect(getByTestId('message').textContent).toBe('products'); + expect(scope.getState(shopRoutes.products.$isOpened)).toBeTruthy(); + + await act(() => + allSettled(shopRoutes.cart.open, { scope, params: undefined }), + ); + + expect(getByTestId('message').textContent).toBe('cart'); + expect(scope.getState(shopRoutes.cart.$isOpened)).toBeTruthy(); + + await act(() => + allSettled(mainRoutes.settings.open, { scope, params: undefined }), + ); + + expect(getByTestId('message').textContent).toBe('settings'); + expect(scope.getState(mainRoutes.settings.$isOpened)).toBeTruthy(); + }); + + test('multiple nested routers at same level', async () => { + const scope = fork(); + + const shopRoutes = { + products: createRoute({ path: '/products' }), + orders: createRoute({ path: '/orders' }), + }; + + const shopRouter = createRouter({ + routes: [shopRoutes.products, shopRoutes.orders], + }); + + const blogRoutes = { + posts: createRoute({ path: '/posts' }), + authors: createRoute({ path: '/authors' }), + }; + + const blogRouter = createRouter({ + routes: [blogRoutes.posts, blogRoutes.authors], + }); + + const mainRoutes = { + home: createRoute({ path: '/' }), + }; + + const mainRouter = createRouter({ + routes: [mainRoutes.home, shopRouter, blogRouter], + }); + + const history = createMemoryHistory(); + history.push('/'); + + await allSettled(mainRouter.setHistory, { + scope, + params: historyAdapter(history), + }); + + const ShopRoutesView = createRoutesView({ + routes: [ + { + route: shopRoutes.products, + view: () =>

Shop - Products

, + }, + { + route: shopRoutes.orders, + view: () =>

Shop - Orders

, + }, + ], + }); + + const BlogRoutesView = createRoutesView({ + routes: [ + { + route: blogRoutes.posts, + view: () =>

Blog - Posts

, + }, + { + route: blogRoutes.authors, + view: () =>

Blog - Authors

, + }, + ], + }); + + const MainRoutesView = createRoutesView({ + routes: [ + { + route: mainRoutes.home, + view: () =>

Home

, + }, + { + route: shopRouter, + view: ShopRoutesView, + }, + { + route: blogRouter, + view: BlogRoutesView, + }, + ], + }); + + const { getByTestId } = render( + + + + + , + ); + + expect(getByTestId('message').textContent).toBe('Home'); + + await act(() => + allSettled(shopRoutes.products.open, { scope, params: undefined }), + ); + + expect(getByTestId('message').textContent).toBe('Shop - Products'); + + await act(() => + allSettled(blogRoutes.posts.open, { scope, params: undefined }), + ); + + expect(getByTestId('message').textContent).toBe('Blog - Posts'); + + await act(() => + allSettled(shopRoutes.orders.open, { scope, params: undefined }), + ); + + expect(getByTestId('message').textContent).toBe('Shop - Orders'); + + await act(() => + allSettled(blogRoutes.authors.open, { scope, params: undefined }), + ); + + expect(getByTestId('message').textContent).toBe('Blog - Authors'); + }); + + test('nested router state isolation', async () => { + const scope = fork(); + + const moduleARoute = createRoute({ path: '/module-a' }); + const moduleBRoute = createRoute({ path: '/module-b' }); + + const mainRouter = createRouter({ + routes: [moduleARoute, moduleBRoute], + }); + + const history = createMemoryHistory(); + history.push('/'); + + await allSettled(mainRouter.setHistory, { + scope, + params: historyAdapter(history), + }); + + const MainRoutesView = createRoutesView({ + routes: [ + { + route: moduleARoute, + view: () =>

Module A

, + }, + { + route: moduleBRoute, + view: () =>

Module B

, + }, + ], + }); + + const { getByTestId, queryByTestId } = render( + + + + + , + ); + + expect(queryByTestId('message')).toBeFalsy(); + + await act(() => + allSettled(moduleARoute.open, { scope, params: undefined }), + ); + + expect(getByTestId('message').textContent).toBe('Module A'); + + expect(scope.getState(moduleARoute.$isOpened)).toBeTruthy(); + expect(scope.getState(moduleBRoute.$isOpened)).toBeFalsy(); + + await act(() => + allSettled(moduleBRoute.open, { scope, params: undefined }), + ); + + expect(getByTestId('message').textContent).toBe('Module B'); + + expect(scope.getState(moduleARoute.$isOpened)).toBeFalsy(); + expect(scope.getState(moduleBRoute.$isOpened)).toBeTruthy(); + }); + + test('nested router with route parameters', async () => { + const scope = fork(); + + const projectRoutes = { + details: createRoute({ path: '/details' }), + tasks: createRoute({ path: '/tasks/:taskId' }), + }; + + const projectRouter = createRouter({ + routes: [projectRoutes.details, projectRoutes.tasks], + }); + + const mainRoutes = { + workspace: createRoute({ path: '/workspace/:workspaceId' }), + }; + + const mainRouter = createRouter({ + routes: [mainRoutes.workspace, projectRouter], + }); + + const history = createMemoryHistory(); + history.push('/workspace/ws-123'); + + await allSettled(mainRouter.setHistory, { + scope, + params: historyAdapter(history), + }); + + const ProjectRoutesView = createRoutesView({ + routes: [ + { + route: projectRoutes.details, + view: () =>

Project Details

, + }, + { + route: projectRoutes.tasks, + view: () =>

Task View

, + }, + ], + }); + + const MainRoutesView = createRoutesView({ + routes: [ + { + route: mainRoutes.workspace, + view: () =>

Workspace

, + }, + { + route: projectRouter, + view: ProjectRoutesView, + }, + ], + }); + + const { getByTestId } = render( + + + + + , + ); + + expect(getByTestId('message').textContent).toBe('Workspace'); + expect(scope.getState(mainRoutes.workspace.$params)).toEqual({ + workspaceId: 'ws-123', + }); + + await act(() => + allSettled(projectRoutes.tasks.open, { + scope, + params: { params: { taskId: 'task-456' } }, + }), + ); + + expect(getByTestId('message').textContent).toBe('Task View'); + + expect(scope.getState(projectRoutes.tasks.$params)).toEqual({ + taskId: 'task-456', + }); + + await act(() => + allSettled(projectRoutes.details.open, { + scope, + params: undefined, + }), + ); + + expect(getByTestId('message').textContent).toBe('Project Details'); + }); + + test('nested router isolated state management', async () => { + const scope = fork(); + + const moduleARoutes = { + page1: createRoute({ path: '/module-a/page1' }), + page2: createRoute({ path: '/module-a/page2' }), + }; + + const moduleARouter = createRouter({ + routes: [moduleARoutes.page1, moduleARoutes.page2], + }); + + const moduleBRoutes = { + page1: createRoute({ path: '/module-b/page1' }), + page2: createRoute({ path: '/module-b/page2' }), + }; + + const moduleBRouter = createRouter({ + routes: [moduleBRoutes.page1, moduleBRoutes.page2], + }); + + const mainRouter = createRouter({ + routes: [moduleARouter, moduleBRouter], + }); + + const history = createMemoryHistory(); + history.push('/'); + + await allSettled(mainRouter.setHistory, { + scope, + params: historyAdapter(history), + }); + + const ModuleARoutesView = createRoutesView({ + routes: [ + { + route: moduleARoutes.page1, + view: () =>

Module A - Page 1

, + }, + { + route: moduleARoutes.page2, + view: () =>

Module A - Page 2

, + }, + ], + }); + + const ModuleBRoutesView = createRoutesView({ + routes: [ + { + route: moduleBRoutes.page1, + view: () =>

Module B - Page 1

, + }, + { + route: moduleBRoutes.page2, + view: () =>

Module B - Page 2

, + }, + ], + }); + + const MainRoutesView = createRoutesView({ + routes: [ + { + route: moduleARouter, + view: ModuleARoutesView, + }, + { + route: moduleBRouter, + view: ModuleBRoutesView, + }, + ], + }); + + const { getByTestId } = render( + + + + + , + ); + + await act(() => + allSettled(moduleARoutes.page1.open, { scope, params: undefined }), + ); + + expect(getByTestId('message').textContent).toBe('Module A - Page 1'); + + expect(scope.getState(moduleARoutes.page1.$isOpened)).toBeTruthy(); + expect(scope.getState(moduleBRoutes.page1.$isOpened)).toBeFalsy(); + + await act(() => + allSettled(moduleBRoutes.page1.open, { scope, params: undefined }), + ); + + expect(getByTestId('message').textContent).toBe('Module B - Page 1'); + + expect(scope.getState(moduleARoutes.page1.$isOpened)).toBeFalsy(); + expect(scope.getState(moduleBRoutes.page1.$isOpened)).toBeTruthy(); + + await act(() => + allSettled(moduleARoutes.page2.open, { scope, params: undefined }), + ); + + expect(getByTestId('message').textContent).toBe('Module A - Page 2'); + + expect(scope.getState(moduleARoutes.page2.$isOpened)).toBeTruthy(); + expect(scope.getState(moduleBRoutes.page1.$isOpened)).toBeFalsy(); + }); +}); diff --git a/packages/react/tests/outlet.test.tsx b/packages/react/tests/outlet.test.tsx new file mode 100644 index 0000000..0b509d1 --- /dev/null +++ b/packages/react/tests/outlet.test.tsx @@ -0,0 +1,458 @@ +import { allSettled, fork } from 'effector'; +import { Provider } from 'effector-react'; +import { + createRoutesView, + createRouteView, + Outlet, + RouterProvider, +} from '../lib'; +import { describe, expect, test } from 'vitest'; +import { createRoute, createRouter, historyAdapter } from '@effector/router'; +import { createBrowserHistory, createMemoryHistory } from 'history'; +import { act, render } from '@testing-library/react'; + +describe('Outlet Component', () => { + test('renders child route in outlet', async () => { + const profileRoute = createRoute({ path: '/profile' }); + const settingsRoute = createRoute({ + path: '/settings', + parent: profileRoute, + }); + + const scope = fork(); + const router = createRouter({ routes: [profileRoute, settingsRoute] }); + + const history = createMemoryHistory(); + history.push('/profile'); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + const ProfileView = () => ( +
+

Profile

+ +
+ ); + + const SettingsView = () =>

Settings

; + + const RoutesView = createRoutesView({ + routes: [ + { + route: profileRoute, + view: ProfileView, + children: [ + { + route: settingsRoute, + view: SettingsView, + }, + ], + }, + ], + }); + + const { getByTestId, queryByTestId } = render( + + + + + , + ); + + expect(getByTestId('profile').textContent).toBe('Profile'); + expect(queryByTestId('settings')).toBeFalsy(); + + await act(() => + allSettled(settingsRoute.open, { scope, params: undefined }), + ); + + expect(getByTestId('settings')).toBeTruthy(); + + expect(getByTestId('profile').textContent).toBe('Profile'); + expect(getByTestId('settings').textContent).toBe('Settings'); + }); + + test('outlet renders nothing when no child route is active', async () => { + const profileRoute = createRoute({ path: '/profile' }); + const settingsRoute = createRoute({ + path: '/settings', + parent: profileRoute, + }); + + const scope = fork(); + const router = createRouter({ routes: [profileRoute, settingsRoute] }); + + const history = createMemoryHistory(); + history.push('/profile'); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + const ProfileView = () => ( +
+

Profile

+
+ +
+
+ ); + + const SettingsView = () =>

Settings

; + + const RoutesView = createRoutesView({ + routes: [ + { + route: profileRoute, + view: ProfileView, + children: [ + { + route: settingsRoute, + view: SettingsView, + }, + ], + }, + ], + }); + + const { getByTestId, queryByTestId } = render( + + + + + , + ); + + expect(getByTestId('profile').textContent).toBe('Profile'); + expect(getByTestId('outlet-container').children.length).toBe(0); + expect(queryByTestId('settings')).toBeFalsy(); + }); + + test('outlet switches between sibling routes', async () => { + const profileRoute = createRoute({ path: '/profile' }); + const settingsRoute = createRoute({ + path: '/settings', + parent: profileRoute, + }); + const notificationsRoute = createRoute({ + path: '/notifications', + parent: profileRoute, + }); + + const scope = fork(); + const router = createRouter({ + routes: [profileRoute, settingsRoute, notificationsRoute], + }); + + const history = createMemoryHistory(); + history.push('/profile'); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + const ProfileView = () => ( +
+

Profile

+ +
+ ); + + const SettingsView = () =>

Settings

; + const NotificationsView = () => ( +

Notifications

+ ); + + const RoutesView = createRoutesView({ + routes: [ + { + route: profileRoute, + view: ProfileView, + children: [ + { + route: settingsRoute, + view: SettingsView, + }, + { + route: notificationsRoute, + view: NotificationsView, + }, + ], + }, + ], + }); + + const { getByTestId, queryByTestId } = render( + + + + + , + ); + + await act(() => + allSettled(settingsRoute.open, { scope, params: undefined }), + ); + + expect(getByTestId('settings')).toBeTruthy(); + expect(getByTestId('settings').textContent).toBe('Settings'); + expect(queryByTestId('notifications')).toBeFalsy(); + + await act(() => + allSettled(notificationsRoute.open, { scope, params: undefined }), + ); + + expect(getByTestId('notifications')).toBeTruthy(); + expect(getByTestId('notifications').textContent).toBe('Notifications'); + expect(queryByTestId('settings')).toBeFalsy(); + + await act(() => + allSettled(profileRoute.open, { scope, params: undefined }), + ); + + expect(queryByTestId('notifications')).toBeFalsy(); + expect(getByTestId('profile').textContent).toBe('Profile'); + expect(queryByTestId('settings')).toBeFalsy(); + expect(queryByTestId('notifications')).toBeFalsy(); + }); + + test('outlet with simple nested routes', async () => { + const dashboardRoute = createRoute({ path: '/dashboard' }); + const settingsRoute = createRoute({ + path: '/settings', + parent: dashboardRoute, + }); + + const scope = fork(); + const router = createRouter({ routes: [dashboardRoute, settingsRoute] }); + + const history = createMemoryHistory(); + history.push('/dashboard'); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + const DashboardView = () => ( +
+

Dashboard

+ +
+ ); + + const SettingsView = () =>

Settings Content

; + + const RoutesView = createRoutesView({ + routes: [ + { + route: dashboardRoute, + view: DashboardView, + children: [ + { + route: settingsRoute, + view: SettingsView, + }, + ], + }, + ], + }); + + const { getByTestId, queryByTestId } = render( + + + + + , + ); + + expect(getByTestId('dashboard').textContent).toBe('Dashboard'); + expect(queryByTestId('settings')).toBeFalsy(); + + await act(() => + allSettled(settingsRoute.open, { scope, params: undefined }), + ); + + expect(getByTestId('settings')).toBeTruthy(); + + expect(getByTestId('dashboard').textContent).toBe('Dashboard'); + expect(getByTestId('settings').textContent).toBe('Settings Content'); + }); + + test('outlet with nested router', async () => { + const scope = fork(); + + const rootRoutes = { + profile: createRoute({ path: '/profile' }), + }; + + const profileRoutes = { + friends: createRoute({ path: '/friends', parent: rootRoutes.profile }), + settings: createRoute({ path: '/settings', parent: rootRoutes.profile }), + }; + + const profileRouter = createRouter({ + routes: [profileRoutes.friends, profileRoutes.settings], + }); + + const router = createRouter({ + routes: [rootRoutes.profile, profileRouter], + }); + + const history = createMemoryHistory(); + history.push('/profile'); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + const ProfileView = () => ( +
+

Profile

+ +
+ ); + + const FriendsView = () =>

Friends

; + const SettingsView = () =>

Settings

; + + const ProfileRoutesView = createRoutesView({ + routes: [ + { + route: profileRoutes.friends, + view: FriendsView, + }, + { + route: profileRoutes.settings, + view: SettingsView, + }, + ], + }); + + const RoutesView = createRoutesView({ + routes: [ + { + route: rootRoutes.profile, + view: ProfileView, + children: [ + { + route: profileRouter, + view: ProfileRoutesView, + }, + ], + }, + ], + }); + + const { getByTestId, queryByTestId } = render( + + + + + , + ); + + expect(getByTestId('profile').textContent).toBe('Profile'); + expect(queryByTestId('friends')).toBeFalsy(); + expect(queryByTestId('settings')).toBeFalsy(); + + await act(() => + allSettled(profileRoutes.friends.open, { scope, params: undefined }), + ); + + expect(getByTestId('friends')).toBeTruthy(); + + expect(getByTestId('profile').textContent).toBe('Profile'); + expect(getByTestId('friends').textContent).toBe('Friends'); + expect(queryByTestId('settings')).toBeFalsy(); + + await act(() => + allSettled(profileRoutes.settings.open, { scope, params: undefined }), + ); + + expect(getByTestId('settings')).toBeTruthy(); + + expect(getByTestId('profile').textContent).toBe('Profile'); + expect(getByTestId('settings').textContent).toBe('Settings'); + expect(queryByTestId('friends')).toBeFalsy(); + + await act(() => + allSettled(rootRoutes.profile.open, { scope, params: undefined }), + ); + + expect(queryByTestId('friends')).toBeFalsy(); + expect(getByTestId('profile').textContent).toBe('Profile'); + expect(queryByTestId('friends')).toBeFalsy(); + expect(queryByTestId('settings')).toBeFalsy(); + }); + + test('outled with nested routes created via createRouteView', async () => { + const profileRoute = createRoute({ path: '/profile' }); + const settingsRoute = createRoute({ + path: '/settings', + parent: profileRoute, + }); + + const scope = fork(); + const router = createRouter({ routes: [profileRoute, settingsRoute] }); + + const history = createBrowserHistory(); + history.push('/profile/settings'); + + await allSettled(router.setHistory, { + scope, + params: historyAdapter(history), + }); + + const RoutesView = createRoutesView({ + routes: [ + createRouteView({ + route: profileRoute, + view: () => ( +
+

Profile

+ +
+ ), + children: [ + createRouteView({ + route: settingsRoute, + view: () =>

Settings

, + }), + ], + }), + ], + }); + + const { container } = render( + + + + + , + ); + + expect(container).toMatchInlineSnapshot(` +
+
+

+ Profile +

+

+ Settings +

+
+
+ `); + }); +}); diff --git a/packages/react/vite.config.ts b/packages/react/vite.config.ts new file mode 100644 index 0000000..cd6ba1c --- /dev/null +++ b/packages/react/vite.config.ts @@ -0,0 +1,53 @@ +import { defineConfig } from 'vite'; +import { resolve } from 'path'; +import dts from 'vite-plugin-dts'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + mode: 'production', + build: { + lib: { + entry: resolve(__dirname, 'lib/index.ts'), + fileName: 'index', + formats: ['es', 'cjs'], + }, + rollupOptions: { + external: [ + 'effector', + 'effector-react', + '@effector/router', + 'react', + 'react/jsx-runtime', + ], + output: { + globals: { + react: 'react', + effector: 'effector', + 'effector-react': 'effector-react', + '@effector/router': '@effector/router', + 'react/jsx-runtime': 'react/jsx-runtime', + }, + }, + }, + }, + plugins: [ + react(), + dts({ + outDir: resolve(__dirname, 'dist'), + entryRoot: resolve(__dirname, 'lib'), + exclude: [ + resolve(__dirname, 'tests'), + resolve(__dirname, '../effector/router-core'), + resolve(__dirname, '../effector/router-paths'), + resolve(__dirname, '../effector/router-react-native'), + ], + staticImport: true, + insertTypesEntry: true, + rollupTypes: true, + }), + ], + test: { + globals: true, + environment: 'happy-dom', + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..63b9cbb --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,12829 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + .: + devDependencies: + '@babel/core': + specifier: 7.29.0 + version: 7.29.0 + '@babel/preset-env': + specifier: 7.29.0 + version: 7.29.0(@babel/core@7.29.0) + '@babel/preset-react': + specifier: 7.28.5 + version: 7.28.5(@babel/core@7.29.0) + '@babel/preset-typescript': + specifier: 7.28.5 + version: 7.28.5(@babel/core@7.29.0) + '@changesets/cli': + specifier: ^2.30.0 + version: 2.30.0(@types/node@25.3.3) + '@eslint/eslintrc': + specifier: ^3.3.4 + version: 3.3.4 + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.0.2(jiti@2.6.1)) + '@react-navigation/bottom-tabs': + specifier: 7.15.5 + version: 7.15.5(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + '@react-navigation/native': + specifier: 7.1.33 + version: 7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + '@react-navigation/stack': + specifier: 7.8.4 + version: 7.8.4(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-gesture-handler@2.30.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + '@testing-library/dom': + specifier: ^10.4.1 + version: 10.4.1 + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@testing-library/user-event': + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) + '@types/react': + specifier: 19.2.14 + version: 19.2.14 + '@typescript-eslint/parser': + specifier: 8.56.1 + version: 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.8.2) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.7.0(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2)) + effector: + specifier: ^23.4.4 + version: 23.4.4 + effector-react: + specifier: ^23.3.0 + version: 23.3.0(effector@23.4.4)(react@19.2.4) + eslint: + specifier: 10.0.2 + version: 10.0.2(jiti@2.6.1) + eslint-config-prettier: + specifier: 10.1.8 + version: 10.1.8(eslint@10.0.2(jiti@2.6.1)) + eslint-plugin-prettier: + specifier: 5.5.5 + version: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.0.2(jiti@2.6.1)))(eslint@10.0.2(jiti@2.6.1))(prettier@3.8.1) + globals: + specifier: ^17.4.0 + version: 17.4.0 + happy-dom: + specifier: ^20.8.3 + version: 20.8.3 + history: + specifier: 5.3.0 + version: 5.3.0 + jiti: + specifier: ^2.6.1 + version: 2.6.1 + patronum: + specifier: ^2.3.0 + version: 2.3.0(effector@23.4.4) + prettier: + specifier: 3.8.1 + version: 3.8.1 + query-string: + specifier: ^9.3.1 + version: 9.3.1 + react: + specifier: 19.2.4 + version: 19.2.4 + react-dom: + specifier: 19.2.4 + version: 19.2.4(react@19.2.4) + react-native: + specifier: 0.84.1 + version: 0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4) + typescript-eslint: + specifier: ^8.56.1 + version: 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.8.2) + vite: + specifier: 7.3.1 + version: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2) + vite-plugin-babel: + specifier: 1.5.1 + version: 1.5.1(@babel/core@7.29.0)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2)) + vite-plugin-dts: + specifier: 4.5.4 + version: 4.5.4(@types/node@25.3.3)(rollup@4.59.0)(typescript@5.8.2)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2)) + vitepress: + specifier: 1.6.4 + version: 1.6.4(@algolia/client-search@5.49.1)(@types/node@25.3.3)(@types/react@19.2.14)(postcss@8.5.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(search-insights@2.17.3)(terser@5.46.0)(typescript@5.8.2) + vitest: + specifier: 4.0.18 + version: 4.0.18(@types/node@25.3.3)(happy-dom@20.8.3)(jiti@2.6.1)(jsdom@25.0.1)(terser@5.46.0)(yaml@2.8.2) + zod: + specifier: ^4.3.6 + version: 4.3.6 + + docs: + dependencies: + vue: + specifier: ^3.5.29 + version: 3.5.29(typescript@5.8.2) + devDependencies: + '@iconify-json/logos': + specifier: 1.2.10 + version: 1.2.10 + '@unocss/preset-icons': + specifier: 66.6.4 + version: 66.6.4 + unocss: + specifier: 66.6.4 + version: 66.6.4(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2)) + vite: + specifier: 7.3.1 + version: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2) + vitepress: + specifier: 1.6.4 + version: 1.6.4(@algolia/client-search@5.49.1)(@types/node@25.3.3)(@types/react@19.2.14)(postcss@8.5.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(search-insights@2.17.3)(terser@5.46.0)(typescript@5.8.2) + + packages/core: + dependencies: + '@effector/router-paths': + specifier: workspace:* + version: link:../paths + effector: + specifier: '>=23' + version: 23.4.4 + effector-action: + specifier: '>=1.2.2' + version: 1.2.2(effector@23.4.4)(patronum@2.3.0(effector@23.4.4)) + patronum: + specifier: '>=2.2' + version: 2.3.0(effector@23.4.4) + query-string: + specifier: ^9.1.1 + version: 9.3.1 + zod: + specifier: '>=4' + version: 4.3.6 + + packages/paths: {} + + packages/react: + dependencies: + '@effector/router': + specifier: workspace:* + version: link:../core + effector: + specifier: '>=23' + version: 23.4.4 + effector-react: + specifier: '>=23' + version: 23.3.0(effector@23.4.4)(react@19.2.4) + react: + specifier: 18 || 19 + version: 19.2.4 + + packages/react-native: + dependencies: + '@effector/router': + specifier: workspace:* + version: link:../core + '@effector/router-react': + specifier: workspace:* + version: link:../react + '@react-navigation/bottom-tabs': + specifier: '>=6' + version: 7.15.5(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + '@react-navigation/native': + specifier: '>=6' + version: 7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + '@react-navigation/stack': + specifier: '>=6' + version: 7.8.4(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-gesture-handler@2.30.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + effector: + specifier: '>=23' + version: 23.4.4 + effector-react: + specifier: '>=23' + version: 23.3.0(effector@23.4.4)(react@19.2.4) + react: + specifier: 18 || 19 + version: 19.2.4 + react-native: + specifier: '>=0.70' + version: 0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4) + +packages: + '@algolia/abtesting@1.15.1': + resolution: + { + integrity: sha512-2yuIC48rUuHGhU1U5qJ9kJHaxYpJ0jpDHJVI5ekOxSMYXlH4+HP+pA31G820lsAznfmu2nzDV7n5RO44zIY1zw==, + } + engines: { node: '>= 14.0.0' } + + '@algolia/autocomplete-core@1.17.7': + resolution: + { + integrity: sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==, + } + + '@algolia/autocomplete-plugin-algolia-insights@1.17.7': + resolution: + { + integrity: sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==, + } + peerDependencies: + search-insights: '>= 1 < 3' + + '@algolia/autocomplete-preset-algolia@1.17.7': + resolution: + { + integrity: sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==, + } + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/autocomplete-shared@1.17.7': + resolution: + { + integrity: sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==, + } + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/client-abtesting@5.49.1': + resolution: + { + integrity: sha512-h6M7HzPin+45/l09q0r2dYmocSSt2MMGOOk5c4O5K/bBBlEwf1BKfN6z+iX4b8WXcQQhf7rgQwC52kBZJt/ZZw==, + } + engines: { node: '>= 14.0.0' } + + '@algolia/client-analytics@5.49.1': + resolution: + { + integrity: sha512-048T9/Z8OeLmTk8h76QUqaNFp7Rq2VgS2Zm6Y2tNMYGQ1uNuzePY/udB5l5krlXll7ZGflyCjFvRiOtlPZpE9g==, + } + engines: { node: '>= 14.0.0' } + + '@algolia/client-common@5.49.1': + resolution: + { + integrity: sha512-vp5/a9ikqvf3mn9QvHN8PRekn8hW34aV9eX+O0J5mKPZXeA6Pd5OQEh2ZWf7gJY6yyfTlLp5LMFzQUAU+Fpqpg==, + } + engines: { node: '>= 14.0.0' } + + '@algolia/client-insights@5.49.1': + resolution: + { + integrity: sha512-B6N7PgkvYrul3bntTz/l6uXnhQ2bvP+M7NqTcayh681tSqPaA5cJCUBp/vrP7vpPRpej4Eeyx2qz5p0tE/2N2g==, + } + engines: { node: '>= 14.0.0' } + + '@algolia/client-personalization@5.49.1': + resolution: + { + integrity: sha512-v+4DN+lkYfBd01Hbnb9ZrCHe7l+mvihyx218INRX/kaCXROIWUDIT1cs3urQxfE7kXBFnLsqYeOflQALv/gA5w==, + } + engines: { node: '>= 14.0.0' } + + '@algolia/client-query-suggestions@5.49.1': + resolution: + { + integrity: sha512-Un11cab6ZCv0W+Jiak8UktGIqoa4+gSNgEZNfG8m8eTsXGqwIEr370H3Rqwj87zeNSlFpH2BslMXJ/cLNS1qtg==, + } + engines: { node: '>= 14.0.0' } + + '@algolia/client-search@5.49.1': + resolution: + { + integrity: sha512-Nt9hri7nbOo0RipAsGjIssHkpLMHHN/P7QqENywAq5TLsoYDzUyJGny8FEiD/9KJUxtGH8blGpMedilI6kK3rA==, + } + engines: { node: '>= 14.0.0' } + + '@algolia/ingestion@1.49.1': + resolution: + { + integrity: sha512-b5hUXwDqje0Y4CpU6VL481DXgPgxpTD5sYMnfQTHKgUispGnaCLCm2/T9WbJo1YNUbX3iHtYDArp804eD6CmRQ==, + } + engines: { node: '>= 14.0.0' } + + '@algolia/monitoring@1.49.1': + resolution: + { + integrity: sha512-bvrXwZ0WsL3rN6Q4m4QqxsXFCo6WAew7sAdrpMQMK4Efn4/W920r9ptOuckejOSSvyLr9pAWgC5rsHhR2FYuYw==, + } + engines: { node: '>= 14.0.0' } + + '@algolia/recommend@5.49.1': + resolution: + { + integrity: sha512-h2yz3AGeGkQwNgbLmoe3bxYs8fac4An1CprKTypYyTU/k3Q+9FbIvJ8aS1DoBKaTjSRZVoyQS7SZQio6GaHbZw==, + } + engines: { node: '>= 14.0.0' } + + '@algolia/requester-browser-xhr@5.49.1': + resolution: + { + integrity: sha512-2UPyRuUR/qpqSqH8mxFV5uBZWEpxhGPHLlx9Xf6OVxr79XO2ctzZQAhsmTZ6X22x+N8MBWpB9UEky7YU2HGFgA==, + } + engines: { node: '>= 14.0.0' } + + '@algolia/requester-fetch@5.49.1': + resolution: + { + integrity: sha512-N+xlE4lN+wpuT+4vhNEwPVlrfN+DWAZmSX9SYhbz986Oq8AMsqdntOqUyiOXVxYsQtfLwmiej24vbvJGYv1Qtw==, + } + engines: { node: '>= 14.0.0' } + + '@algolia/requester-node-http@5.49.1': + resolution: + { + integrity: sha512-zA5bkUOB5PPtTr182DJmajCiizHp0rCJQ0Chf96zNFvkdESKYlDeYA3tQ7r2oyHbu/8DiohAQ5PZ85edctzbXA==, + } + engines: { node: '>= 14.0.0' } + + '@antfu/install-pkg@1.1.0': + resolution: + { + integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==, + } + + '@asamuzakjp/css-color@3.2.0': + resolution: + { + integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==, + } + + '@babel/code-frame@7.29.0': + resolution: + { + integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==, + } + engines: { node: '>=6.9.0' } + + '@babel/compat-data@7.29.0': + resolution: + { + integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==, + } + engines: { node: '>=6.9.0' } + + '@babel/core@7.29.0': + resolution: + { + integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==, + } + engines: { node: '>=6.9.0' } + + '@babel/generator@7.29.1': + resolution: + { + integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: + { + integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-compilation-targets@7.28.6': + resolution: + { + integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: + { + integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.28.5': + resolution: + { + integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.6': + resolution: + { + integrity: sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==, + } + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-globals@7.28.0': + resolution: + { + integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: + { + integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-module-imports@7.28.6': + resolution: + { + integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-module-transforms@7.28.6': + resolution: + { + integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: + { + integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-plugin-utils@7.28.6': + resolution: + { + integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-remap-async-to-generator@7.27.1': + resolution: + { + integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.28.6': + resolution: + { + integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: + { + integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-string-parser@7.27.1': + resolution: + { + integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-validator-identifier@7.28.5': + resolution: + { + integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-validator-option@7.27.1': + resolution: + { + integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-wrap-function@7.28.6': + resolution: + { + integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==, + } + engines: { node: '>=6.9.0' } + + '@babel/helpers@7.28.6': + resolution: + { + integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==, + } + engines: { node: '>=6.9.0' } + + '@babel/parser@7.29.0': + resolution: + { + integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==, + } + engines: { node: '>=6.0.0' } + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': + resolution: + { + integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1': + resolution: + { + integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1': + resolution: + { + integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1': + resolution: + { + integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6': + resolution: + { + integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: + { + integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: + { + integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: + { + integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: + { + integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: + { + integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-assertions@7.28.6': + resolution: + { + integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: + { + integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: + { + integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: + { + integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.28.6': + resolution: + { + integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: + { + integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: + { + integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: + { + integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: + { + integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: + { + integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: + { + integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: + { + integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: + { + integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.28.6': + resolution: + { + integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: + { + integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-arrow-functions@7.27.1': + resolution: + { + integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.29.0': + resolution: + { + integrity: sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.28.6': + resolution: + { + integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.27.1': + resolution: + { + integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.28.6': + resolution: + { + integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.28.6': + resolution: + { + integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.28.6': + resolution: + { + integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.28.6': + resolution: + { + integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.28.6': + resolution: + { + integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.28.5': + resolution: + { + integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.28.6': + resolution: + { + integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.27.1': + resolution: + { + integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0': + resolution: + { + integrity: sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-dynamic-import@7.27.1': + resolution: + { + integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-explicit-resource-management@7.28.6': + resolution: + { + integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.28.6': + resolution: + { + integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.27.1': + resolution: + { + integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.27.1': + resolution: + { + integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.27.1': + resolution: + { + integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.28.6': + resolution: + { + integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.27.1': + resolution: + { + integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.28.6': + resolution: + { + integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.27.1': + resolution: + { + integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.27.1': + resolution: + { + integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.28.6': + resolution: + { + integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.29.0': + resolution: + { + integrity: sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.27.1': + resolution: + { + integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0': + resolution: + { + integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.27.1': + resolution: + { + integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6': + resolution: + { + integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.28.6': + resolution: + { + integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.28.6': + resolution: + { + integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.27.1': + resolution: + { + integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.28.6': + resolution: + { + integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.28.6': + resolution: + { + integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.27.7': + resolution: + { + integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.28.6': + resolution: + { + integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.28.6': + resolution: + { + integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.27.1': + resolution: + { + integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-display-name@7.28.0': + resolution: + { + integrity: sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-development@7.27.1': + resolution: + { + integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: + { + integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: + { + integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx@7.28.6': + resolution: + { + integrity: sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-pure-annotations@7.27.1': + resolution: + { + integrity: sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.29.0': + resolution: + { + integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regexp-modifiers@7.28.6': + resolution: + { + integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-reserved-words@7.27.1': + resolution: + { + integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.27.1': + resolution: + { + integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.28.6': + resolution: + { + integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.27.1': + resolution: + { + integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.27.1': + resolution: + { + integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.27.1': + resolution: + { + integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.6': + resolution: + { + integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.27.1': + resolution: + { + integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.28.6': + resolution: + { + integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.27.1': + resolution: + { + integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.28.6': + resolution: + { + integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.29.0': + resolution: + { + integrity: sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: + { + integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/preset-react@7.28.5': + resolution: + { + integrity: sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.28.5': + resolution: + { + integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.6': + resolution: + { + integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==, + } + engines: { node: '>=6.9.0' } + + '@babel/template@7.28.6': + resolution: + { + integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==, + } + engines: { node: '>=6.9.0' } + + '@babel/traverse@7.29.0': + resolution: + { + integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==, + } + engines: { node: '>=6.9.0' } + + '@babel/types@7.29.0': + resolution: + { + integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==, + } + engines: { node: '>=6.9.0' } + + '@changesets/apply-release-plan@7.1.0': + resolution: + { + integrity: sha512-yq8ML3YS7koKQ/9bk1PqO0HMzApIFNwjlwCnwFEXMzNe8NpzeeYYKCmnhWJGkN8g7E51MnWaSbqRcTcdIxUgnQ==, + } + + '@changesets/assemble-release-plan@6.0.9': + resolution: + { + integrity: sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==, + } + + '@changesets/changelog-git@0.2.1': + resolution: + { + integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==, + } + + '@changesets/cli@2.30.0': + resolution: + { + integrity: sha512-5D3Nk2JPqMI1wK25pEymeWRSlSMdo5QOGlyfrKg0AOufrUcjEE3RQgaCpHoBiM31CSNrtSgdJ0U6zL1rLDDfBA==, + } + hasBin: true + + '@changesets/config@3.1.3': + resolution: + { + integrity: sha512-vnXjcey8YgBn2L1OPWd3ORs0bGC4LoYcK/ubpgvzNVr53JXV5GiTVj7fWdMRsoKUH7hhhMAQnsJUqLr21EncNw==, + } + + '@changesets/errors@0.2.0': + resolution: + { + integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==, + } + + '@changesets/get-dependents-graph@2.1.3': + resolution: + { + integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==, + } + + '@changesets/get-release-plan@4.0.15': + resolution: + { + integrity: sha512-Q04ZaRPuEVZtA+auOYgFaVQQSA98dXiVe/yFaZfY7hoSmQICHGvP0TF4u3EDNHWmmCS4ekA/XSpKlSM2PyTS2g==, + } + + '@changesets/get-version-range-type@0.4.0': + resolution: + { + integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==, + } + + '@changesets/git@3.0.4': + resolution: + { + integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==, + } + + '@changesets/logger@0.1.1': + resolution: + { + integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==, + } + + '@changesets/parse@0.4.3': + resolution: + { + integrity: sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A==, + } + + '@changesets/pre@2.0.2': + resolution: + { + integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==, + } + + '@changesets/read@0.6.7': + resolution: + { + integrity: sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA==, + } + + '@changesets/should-skip-package@0.1.2': + resolution: + { + integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==, + } + + '@changesets/types@4.1.0': + resolution: + { + integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==, + } + + '@changesets/types@6.1.0': + resolution: + { + integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==, + } + + '@changesets/write@0.4.0': + resolution: + { + integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==, + } + + '@csstools/color-helpers@5.1.0': + resolution: + { + integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==, + } + engines: { node: '>=18' } + + '@csstools/css-calc@2.1.4': + resolution: + { + integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==, + } + engines: { node: '>=18' } + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: + { + integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==, + } + engines: { node: '>=18' } + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: + { + integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==, + } + engines: { node: '>=18' } + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: + { + integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==, + } + engines: { node: '>=18' } + + '@docsearch/css@3.8.2': + resolution: + { + integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==, + } + + '@docsearch/js@3.8.2': + resolution: + { + integrity: sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==, + } + + '@docsearch/react@3.8.2': + resolution: + { + integrity: sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==, + } + peerDependencies: + '@types/react': '>= 16.8.0 < 19.0.0' + react: '>= 16.8.0 < 19.0.0' + react-dom: '>= 16.8.0 < 19.0.0' + search-insights: '>= 1 < 3' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + react-dom: + optional: true + search-insights: + optional: true + + '@egjs/hammerjs@2.0.17': + resolution: + { + integrity: sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==, + } + engines: { node: '>=0.8.0' } + + '@emnapi/core@1.8.1': + resolution: + { + integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==, + } + + '@emnapi/runtime@1.8.1': + resolution: + { + integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==, + } + + '@emnapi/wasi-threads@1.1.0': + resolution: + { + integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==, + } + + '@esbuild/aix-ppc64@0.21.5': + resolution: + { + integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==, + } + engines: { node: '>=12' } + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.3': + resolution: + { + integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==, + } + engines: { node: '>=18' } + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: + { + integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==, + } + engines: { node: '>=12' } + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.3': + resolution: + { + integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: + { + integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==, + } + engines: { node: '>=12' } + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.3': + resolution: + { + integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==, + } + engines: { node: '>=18' } + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: + { + integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.3': + resolution: + { + integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: + { + integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==, + } + engines: { node: '>=12' } + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.3': + resolution: + { + integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: + { + integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.3': + resolution: + { + integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: + { + integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==, + } + engines: { node: '>=12' } + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.3': + resolution: + { + integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: + { + integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.3': + resolution: + { + integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: + { + integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==, + } + engines: { node: '>=12' } + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.3': + resolution: + { + integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: + { + integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==, + } + engines: { node: '>=12' } + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.3': + resolution: + { + integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==, + } + engines: { node: '>=18' } + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: + { + integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==, + } + engines: { node: '>=12' } + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.3': + resolution: + { + integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==, + } + engines: { node: '>=18' } + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: + { + integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==, + } + engines: { node: '>=12' } + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.3': + resolution: + { + integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==, + } + engines: { node: '>=18' } + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: + { + integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==, + } + engines: { node: '>=12' } + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.3': + resolution: + { + integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==, + } + engines: { node: '>=18' } + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: + { + integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==, + } + engines: { node: '>=12' } + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.3': + resolution: + { + integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==, + } + engines: { node: '>=18' } + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: + { + integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==, + } + engines: { node: '>=12' } + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.3': + resolution: + { + integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==, + } + engines: { node: '>=18' } + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: + { + integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==, + } + engines: { node: '>=12' } + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.3': + resolution: + { + integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==, + } + engines: { node: '>=18' } + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: + { + integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.3': + resolution: + { + integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.3': + resolution: + { + integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.21.5': + resolution: + { + integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.3': + resolution: + { + integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.3': + resolution: + { + integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: + { + integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.3': + resolution: + { + integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.3': + resolution: + { + integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.21.5': + resolution: + { + integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.3': + resolution: + { + integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: + { + integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==, + } + engines: { node: '>=12' } + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.3': + resolution: + { + integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: + { + integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==, + } + engines: { node: '>=12' } + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.3': + resolution: + { + integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==, + } + engines: { node: '>=18' } + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: + { + integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.3': + resolution: + { + integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: + { + integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: + { + integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==, + } + engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + + '@eslint/config-array@0.23.2': + resolution: + { + integrity: sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + '@eslint/config-helpers@0.5.2': + resolution: + { + integrity: sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + '@eslint/core@1.1.0': + resolution: + { + integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + '@eslint/eslintrc@3.3.4': + resolution: + { + integrity: sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/js@10.0.1': + resolution: + { + integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.2': + resolution: + { + integrity: sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + '@eslint/plugin-kit@0.6.0': + resolution: + { + integrity: sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + '@humanfs/core@0.19.1': + resolution: + { + integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==, + } + engines: { node: '>=18.18.0' } + + '@humanfs/node@0.16.7': + resolution: + { + integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==, + } + engines: { node: '>=18.18.0' } + + '@humanwhocodes/module-importer@1.0.1': + resolution: + { + integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, + } + engines: { node: '>=12.22' } + + '@humanwhocodes/retry@0.4.3': + resolution: + { + integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==, + } + engines: { node: '>=18.18' } + + '@iconify-json/logos@1.2.10': + resolution: + { + integrity: sha512-qxaXKJ6fu8jzTMPQdHtNxlfx6tBQ0jXRbHZIYy5Ilh8Lx9US9FsAdzZWUR8MXV8PnWTKGDFO4ZZee9VwerCyMA==, + } + + '@iconify-json/simple-icons@1.2.72': + resolution: + { + integrity: sha512-wkcixntHvaCoqPqerGrNFcHQ3Yx1ux4ZkhscCDK0DEHpP62XCH+cxq1HTsRjbUiQl/M9K8bj03HF6Wgn5iE2rQ==, + } + + '@iconify/types@2.0.0': + resolution: + { + integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==, + } + + '@iconify/utils@3.1.0': + resolution: + { + integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==, + } + + '@inquirer/external-editor@1.0.3': + resolution: + { + integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==, + } + engines: { node: '>=18' } + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@isaacs/ttlcache@1.4.1': + resolution: + { + integrity: sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==, + } + engines: { node: '>=12' } + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: + { + integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==, + } + engines: { node: '>=8' } + + '@istanbuljs/schema@0.1.3': + resolution: + { + integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==, + } + engines: { node: '>=8' } + + '@jest/create-cache-key-function@29.7.0': + resolution: + { + integrity: sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jest/environment@29.7.0': + resolution: + { + integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jest/fake-timers@29.7.0': + resolution: + { + integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jest/schemas@29.6.3': + resolution: + { + integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jest/transform@29.7.0': + resolution: + { + integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jest/types@29.6.3': + resolution: + { + integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jridgewell/gen-mapping@0.3.13': + resolution: + { + integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==, + } + + '@jridgewell/remapping@2.3.5': + resolution: + { + integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==, + } + + '@jridgewell/resolve-uri@3.1.2': + resolution: + { + integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, + } + engines: { node: '>=6.0.0' } + + '@jridgewell/source-map@0.3.11': + resolution: + { + integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==, + } + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: + { + integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==, + } + + '@jridgewell/trace-mapping@0.3.31': + resolution: + { + integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==, + } + + '@manypkg/find-root@1.1.0': + resolution: + { + integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==, + } + + '@manypkg/get-packages@1.1.3': + resolution: + { + integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==, + } + + '@microsoft/api-extractor-model@7.33.4': + resolution: + { + integrity: sha512-u1LTaNTikZAQ9uK6KG1Ms7nvNedsnODnspq/gH2dcyETWvH4hVNGNDvRAEutH66kAmxA4/necElqGNs1FggC8w==, + } + + '@microsoft/api-extractor@7.57.6': + resolution: + { + integrity: sha512-0rFv/D8Grzw1Mjs2+8NGUR+o4h9LVm5zKRtMeWnpdB5IMJF4TeHCL1zR5LMCIudkOvyvjbhMG5Wjs0B5nqsrRQ==, + } + hasBin: true + + '@microsoft/tsdoc-config@0.18.1': + resolution: + { + integrity: sha512-9brPoVdfN9k9g0dcWkFeA7IH9bbcttzDJlXvkf8b2OBzd5MueR1V2wkKBL0abn0otvmkHJC6aapBOTJDDeMCZg==, + } + + '@microsoft/tsdoc@0.16.0': + resolution: + { + integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==, + } + + '@napi-rs/wasm-runtime@1.1.1': + resolution: + { + integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==, + } + + '@nodelib/fs.scandir@2.1.5': + resolution: + { + integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, + } + engines: { node: '>= 8' } + + '@nodelib/fs.stat@2.0.5': + resolution: + { + integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, + } + engines: { node: '>= 8' } + + '@nodelib/fs.walk@1.2.8': + resolution: + { + integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, + } + engines: { node: '>= 8' } + + '@oxc-parser/binding-android-arm-eabi@0.115.0': + resolution: + { + integrity: sha512-VoB2rhgoqgYf64d6Qs5emONQW8ASiTc0xp+aUE4JUhxjX+0pE3gblTYDO0upcN5vt9UlBNmUhAwfSifkfre7nw==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.115.0': + resolution: + { + integrity: sha512-lWRX75u+gqfB4TF3pWCHuvhaeneAmRl2b2qNBcl4S6yJ0HtnT4VXOMEZrq747i4Zby1ZTxj6mtOe678Bg8gRLw==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.115.0': + resolution: + { + integrity: sha512-ii/oOZjfGY1aszXTy29Z5DRyCEnBOrAXDVCvfdfXFQsOZlbbOa7NMHD7D+06YFe5qdxfmbWAYv4yn6QJi/0d2g==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.115.0': + resolution: + { + integrity: sha512-R/sW/p8l77wglbjpMcF+h/3rWbp9zk1mRP3U14mxTYIC2k3m+aLBpXXgk2zksqf9qKk5mcc4GIYsuCn9l8TgDg==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.115.0': + resolution: + { + integrity: sha512-CSJ5ldNm9wIGGkhaIJeGmxRMZbgxThRN+X1ufYQQUNi5jZDV/U3C2QDMywpP93fczNBj961hXtcUPO/oVGq4Pw==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.115.0': + resolution: + { + integrity: sha512-uWFwssE5dHfQ8lH+ktrsD9JA49+Qa0gtxZHUs62z1e91NgGz6O7jefHGI6aygNyKNS45pnnBSDSP/zV977MsOQ==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.115.0': + resolution: + { + integrity: sha512-fZbqt8y/sKQ+v6bBCuv/mYYFoC0+fZI3mGDDEemmDOhT78+aUs2+4ZMdbd2btlXmnLaScl37r8IRbhnok5Ka9w==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.115.0': + resolution: + { + integrity: sha512-1ej/MjuTY9tJEunU/hUPIFmgH5PqgMQoRjNOvOkibtJ3Zqlw/+Lc+HGHDNET8sjbgIkWzdhX+p4J96A5CPdbag==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [linux] + + '@oxc-parser/binding-linux-arm64-musl@0.115.0': + resolution: + { + integrity: sha512-HjsZbJPH9mMd4swJRywVMsDZsJX0hyKb1iNHo5ijRl5yhtbO3lj7ImSrrL1oZ1VEg0te4iKmDGGz/6YPLd1G8w==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [linux] + + '@oxc-parser/binding-linux-ppc64-gnu@0.115.0': + resolution: + { + integrity: sha512-zhhePoBrd7kQx3oClX/W6NldsuCbuMqaN9rRsY+6/WoorAb4j490PG/FjqgAXscWp2uSW2WV9L+ksn0wHrvsrg==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [ppc64] + os: [linux] + + '@oxc-parser/binding-linux-riscv64-gnu@0.115.0': + resolution: + { + integrity: sha512-t/IRojvUE9XrKu+/H1b8YINug+7Q6FLls5rsm2lxB5mnS8GN/eYAYrPgHkcg9/1SueRDSzGpDYu3lGWTObk1zw==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [riscv64] + os: [linux] + + '@oxc-parser/binding-linux-riscv64-musl@0.115.0': + resolution: + { + integrity: sha512-79jBHSSh/YpQRAmvYoaCfpyToRbJ/HBrdB7hxK2ku2JMehjopTVo+xMJss/RV7/ZYqeezgjvKDQzapJbgcjVZA==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [riscv64] + os: [linux] + + '@oxc-parser/binding-linux-s390x-gnu@0.115.0': + resolution: + { + integrity: sha512-nA1TpxkhNTIOMMyiSSsa7XIVJVoOU/SsVrHIz3gHvWweB5PHCQfO7w+Lb2EP0lBWokv7HtA/KbF7aLDoXzmuMw==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [s390x] + os: [linux] + + '@oxc-parser/binding-linux-x64-gnu@0.115.0': + resolution: + { + integrity: sha512-9iVX789DoC3SaOOG+X6NcF/tVChgLp2vcHffzOC2/Z1JTPlz6bMG2ogvcW6/9s0BG2qvhNQImd+gbWYeQbOwVw==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [linux] + + '@oxc-parser/binding-linux-x64-musl@0.115.0': + resolution: + { + integrity: sha512-RmQmk+mjCB0nMNfEYhaCxwofLo1Z95ebHw1AGvRiWGCd4zhCNOyskgCbMogIcQzSB3SuEKWgkssyaiQYVAA4hQ==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [linux] + + '@oxc-parser/binding-openharmony-arm64@0.115.0': + resolution: + { + integrity: sha512-viigraWWQhhDvX5aGq+wrQq58k00Xq3MHz/0R4AFMxGlZ8ogNonpEfNc73Q5Ly87Z6sU9BvxEdG0dnYTfVnmew==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.115.0': + resolution: + { + integrity: sha512-IzGCrMwXhpb4kTXy/8lnqqqwjI7eOvy+r9AhVw+hsr8t1ecBBEHprcNy0aKatFHN6hsX7UMHHQmBAQjVvL/p1A==, + } + engines: { node: '>=14.0.0' } + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.115.0': + resolution: + { + integrity: sha512-/ym+Absk/TLFvbhh3se9XYuI1D7BrUVHw4RaG/2dmWKgBenrZHaJsgnRb7NJtaOyjEOLIPtULx1wDdVL0SX2eg==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.115.0': + resolution: + { + integrity: sha512-AQSZjIR+b+Te7uaO/hGTMjT8/oxlYrvKrOTi4KTHF/O6osjHEatUQ3y6ZW2+8+lJxy20zIcGz6iQFmFq/qDKkg==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.115.0': + resolution: + { + integrity: sha512-oxUl82N+fIO9jIaXPph8SPPHQXrA08BHokBBJW8ct9F/x6o6bZE6eUAhUtWajbtvFhL8UYcCWRMba+kww6MBlA==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [win32] + + '@oxc-project/types@0.115.0': + resolution: + { + integrity: sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==, + } + + '@pkgr/core@0.2.9': + resolution: + { + integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==, + } + engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + + '@polka/url@1.0.0-next.29': + resolution: + { + integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==, + } + + '@quansync/fs@1.0.0': + resolution: + { + integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==, + } + + '@react-native/assets-registry@0.84.1': + resolution: + { + integrity: sha512-lAJ6PDZv95FdT9s9uhc9ivhikW1Zwh4j9XdXM7J2l4oUA3t37qfoBmTSDLuPyE3Bi+Xtwa11hJm0BUTT2sc/gg==, + } + engines: { node: '>= 20.19.4' } + + '@react-native/codegen@0.84.1': + resolution: + { + integrity: sha512-n1RIU0QAavgCg1uC5+s53arL7/mpM+16IBhJ3nCFSd/iK5tUmCwxQDcIDC703fuXfpub/ZygeSjVN8bcOWn0gA==, + } + engines: { node: '>= 20.19.4' } + peerDependencies: + '@babel/core': '*' + + '@react-native/community-cli-plugin@0.84.1': + resolution: + { + integrity: sha512-f6a+mJEJ6Joxlt/050TqYUr7uRRbeKnz8lnpL7JajhpsgZLEbkJRjH8HY5QiLcRdUwWFtizml4V+vcO3P4RxoQ==, + } + engines: { node: '>= 20.19.4' } + peerDependencies: + '@react-native-community/cli': '*' + '@react-native/metro-config': '*' + peerDependenciesMeta: + '@react-native-community/cli': + optional: true + '@react-native/metro-config': + optional: true + + '@react-native/debugger-frontend@0.84.1': + resolution: + { + integrity: sha512-rUU/Pyh3R5zT0WkVgB+yA6VwOp7HM5Hz4NYE97ajFS07OUIcv8JzBL3MXVdSSjLfldfqOuPEuKUaZcAOwPgabw==, + } + engines: { node: '>= 20.19.4' } + + '@react-native/debugger-shell@0.84.1': + resolution: + { + integrity: sha512-LIGhh4q4ette3yW5OzmukNMYwmINYrRGDZqKyTYc/VZyNpblZPw72coXVHXdfpPT6+YlxHqXzn3UjFZpNODGCQ==, + } + engines: { node: '>= 20.19.4' } + + '@react-native/dev-middleware@0.84.1': + resolution: + { + integrity: sha512-Z83ra+Gk6ElAhH3XRrv3vwbwCPTb04sPPlNpotxcFZb5LtRQZwT91ZQEXw3GOJCVIFp9EQ/gj8AQbVvtHKOUlQ==, + } + engines: { node: '>= 20.19.4' } + + '@react-native/gradle-plugin@0.84.1': + resolution: + { + integrity: sha512-7uVlPBE3uluRNRX4MW7PUJIO1LDBTpAqStKHU7LHH+GRrdZbHsWtOEAX8PiY4GFfBEvG8hEjiuTOqAxMjV+hDg==, + } + engines: { node: '>= 20.19.4' } + + '@react-native/js-polyfills@0.84.1': + resolution: + { + integrity: sha512-UsTe2AbUugsfyI7XIHMQq4E7xeC8a6GrYwuK+NohMMMJMxmyM3JkzIk+GB9e2il6ScEQNMJNaj+q+i5za8itxQ==, + } + engines: { node: '>= 20.19.4' } + + '@react-native/normalize-colors@0.84.1': + resolution: + { + integrity: sha512-/UPaQ4jl95soXnLDEJ6Cs6lnRXhwbxtT4KbZz+AFDees7prMV2NOLcHfCnzmTabf5Y3oxENMVBL666n4GMLcTA==, + } + + '@react-native/virtualized-lists@0.84.1': + resolution: + { + integrity: sha512-sJoDunzhci8ZsqxlUiKoLut4xQeQcmbIgvDHGQKeBz6uEq9HgU+hCWOijMRr6sLP0slQVfBAza34Rq7IbXZZOA==, + } + engines: { node: '>= 20.19.4' } + peerDependencies: + '@types/react': ^19.2.0 + react: '*' + react-native: '*' + peerDependenciesMeta: + '@types/react': + optional: true + + '@react-navigation/bottom-tabs@7.15.5': + resolution: + { + integrity: sha512-wQHredlCrRmShWQ1vF4HUcLdaiJ8fUgnbaeQH7BJ7MQVQh4mdzab0IOY/4QSmUyNRB350oyu1biTycyQ5FKWMQ==, + } + peerDependencies: + '@react-navigation/native': ^7.1.33 + react: '>= 18.2.0' + react-native: '*' + react-native-safe-area-context: '>= 4.0.0' + react-native-screens: '>= 4.0.0' + + '@react-navigation/core@7.16.1': + resolution: + { + integrity: sha512-xhquoyhKdqDfiL7LuupbwYnmauUGfVFGDEJO34m26k8zSN1eDjQ2stBZcHN8ILOI1PrG9885nf8ZmfaQxPS0ww==, + } + peerDependencies: + react: '>= 18.2.0' + + '@react-navigation/elements@2.9.10': + resolution: + { + integrity: sha512-N8tuBekzTRb0pkMHFJGvmC6Q5OisSbt6gzvw7RHMnp4NDo5auVllT12sWFaTXf8mTduaLKNSrD/NZNaOqThCBg==, + } + peerDependencies: + '@react-native-masked-view/masked-view': '>= 0.2.0' + '@react-navigation/native': ^7.1.33 + react: '>= 18.2.0' + react-native: '*' + react-native-safe-area-context: '>= 4.0.0' + peerDependenciesMeta: + '@react-native-masked-view/masked-view': + optional: true + + '@react-navigation/native@7.1.33': + resolution: + { + integrity: sha512-DpFdWGcgLajKZ1TuIvDNQsblN2QaUFWpTQaB8v7WRP9Mix8H/6TFoIrZd93pbymI2hybd6UYrD+lI408eWVcfw==, + } + peerDependencies: + react: '>= 18.2.0' + react-native: '*' + + '@react-navigation/routers@7.5.3': + resolution: + { + integrity: sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==, + } + + '@react-navigation/stack@7.8.4': + resolution: + { + integrity: sha512-Sbg4p/tW1oNnXic8eSDTuiLrtLg4eB54WjI+s3HTW1FkZMKxcMVSMDjOm/tc3mwIoZ4wmHkcj2zE2H/W0Vrm9g==, + } + peerDependencies: + '@react-navigation/native': ^7.1.33 + react: '>= 18.2.0' + react-native: '*' + react-native-gesture-handler: '>= 2.0.0' + react-native-safe-area-context: '>= 4.0.0' + react-native-screens: '>= 4.0.0' + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: + { + integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==, + } + + '@rollup/pluginutils@5.3.0': + resolution: + { + integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==, + } + engines: { node: '>=14.0.0' } + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.59.0': + resolution: + { + integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==, + } + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.59.0': + resolution: + { + integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==, + } + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.59.0': + resolution: + { + integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==, + } + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.59.0': + resolution: + { + integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==, + } + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.59.0': + resolution: + { + integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==, + } + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.59.0': + resolution: + { + integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==, + } + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + resolution: + { + integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==, + } + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + resolution: + { + integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==, + } + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + resolution: + { + integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==, + } + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.59.0': + resolution: + { + integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==, + } + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + resolution: + { + integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==, + } + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.59.0': + resolution: + { + integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==, + } + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + resolution: + { + integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==, + } + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + resolution: + { + integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==, + } + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + resolution: + { + integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==, + } + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + resolution: + { + integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==, + } + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + resolution: + { + integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==, + } + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.59.0': + resolution: + { + integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==, + } + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.59.0': + resolution: + { + integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==, + } + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.59.0': + resolution: + { + integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==, + } + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.59.0': + resolution: + { + integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==, + } + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + resolution: + { + integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==, + } + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + resolution: + { + integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==, + } + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.59.0': + resolution: + { + integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==, + } + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.59.0': + resolution: + { + integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==, + } + cpu: [x64] + os: [win32] + + '@rushstack/node-core-library@5.20.3': + resolution: + { + integrity: sha512-95JgEPq2k7tHxhF9/OJnnyHDXfC9cLhhta0An/6MlkDsX2A6dTzDrTUG18vx4vjc280V0fi0xDH9iQczpSuWsw==, + } + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/problem-matcher@0.2.1': + resolution: + { + integrity: sha512-gulfhBs6n+I5b7DvjKRfhMGyUejtSgOHTclF/eONr8hcgF1APEDjhxIsfdUYYMzC3rvLwGluqLjbwCFZ8nxrog==, + } + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/rig-package@0.7.2': + resolution: + { + integrity: sha512-9XbFWuqMYcHUso4mnETfhGVUSaADBRj6HUAAEYk50nMPn8WRICmBuCphycQGNB3duIR6EEZX3Xj3SYc2XiP+9A==, + } + + '@rushstack/terminal@0.22.3': + resolution: + { + integrity: sha512-gHC9pIMrUPzAbBiI4VZMU7Q+rsCzb8hJl36lFIulIzoceKotyKL3Rd76AZ2CryCTKEg+0bnTj406HE5YY5OQvw==, + } + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/ts-command-line@5.3.3': + resolution: + { + integrity: sha512-c+ltdcvC7ym+10lhwR/vWiOhsrm/bP3By2VsFcs5qTKv+6tTmxgbVrtJ5NdNjANiV5TcmOZgUN+5KYQ4llsvEw==, + } + + '@shikijs/core@2.5.0': + resolution: + { + integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==, + } + + '@shikijs/engine-javascript@2.5.0': + resolution: + { + integrity: sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==, + } + + '@shikijs/engine-oniguruma@2.5.0': + resolution: + { + integrity: sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==, + } + + '@shikijs/langs@2.5.0': + resolution: + { + integrity: sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==, + } + + '@shikijs/themes@2.5.0': + resolution: + { + integrity: sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==, + } + + '@shikijs/transformers@2.5.0': + resolution: + { + integrity: sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==, + } + + '@shikijs/types@2.5.0': + resolution: + { + integrity: sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==, + } + + '@shikijs/vscode-textmate@10.0.2': + resolution: + { + integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==, + } + + '@sinclair/typebox@0.27.10': + resolution: + { + integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==, + } + + '@sinonjs/commons@3.0.1': + resolution: + { + integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==, + } + + '@sinonjs/fake-timers@10.3.0': + resolution: + { + integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==, + } + + '@standard-schema/spec@1.1.0': + resolution: + { + integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==, + } + + '@testing-library/dom@10.4.1': + resolution: + { + integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==, + } + engines: { node: '>=18' } + + '@testing-library/react@16.3.2': + resolution: + { + integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==, + } + engines: { node: '>=18' } + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: + { + integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==, + } + engines: { node: '>=12', npm: '>=6' } + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@tybys/wasm-util@0.10.1': + resolution: + { + integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==, + } + + '@types/argparse@1.0.38': + resolution: + { + integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==, + } + + '@types/aria-query@5.0.4': + resolution: + { + integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==, + } + + '@types/babel__core@7.20.5': + resolution: + { + integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==, + } + + '@types/babel__generator@7.27.0': + resolution: + { + integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==, + } + + '@types/babel__template@7.4.4': + resolution: + { + integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==, + } + + '@types/babel__traverse@7.28.0': + resolution: + { + integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==, + } + + '@types/chai@5.2.3': + resolution: + { + integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==, + } + + '@types/deep-eql@4.0.2': + resolution: + { + integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==, + } + + '@types/esrecurse@4.3.1': + resolution: + { + integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==, + } + + '@types/estree@1.0.8': + resolution: + { + integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, + } + + '@types/graceful-fs@4.1.9': + resolution: + { + integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==, + } + + '@types/hammerjs@2.0.46': + resolution: + { + integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==, + } + + '@types/hast@3.0.4': + resolution: + { + integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==, + } + + '@types/istanbul-lib-coverage@2.0.6': + resolution: + { + integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==, + } + + '@types/istanbul-lib-report@3.0.3': + resolution: + { + integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==, + } + + '@types/istanbul-reports@3.0.4': + resolution: + { + integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==, + } + + '@types/json-schema@7.0.15': + resolution: + { + integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, + } + + '@types/linkify-it@5.0.0': + resolution: + { + integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==, + } + + '@types/markdown-it@14.1.2': + resolution: + { + integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==, + } + + '@types/mdast@4.0.4': + resolution: + { + integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==, + } + + '@types/mdurl@2.0.0': + resolution: + { + integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==, + } + + '@types/node@12.20.55': + resolution: + { + integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==, + } + + '@types/node@25.3.3': + resolution: + { + integrity: sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==, + } + + '@types/react@19.2.14': + resolution: + { + integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==, + } + + '@types/stack-utils@2.0.3': + resolution: + { + integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==, + } + + '@types/unist@3.0.3': + resolution: + { + integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==, + } + + '@types/web-bluetooth@0.0.21': + resolution: + { + integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==, + } + + '@types/whatwg-mimetype@3.0.2': + resolution: + { + integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==, + } + + '@types/ws@8.18.1': + resolution: + { + integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==, + } + + '@types/yargs-parser@21.0.3': + resolution: + { + integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==, + } + + '@types/yargs@17.0.35': + resolution: + { + integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==, + } + + '@typescript-eslint/eslint-plugin@8.56.1': + resolution: + { + integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + '@typescript-eslint/parser': ^8.56.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.56.1': + resolution: + { + integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.56.1': + resolution: + { + integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.56.1': + resolution: + { + integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@typescript-eslint/tsconfig-utils@8.56.1': + resolution: + { + integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.56.1': + resolution: + { + integrity: sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.56.1': + resolution: + { + integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@typescript-eslint/typescript-estree@8.56.1': + resolution: + { + integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.56.1': + resolution: + { + integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.56.1': + resolution: + { + integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@ungap/structured-clone@1.3.0': + resolution: + { + integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==, + } + + '@unocss/cli@66.6.4': + resolution: + { + integrity: sha512-jSeGL9a7tchoKvGQAsEdtjmvEu1axdikK5fdvmQnDOnLSM5Vo5wCthGYtsIIpQvb9HFBe0NupAJNwpjRBGiCaQ==, + } + engines: { node: '>=14' } + hasBin: true + + '@unocss/config@66.6.4': + resolution: + { + integrity: sha512-iwHl5FG81cOAMalqigjw21Z2tMa0xjN0doQxnGOLx8KP+BllruXSjBj8CRk3m6Ny9fDxfpFY0ruYbIBA5AGwDQ==, + } + engines: { node: '>=14' } + + '@unocss/core@66.6.4': + resolution: + { + integrity: sha512-Fii3lhVJVFrKUz6hMGAkq3sXBfNnXB2G8bldNHuBHJpDAoP1F0oO/SU/oSqSjCYvtcD5RtOn8qwzcHuuN3B/mg==, + } + + '@unocss/extractor-arbitrary-variants@66.6.4': + resolution: + { + integrity: sha512-l827c/UdE2FUBiaXDde5f/IjW41TflhtnjgQr3tJoCw7v9VuokDJFl+iOTyaH6AwMKpMeSBB+DU5Ippj4IOs9w==, + } + + '@unocss/inspector@66.6.4': + resolution: + { + integrity: sha512-q5oplYKCyO6YHN1MFQadkjs4fTTOKgsw0tXoSft6RLXowo8Utv6nBmED4yWb6Y6iYFmFU5RZ8VavxZvfghOlmg==, + } + + '@unocss/preset-attributify@66.6.4': + resolution: + { + integrity: sha512-pksrugV/GqfgyUonHycxDvxUPVI3H9LiRcOEf1mZweD2qAqT6lH9qE1AHHddiZpWAcics4CkUkDpgXRwgt+wJQ==, + } + + '@unocss/preset-icons@66.6.4': + resolution: + { + integrity: sha512-Xz8EQdPkANHlHUmWDw5/ehWTcn4tJeNltB4OnxI5vsi0hiqpLJxxKUE/vLUVH1I4GnVFCF4bBg7fmHanEcL0/A==, + } + + '@unocss/preset-mini@66.6.4': + resolution: + { + integrity: sha512-8xUXf/Bbi1/h98ldL56OxOnWUgWy0el0/xCGDLKYtBRUYGvZgrV+ys9UxY1/z+w7q+T+PZi+3qhc0O06nJ8wUw==, + } + + '@unocss/preset-tagify@66.6.4': + resolution: + { + integrity: sha512-eWu9fH6c6gZH1FswMVPaX0kMS8Jw6dqDvlVLbjZgWraAHTon53lOnB2365bXgsl5zXYg30JGMzP/k171FJQWig==, + } + + '@unocss/preset-typography@66.6.4': + resolution: + { + integrity: sha512-APtMRFUPA4e5S1Yyc3LWTqiy+XMq/SEMStkcGM6Rroy8Rzx+ItfqV/UrOWdg8gFYFPK8tVOvNG+40qlZy5Keww==, + } + + '@unocss/preset-uno@66.6.4': + resolution: + { + integrity: sha512-9BAprWrx6/leMaRBzH91vGYl4mEgIX/BP1h8ucEJ3aAo6dFrfmpC56HG7wOHNGMr4/uxm4aD7uI2SUpN+CBEEg==, + } + + '@unocss/preset-web-fonts@66.6.4': + resolution: + { + integrity: sha512-N2qqeKf0W1mDXDBlXBdN32Dm6pLEbTFQsRe6WpX9SH5pCrEvuJG8cnIBPPpATLC+Qf2EWOepg1fIX+iWoF4Cyw==, + } + + '@unocss/preset-wind3@66.6.4': + resolution: + { + integrity: sha512-RxPR5czvE3RJ+eJoMM2AkPews7z4vSOeqTX8OIILzvEUFG1fRUvxMLaHGb4qstGPtHBJKrwNmvYjMozoiU2EgA==, + } + + '@unocss/preset-wind4@66.6.4': + resolution: + { + integrity: sha512-MvI3bxoOafEADiFJSHr7WB8nT01ZQvjsfWEuRNtNeRSTBVZ2QuJW8imL2sr9fk1qHoHmzN/3HefpTQoxiQWVcA==, + } + + '@unocss/preset-wind@66.6.4': + resolution: + { + integrity: sha512-OGeLXvcGQROuFrFmu/WOY8sbBvNBzAyi0firMY5AZhSkGmX/q4aBEJGGs3eiuMwg6JIhPg4QXzLjL7uWZJ0ZgQ==, + } + + '@unocss/rule-utils@66.6.4': + resolution: + { + integrity: sha512-n/vCodRuzKtRBpZqd4OLVujDEJlPl11Iw5AtxB4GYsRT4AED/JY//XHLb5ubdLa1j3m84OAfnkT9Gr9sMWcwig==, + } + engines: { node: '>=14' } + + '@unocss/transformer-attributify-jsx@66.6.4': + resolution: + { + integrity: sha512-Rw9g3Ed/Et1W68znIuCod4OTLlOmuPpt2/6ZsylzNPEgGdJCHGYOdNs6Ai5IlbjrlOE4XfwK0O0iJQdk01V6FA==, + } + + '@unocss/transformer-compile-class@66.6.4': + resolution: + { + integrity: sha512-sZrPIp28xPnroT+BTX6onHfIXwjBkuPDyO3oKyciuCRZxGgTkV6GXV6lSGSu2EHFRjCmzzuCWgo33gU55TtklA==, + } + + '@unocss/transformer-directives@66.6.4': + resolution: + { + integrity: sha512-IIczs0NZeEOIa/X28gkJevT6FtCWoMT3OmnMFDRi9plu3d7BYuQuBkBUYVyT7lIspn+iENCaXFl3e1l60e/xpw==, + } + + '@unocss/transformer-variant-group@66.6.4': + resolution: + { + integrity: sha512-evAbg2fKuhJ0en71Y8iHJYbuED0SSiqg7BIajSbk0BQvy8N70wbu19Ljpjfc7JfcWV/vSWgNIklOr/TsYJhU6g==, + } + + '@unocss/vite@66.6.4': + resolution: + { + integrity: sha512-qLSfJ2a0iDMhM/d3zpg9RQ7RW22tnP5hXARo430m9UK7bK1SmAbMAS70Wv2/FuRScBGLeMfluIuePghtuzgOLQ==, + } + peerDependencies: + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0-0 + + '@vitejs/plugin-react@4.7.0': + resolution: + { + integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==, + } + engines: { node: ^14.18.0 || >=16.0.0 } + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@vitejs/plugin-vue@5.2.4': + resolution: + { + integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==, + } + engines: { node: ^18.0.0 || >=20.0.0 } + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + + '@vitest/expect@4.0.18': + resolution: + { + integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==, + } + + '@vitest/mocker@4.0.18': + resolution: + { + integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==, + } + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.0.18': + resolution: + { + integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==, + } + + '@vitest/runner@4.0.18': + resolution: + { + integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==, + } + + '@vitest/snapshot@4.0.18': + resolution: + { + integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==, + } + + '@vitest/spy@4.0.18': + resolution: + { + integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==, + } + + '@vitest/utils@4.0.18': + resolution: + { + integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==, + } + + '@volar/language-core@2.4.28': + resolution: + { + integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==, + } + + '@volar/source-map@2.4.28': + resolution: + { + integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==, + } + + '@volar/typescript@2.4.28': + resolution: + { + integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==, + } + + '@vue/compiler-core@3.5.29': + resolution: + { + integrity: sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==, + } + + '@vue/compiler-dom@3.5.29': + resolution: + { + integrity: sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==, + } + + '@vue/compiler-sfc@3.5.29': + resolution: + { + integrity: sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==, + } + + '@vue/compiler-ssr@3.5.29': + resolution: + { + integrity: sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==, + } + + '@vue/compiler-vue2@2.7.16': + resolution: + { + integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==, + } + + '@vue/devtools-api@7.7.9': + resolution: + { + integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==, + } + + '@vue/devtools-kit@7.7.9': + resolution: + { + integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==, + } + + '@vue/devtools-shared@7.7.9': + resolution: + { + integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==, + } + + '@vue/language-core@2.2.0': + resolution: + { + integrity: sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==, + } + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.5.29': + resolution: + { + integrity: sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==, + } + + '@vue/runtime-core@3.5.29': + resolution: + { + integrity: sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==, + } + + '@vue/runtime-dom@3.5.29': + resolution: + { + integrity: sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==, + } + + '@vue/server-renderer@3.5.29': + resolution: + { + integrity: sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==, + } + peerDependencies: + vue: 3.5.29 + + '@vue/shared@3.5.29': + resolution: + { + integrity: sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==, + } + + '@vueuse/core@12.8.2': + resolution: + { + integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==, + } + + '@vueuse/integrations@12.8.2': + resolution: + { + integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==, + } + peerDependencies: + async-validator: ^4 + axios: ^1 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + + '@vueuse/metadata@12.8.2': + resolution: + { + integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==, + } + + '@vueuse/shared@12.8.2': + resolution: + { + integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==, + } + + abort-controller@3.0.0: + resolution: + { + integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==, + } + engines: { node: '>=6.5' } + + accepts@2.0.0: + resolution: + { + integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==, + } + engines: { node: '>= 0.6' } + + acorn-jsx@5.3.2: + resolution: + { + integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, + } + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: + { + integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==, + } + engines: { node: '>=0.4.0' } + hasBin: true + + agent-base@7.1.4: + resolution: + { + integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==, + } + engines: { node: '>= 14' } + + ajv-draft-04@1.0.0: + resolution: + { + integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==, + } + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-formats@3.0.1: + resolution: + { + integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==, + } + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@6.14.0: + resolution: + { + integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==, + } + + ajv@8.18.0: + resolution: + { + integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==, + } + + algoliasearch@5.49.1: + resolution: + { + integrity: sha512-X3Pp2aRQhg4xUC6PQtkubn5NpRKuUPQ9FPDQlx36SmpFwwH2N0/tw4c+NXV3nw3PsgeUs+BuWGP0gjz3TvENLQ==, + } + engines: { node: '>= 14.0.0' } + + alien-signals@0.4.14: + resolution: + { + integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==, + } + + anser@1.4.10: + resolution: + { + integrity: sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==, + } + + ansi-colors@4.1.3: + resolution: + { + integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==, + } + engines: { node: '>=6' } + + ansi-regex@5.0.1: + resolution: + { + integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, + } + engines: { node: '>=8' } + + ansi-styles@4.3.0: + resolution: + { + integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, + } + engines: { node: '>=8' } + + ansi-styles@5.2.0: + resolution: + { + integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==, + } + engines: { node: '>=10' } + + anymatch@3.1.3: + resolution: + { + integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==, + } + engines: { node: '>= 8' } + + argparse@1.0.10: + resolution: + { + integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==, + } + + argparse@2.0.1: + resolution: + { + integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, + } + + aria-query@5.3.0: + resolution: + { + integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==, + } + + array-union@2.1.0: + resolution: + { + integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==, + } + engines: { node: '>=8' } + + asap@2.0.6: + resolution: + { + integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==, + } + + assertion-error@2.0.1: + resolution: + { + integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==, + } + engines: { node: '>=12' } + + asynckit@0.4.0: + resolution: + { + integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==, + } + + babel-jest@29.7.0: + resolution: + { + integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: + { + integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==, + } + engines: { node: '>=8' } + + babel-plugin-jest-hoist@29.6.3: + resolution: + { + integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + babel-plugin-polyfill-corejs2@0.4.15: + resolution: + { + integrity: sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==, + } + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.14.0: + resolution: + { + integrity: sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==, + } + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.6: + resolution: + { + integrity: sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==, + } + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-syntax-hermes-parser@0.32.0: + resolution: + { + integrity: sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg==, + } + + babel-preset-current-node-syntax@1.2.0: + resolution: + { + integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==, + } + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.0-0 + + babel-preset-jest@29.6.3: + resolution: + { + integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + peerDependencies: + '@babel/core': ^7.0.0 + + balanced-match@1.0.2: + resolution: + { + integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, + } + + balanced-match@4.0.4: + resolution: + { + integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==, + } + engines: { node: 18 || 20 || >=22 } + + base64-js@1.5.1: + resolution: + { + integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, + } + + baseline-browser-mapping@2.10.0: + resolution: + { + integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==, + } + engines: { node: '>=6.0.0' } + hasBin: true + + better-path-resolve@1.0.0: + resolution: + { + integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==, + } + engines: { node: '>=4' } + + birpc@2.9.0: + resolution: + { + integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==, + } + + brace-expansion@1.1.12: + resolution: + { + integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==, + } + + brace-expansion@2.0.2: + resolution: + { + integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==, + } + + brace-expansion@5.0.4: + resolution: + { + integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==, + } + engines: { node: 18 || 20 || >=22 } + + braces@3.0.3: + resolution: + { + integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==, + } + engines: { node: '>=8' } + + browserslist@4.28.1: + resolution: + { + integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==, + } + engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + hasBin: true + + bser@2.1.1: + resolution: + { + integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==, + } + + buffer-from@1.1.2: + resolution: + { + integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==, + } + + cac@6.7.14: + resolution: + { + integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==, + } + engines: { node: '>=8' } + + call-bind-apply-helpers@1.0.2: + resolution: + { + integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==, + } + engines: { node: '>= 0.4' } + + callsites@3.1.0: + resolution: + { + integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, + } + engines: { node: '>=6' } + + camelcase@5.3.1: + resolution: + { + integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==, + } + engines: { node: '>=6' } + + camelcase@6.3.0: + resolution: + { + integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==, + } + engines: { node: '>=10' } + + caniuse-lite@1.0.30001776: + resolution: + { + integrity: sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==, + } + + ccount@2.0.1: + resolution: + { + integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==, + } + + chai@6.2.2: + resolution: + { + integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==, + } + engines: { node: '>=18' } + + chalk@4.1.2: + resolution: + { + integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, + } + engines: { node: '>=10' } + + character-entities-html4@2.1.0: + resolution: + { + integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==, + } + + character-entities-legacy@3.0.0: + resolution: + { + integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==, + } + + chardet@2.1.1: + resolution: + { + integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==, + } + + chokidar@5.0.0: + resolution: + { + integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==, + } + engines: { node: '>= 20.19.0' } + + chrome-launcher@0.15.2: + resolution: + { + integrity: sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==, + } + engines: { node: '>=12.13.0' } + hasBin: true + + chromium-edge-launcher@0.2.0: + resolution: + { + integrity: sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==, + } + + ci-info@2.0.0: + resolution: + { + integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==, + } + + ci-info@3.9.0: + resolution: + { + integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==, + } + engines: { node: '>=8' } + + cliui@8.0.1: + resolution: + { + integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==, + } + engines: { node: '>=12' } + + color-convert@2.0.1: + resolution: + { + integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, + } + engines: { node: '>=7.0.0' } + + color-name@1.1.4: + resolution: + { + integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, + } + + color-string@1.9.1: + resolution: + { + integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==, + } + + color@4.2.3: + resolution: + { + integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==, + } + engines: { node: '>=12.5.0' } + + colorette@2.0.20: + resolution: + { + integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==, + } + + combined-stream@1.0.8: + resolution: + { + integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==, + } + engines: { node: '>= 0.8' } + + comma-separated-tokens@2.0.3: + resolution: + { + integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==, + } + + commander@12.1.0: + resolution: + { + integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==, + } + engines: { node: '>=18' } + + commander@2.20.3: + resolution: + { + integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==, + } + + compare-versions@6.1.1: + resolution: + { + integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==, + } + + concat-map@0.0.1: + resolution: + { + integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, + } + + confbox@0.1.8: + resolution: + { + integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==, + } + + confbox@0.2.4: + resolution: + { + integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==, + } + + connect@3.7.0: + resolution: + { + integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==, + } + engines: { node: '>= 0.10.0' } + + consola@3.4.2: + resolution: + { + integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==, + } + engines: { node: ^14.18.0 || >=16.10.0 } + + convert-source-map@2.0.0: + resolution: + { + integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, + } + + copy-anything@4.0.5: + resolution: + { + integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==, + } + engines: { node: '>=18' } + + core-js-compat@3.48.0: + resolution: + { + integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==, + } + + cross-spawn@7.0.6: + resolution: + { + integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, + } + engines: { node: '>= 8' } + + css-tree@3.1.0: + resolution: + { + integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==, + } + engines: { node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0 } + + cssstyle@4.6.0: + resolution: + { + integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==, + } + engines: { node: '>=18' } + + csstype@3.2.3: + resolution: + { + integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==, + } + + data-urls@5.0.0: + resolution: + { + integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==, + } + engines: { node: '>=18' } + + de-indent@1.0.2: + resolution: + { + integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==, + } + + debug@2.6.9: + resolution: + { + integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==, + } + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: + { + integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==, + } + engines: { node: '>=6.0' } + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: + { + integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==, + } + + decode-uri-component@0.2.2: + resolution: + { + integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==, + } + engines: { node: '>=0.10' } + + decode-uri-component@0.4.1: + resolution: + { + integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==, + } + engines: { node: '>=14.16' } + + deep-is@0.1.4: + resolution: + { + integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, + } + + defu@6.1.4: + resolution: + { + integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==, + } + + delayed-stream@1.0.0: + resolution: + { + integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, + } + engines: { node: '>=0.4.0' } + + depd@2.0.0: + resolution: + { + integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==, + } + engines: { node: '>= 0.8' } + + dequal@2.0.3: + resolution: + { + integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==, + } + engines: { node: '>=6' } + + destr@2.0.5: + resolution: + { + integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==, + } + + destroy@1.2.0: + resolution: + { + integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==, + } + engines: { node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16 } + + detect-indent@6.1.0: + resolution: + { + integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==, + } + engines: { node: '>=8' } + + devlop@1.1.0: + resolution: + { + integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==, + } + + diff@8.0.3: + resolution: + { + integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==, + } + engines: { node: '>=0.3.1' } + + dir-glob@3.0.1: + resolution: + { + integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==, + } + engines: { node: '>=8' } + + dom-accessibility-api@0.5.16: + resolution: + { + integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==, + } + + dunder-proto@1.0.1: + resolution: + { + integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==, + } + engines: { node: '>= 0.4' } + + duplexer@0.1.2: + resolution: + { + integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==, + } + + ee-first@1.1.1: + resolution: + { + integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==, + } + + effector-action@1.2.2: + resolution: + { + integrity: sha512-gX1MeRoLJawqzvQWSxHio87sEoORm23kV5+gBTzpQDLnZcgc5GUOiPNpiSpgBMFRRhSyi1d5ggh7bH+UYWlQcQ==, + } + peerDependencies: + effector: '>=23' + patronum: '>=2.1.0' + + effector-react@23.3.0: + resolution: + { + integrity: sha512-QR0+x1EnbiWhO80Yc0GVF+I9xCYoxBm3t+QLB5Wg+1uY1Q1BrSWDmKvJaJJZ/+9BU4RAr25yS5J2EkdWnicu8g==, + } + engines: { node: '>=11.0.0' } + peerDependencies: + effector: ^23.0.0 + react: '>=16.8.0 <20.0.0' + + effector@23.4.4: + resolution: + { + integrity: sha512-QkZboRN28K/iwxigDhlJcI3ux3aNbt8kYGGH/GkqWG0OlGeyuBhb7PdM89Iu+ogV8Lmz16xIlwnXR2UNWI6psg==, + } + engines: { node: '>=11.0.0' } + + electron-to-chromium@1.5.307: + resolution: + { + integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==, + } + + emoji-regex-xs@1.0.0: + resolution: + { + integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==, + } + + emoji-regex@8.0.0: + resolution: + { + integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, + } + + encodeurl@1.0.2: + resolution: + { + integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==, + } + engines: { node: '>= 0.8' } + + encodeurl@2.0.0: + resolution: + { + integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==, + } + engines: { node: '>= 0.8' } + + enquirer@2.4.1: + resolution: + { + integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==, + } + engines: { node: '>=8.6' } + + entities@6.0.1: + resolution: + { + integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==, + } + engines: { node: '>=0.12' } + + entities@7.0.1: + resolution: + { + integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==, + } + engines: { node: '>=0.12' } + + error-stack-parser@2.1.4: + resolution: + { + integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==, + } + + es-define-property@1.0.1: + resolution: + { + integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==, + } + engines: { node: '>= 0.4' } + + es-errors@1.3.0: + resolution: + { + integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==, + } + engines: { node: '>= 0.4' } + + es-module-lexer@1.7.0: + resolution: + { + integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==, + } + + es-object-atoms@1.1.1: + resolution: + { + integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==, + } + engines: { node: '>= 0.4' } + + es-set-tostringtag@2.1.0: + resolution: + { + integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==, + } + engines: { node: '>= 0.4' } + + esbuild@0.21.5: + resolution: + { + integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==, + } + engines: { node: '>=12' } + hasBin: true + + esbuild@0.27.3: + resolution: + { + integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==, + } + engines: { node: '>=18' } + hasBin: true + + escalade@3.2.0: + resolution: + { + integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, + } + engines: { node: '>=6' } + + escape-html@1.0.3: + resolution: + { + integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==, + } + + escape-string-regexp@2.0.0: + resolution: + { + integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==, + } + engines: { node: '>=8' } + + escape-string-regexp@4.0.0: + resolution: + { + integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, + } + engines: { node: '>=10' } + + eslint-config-prettier@10.1.8: + resolution: + { + integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==, + } + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@5.5.5: + resolution: + { + integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==, + } + engines: { node: ^14.18.0 || >=16.0.0 } + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-scope@9.1.1: + resolution: + { + integrity: sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + eslint-visitor-keys@3.4.3: + resolution: + { + integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + + eslint-visitor-keys@4.2.1: + resolution: + { + integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + eslint-visitor-keys@5.0.1: + resolution: + { + integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + eslint@10.0.2: + resolution: + { + integrity: sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: + { + integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + espree@11.1.1: + resolution: + { + integrity: sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + esprima@4.0.1: + resolution: + { + integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==, + } + engines: { node: '>=4' } + hasBin: true + + esquery@1.7.0: + resolution: + { + integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==, + } + engines: { node: '>=0.10' } + + esrecurse@4.3.0: + resolution: + { + integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==, + } + engines: { node: '>=4.0' } + + estraverse@5.3.0: + resolution: + { + integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, + } + engines: { node: '>=4.0' } + + estree-walker@2.0.2: + resolution: + { + integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==, + } + + estree-walker@3.0.3: + resolution: + { + integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==, + } + + esutils@2.0.3: + resolution: + { + integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, + } + engines: { node: '>=0.10.0' } + + etag@1.8.1: + resolution: + { + integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==, + } + engines: { node: '>= 0.6' } + + event-target-shim@5.0.1: + resolution: + { + integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==, + } + engines: { node: '>=6' } + + expect-type@1.3.0: + resolution: + { + integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==, + } + engines: { node: '>=12.0.0' } + + exponential-backoff@3.1.3: + resolution: + { + integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==, + } + + exsolve@1.0.8: + resolution: + { + integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==, + } + + extendable-error@0.1.7: + resolution: + { + integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==, + } + + fast-deep-equal@3.1.3: + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, + } + + fast-diff@1.3.0: + resolution: + { + integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==, + } + + fast-glob@3.3.3: + resolution: + { + integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==, + } + engines: { node: '>=8.6.0' } + + fast-json-stable-stringify@2.1.0: + resolution: + { + integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, + } + + fast-levenshtein@2.0.6: + resolution: + { + integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, + } + + fast-uri@3.1.0: + resolution: + { + integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==, + } + + fastq@1.20.1: + resolution: + { + integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==, + } + + fb-dotslash@0.5.8: + resolution: + { + integrity: sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==, + } + engines: { node: '>=20' } + hasBin: true + + fb-watchman@2.0.2: + resolution: + { + integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==, + } + + fdir@6.5.0: + resolution: + { + integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==, + } + engines: { node: '>=12.0.0' } + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: + { + integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==, + } + engines: { node: '>=16.0.0' } + + fill-range@7.1.1: + resolution: + { + integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==, + } + engines: { node: '>=8' } + + filter-obj@1.1.0: + resolution: + { + integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==, + } + engines: { node: '>=0.10.0' } + + filter-obj@5.1.0: + resolution: + { + integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==, + } + engines: { node: '>=14.16' } + + finalhandler@1.1.2: + resolution: + { + integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==, + } + engines: { node: '>= 0.8' } + + find-up@4.1.0: + resolution: + { + integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==, + } + engines: { node: '>=8' } + + find-up@5.0.0: + resolution: + { + integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==, + } + engines: { node: '>=10' } + + flat-cache@4.0.1: + resolution: + { + integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==, + } + engines: { node: '>=16' } + + flatted@3.3.4: + resolution: + { + integrity: sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==, + } + + flow-enums-runtime@0.0.6: + resolution: + { + integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==, + } + + focus-trap@7.8.0: + resolution: + { + integrity: sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==, + } + + form-data@4.0.5: + resolution: + { + integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==, + } + engines: { node: '>= 6' } + + fresh@0.5.2: + resolution: + { + integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==, + } + engines: { node: '>= 0.6' } + + fs-extra@11.3.4: + resolution: + { + integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==, + } + engines: { node: '>=14.14' } + + fs-extra@7.0.1: + resolution: + { + integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==, + } + engines: { node: '>=6 <7 || >=8' } + + fs-extra@8.1.0: + resolution: + { + integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==, + } + engines: { node: '>=6 <7 || >=8' } + + fs.realpath@1.0.0: + resolution: + { + integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==, + } + + fsevents@2.3.3: + resolution: + { + integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, + } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + os: [darwin] + + function-bind@1.1.2: + resolution: + { + integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, + } + + gensync@1.0.0-beta.2: + resolution: + { + integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, + } + engines: { node: '>=6.9.0' } + + get-caller-file@2.0.5: + resolution: + { + integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==, + } + engines: { node: 6.* || 8.* || >= 10.* } + + get-intrinsic@1.3.0: + resolution: + { + integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==, + } + engines: { node: '>= 0.4' } + + get-package-type@0.1.0: + resolution: + { + integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==, + } + engines: { node: '>=8.0.0' } + + get-proto@1.0.1: + resolution: + { + integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==, + } + engines: { node: '>= 0.4' } + + glob-parent@5.1.2: + resolution: + { + integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, + } + engines: { node: '>= 6' } + + glob-parent@6.0.2: + resolution: + { + integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, + } + engines: { node: '>=10.13.0' } + + glob@7.2.3: + resolution: + { + integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==, + } + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + globals@14.0.0: + resolution: + { + integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==, + } + engines: { node: '>=18' } + + globals@17.4.0: + resolution: + { + integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==, + } + engines: { node: '>=18' } + + globby@11.1.0: + resolution: + { + integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==, + } + engines: { node: '>=10' } + + gopd@1.2.0: + resolution: + { + integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==, + } + engines: { node: '>= 0.4' } + + graceful-fs@4.2.11: + resolution: + { + integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, + } + + gzip-size@6.0.0: + resolution: + { + integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==, + } + engines: { node: '>=10' } + + happy-dom@20.8.3: + resolution: + { + integrity: sha512-lMHQRRwIPyJ70HV0kkFT7jH/gXzSI7yDkQFe07E2flwmNDFoWUTRMKpW2sglsnpeA7b6S2TJPp98EbQxai8eaQ==, + } + engines: { node: '>=20.0.0' } + + has-flag@4.0.0: + resolution: + { + integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, + } + engines: { node: '>=8' } + + has-symbols@1.1.0: + resolution: + { + integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==, + } + engines: { node: '>= 0.4' } + + has-tostringtag@1.0.2: + resolution: + { + integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==, + } + engines: { node: '>= 0.4' } + + hasown@2.0.2: + resolution: + { + integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==, + } + engines: { node: '>= 0.4' } + + hast-util-to-html@9.0.5: + resolution: + { + integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==, + } + + hast-util-whitespace@3.0.0: + resolution: + { + integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==, + } + + he@1.2.0: + resolution: + { + integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==, + } + hasBin: true + + hermes-compiler@250829098.0.9: + resolution: + { + integrity: sha512-hZ5O7PDz1vQ99TS7HD3FJ9zVynfU1y+VWId6U1Pldvd8hmAYrNec/XLPYJKD3dLOW6NXak6aAQAuMuSo3ji0tQ==, + } + + hermes-estree@0.32.0: + resolution: + { + integrity: sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==, + } + + hermes-estree@0.33.3: + resolution: + { + integrity: sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==, + } + + hermes-parser@0.32.0: + resolution: + { + integrity: sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==, + } + + hermes-parser@0.33.3: + resolution: + { + integrity: sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==, + } + + history@5.3.0: + resolution: + { + integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==, + } + + hoist-non-react-statics@3.3.2: + resolution: + { + integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==, + } + + hookable@5.5.3: + resolution: + { + integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==, + } + + html-encoding-sniffer@4.0.0: + resolution: + { + integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==, + } + engines: { node: '>=18' } + + html-void-elements@3.0.0: + resolution: + { + integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==, + } + + http-errors@2.0.1: + resolution: + { + integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==, + } + engines: { node: '>= 0.8' } + + http-proxy-agent@7.0.2: + resolution: + { + integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==, + } + engines: { node: '>= 14' } + + https-proxy-agent@7.0.6: + resolution: + { + integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==, + } + engines: { node: '>= 14' } + + human-id@4.1.3: + resolution: + { + integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==, + } + hasBin: true + + iconv-lite@0.6.3: + resolution: + { + integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==, + } + engines: { node: '>=0.10.0' } + + iconv-lite@0.7.2: + resolution: + { + integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==, + } + engines: { node: '>=0.10.0' } + + ignore@5.3.2: + resolution: + { + integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, + } + engines: { node: '>= 4' } + + ignore@7.0.5: + resolution: + { + integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==, + } + engines: { node: '>= 4' } + + image-size@1.2.1: + resolution: + { + integrity: sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==, + } + engines: { node: '>=16.x' } + hasBin: true + + import-fresh@3.3.1: + resolution: + { + integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==, + } + engines: { node: '>=6' } + + import-lazy@4.0.0: + resolution: + { + integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==, + } + engines: { node: '>=8' } + + imurmurhash@0.1.4: + resolution: + { + integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, + } + engines: { node: '>=0.8.19' } + + inflight@1.0.6: + resolution: + { + integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==, + } + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: + { + integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==, + } + + invariant@2.2.4: + resolution: + { + integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==, + } + + is-arrayish@0.3.4: + resolution: + { + integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==, + } + + is-core-module@2.16.1: + resolution: + { + integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==, + } + engines: { node: '>= 0.4' } + + is-docker@2.2.1: + resolution: + { + integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==, + } + engines: { node: '>=8' } + hasBin: true + + is-extglob@2.1.1: + resolution: + { + integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, + } + engines: { node: '>=0.10.0' } + + is-fullwidth-code-point@3.0.0: + resolution: + { + integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, + } + engines: { node: '>=8' } + + is-glob@4.0.3: + resolution: + { + integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, + } + engines: { node: '>=0.10.0' } + + is-number@7.0.0: + resolution: + { + integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, + } + engines: { node: '>=0.12.0' } + + is-potential-custom-element-name@1.0.1: + resolution: + { + integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==, + } + + is-subdir@1.2.0: + resolution: + { + integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==, + } + engines: { node: '>=4' } + + is-what@5.5.0: + resolution: + { + integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==, + } + engines: { node: '>=18' } + + is-windows@1.0.2: + resolution: + { + integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==, + } + engines: { node: '>=0.10.0' } + + is-wsl@2.2.0: + resolution: + { + integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==, + } + engines: { node: '>=8' } + + isexe@2.0.0: + resolution: + { + integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, + } + + istanbul-lib-coverage@3.2.2: + resolution: + { + integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==, + } + engines: { node: '>=8' } + + istanbul-lib-instrument@5.2.1: + resolution: + { + integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==, + } + engines: { node: '>=8' } + + jest-environment-node@29.7.0: + resolution: + { + integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-get-type@29.6.3: + resolution: + { + integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-haste-map@29.7.0: + resolution: + { + integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-message-util@29.7.0: + resolution: + { + integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-mock@29.7.0: + resolution: + { + integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-regex-util@29.6.3: + resolution: + { + integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-util@29.7.0: + resolution: + { + integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-validate@29.7.0: + resolution: + { + integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-worker@29.7.0: + resolution: + { + integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jiti@2.6.1: + resolution: + { + integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==, + } + hasBin: true + + jju@1.4.0: + resolution: + { + integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==, + } + + js-tokens@4.0.0: + resolution: + { + integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, + } + + js-yaml@3.14.2: + resolution: + { + integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==, + } + hasBin: true + + js-yaml@4.1.1: + resolution: + { + integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==, + } + hasBin: true + + jsc-safe-url@0.2.4: + resolution: + { + integrity: sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==, + } + + jsdom@25.0.1: + resolution: + { + integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==, + } + engines: { node: '>=18' } + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: + { + integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==, + } + engines: { node: '>=6' } + hasBin: true + + json-buffer@3.0.1: + resolution: + { + integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, + } + + json-schema-traverse@0.4.1: + resolution: + { + integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, + } + + json-schema-traverse@1.0.0: + resolution: + { + integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==, + } + + json-stable-stringify-without-jsonify@1.0.1: + resolution: + { + integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, + } + + json5@2.2.3: + resolution: + { + integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==, + } + engines: { node: '>=6' } + hasBin: true + + jsonfile@4.0.0: + resolution: + { + integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==, + } + + jsonfile@6.2.0: + resolution: + { + integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==, + } + + keyv@4.5.4: + resolution: + { + integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, + } + + kolorist@1.8.0: + resolution: + { + integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==, + } + + leven@3.1.0: + resolution: + { + integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==, + } + engines: { node: '>=6' } + + levn@0.4.1: + resolution: + { + integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, + } + engines: { node: '>= 0.8.0' } + + lighthouse-logger@1.4.2: + resolution: + { + integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==, + } + + local-pkg@1.1.2: + resolution: + { + integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==, + } + engines: { node: '>=14' } + + locate-path@5.0.0: + resolution: + { + integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==, + } + engines: { node: '>=8' } + + locate-path@6.0.0: + resolution: + { + integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, + } + engines: { node: '>=10' } + + lodash.debounce@4.0.8: + resolution: + { + integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==, + } + + lodash.startcase@4.4.0: + resolution: + { + integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==, + } + + lodash.throttle@4.1.1: + resolution: + { + integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==, + } + + lodash@4.17.23: + resolution: + { + integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==, + } + + loose-envify@1.4.0: + resolution: + { + integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, + } + hasBin: true + + lru-cache@10.4.3: + resolution: + { + integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, + } + + lru-cache@5.1.1: + resolution: + { + integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, + } + + lru-cache@6.0.0: + resolution: + { + integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==, + } + engines: { node: '>=10' } + + lz-string@1.5.0: + resolution: + { + integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==, + } + hasBin: true + + magic-regexp@0.10.0: + resolution: + { + integrity: sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg==, + } + + magic-string@0.30.21: + resolution: + { + integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==, + } + + makeerror@1.0.12: + resolution: + { + integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==, + } + + mark.js@8.11.1: + resolution: + { + integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==, + } + + marky@1.3.0: + resolution: + { + integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==, + } + + math-intrinsics@1.1.0: + resolution: + { + integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==, + } + engines: { node: '>= 0.4' } + + mdast-util-to-hast@13.2.1: + resolution: + { + integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==, + } + + mdn-data@2.12.2: + resolution: + { + integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==, + } + + memoize-one@5.2.1: + resolution: + { + integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==, + } + + merge-stream@2.0.0: + resolution: + { + integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==, + } + + merge2@1.4.1: + resolution: + { + integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, + } + engines: { node: '>= 8' } + + metro-babel-transformer@0.83.5: + resolution: + { + integrity: sha512-d9FfmgUEVejTiSb7bkQeLRGl6aeno2UpuPm3bo3rCYwxewj03ymvOn8s8vnS4fBqAPQ+cE9iQM40wh7nGXR+eA==, + } + engines: { node: '>=20.19.4' } + + metro-cache-key@0.83.5: + resolution: + { + integrity: sha512-Ycl8PBajB7bhbAI7Rt0xEyiF8oJ0RWX8EKkolV1KfCUlC++V/GStMSGpPLwnnBZXZWkCC5edBPzv1Hz1Yi0Euw==, + } + engines: { node: '>=20.19.4' } + + metro-cache@0.83.5: + resolution: + { + integrity: sha512-oH+s4U+IfZyg8J42bne2Skc90rcuESIYf86dYittcdWQtPfcaFXWpByPyTuWk3rR1Zz3Eh5HOrcVImfEhhJLng==, + } + engines: { node: '>=20.19.4' } + + metro-config@0.83.5: + resolution: + { + integrity: sha512-JQ/PAASXH7yczgV6OCUSRhZYME+NU8NYjI2RcaG5ga4QfQ3T/XdiLzpSb3awWZYlDCcQb36l4Vl7i0Zw7/Tf9w==, + } + engines: { node: '>=20.19.4' } + + metro-core@0.83.5: + resolution: + { + integrity: sha512-YcVcLCrf0ed4mdLa82Qob0VxYqfhmlRxUS8+TO4gosZo/gLwSvtdeOjc/Vt0pe/lvMNrBap9LlmvZM8FIsMgJQ==, + } + engines: { node: '>=20.19.4' } + + metro-file-map@0.83.5: + resolution: + { + integrity: sha512-ZEt8s3a1cnYbn40nyCD+CsZdYSlwtFh2kFym4lo+uvfM+UMMH+r/BsrC6rbNClSrt+B7rU9T+Te/sh/NL8ZZKQ==, + } + engines: { node: '>=20.19.4' } + + metro-minify-terser@0.83.5: + resolution: + { + integrity: sha512-Toe4Md1wS1PBqbvB0cFxBzKEVyyuYTUb0sgifAZh/mSvLH84qA1NAWik9sISWatzvfWf3rOGoUoO5E3f193a3Q==, + } + engines: { node: '>=20.19.4' } + + metro-resolver@0.83.5: + resolution: + { + integrity: sha512-7p3GtzVUpbAweJeCcUJihJeOQl1bDuimO5ueo1K0BUpUtR41q5EilbQ3klt16UTPPMpA+tISWBtsrqU556mY1A==, + } + engines: { node: '>=20.19.4' } + + metro-runtime@0.83.5: + resolution: + { + integrity: sha512-f+b3ue9AWTVlZe2Xrki6TAoFtKIqw30jwfk7GQ1rDUBQaE0ZQ+NkiMEtb9uwH7uAjJ87U7Tdx1Jg1OJqUfEVlA==, + } + engines: { node: '>=20.19.4' } + + metro-source-map@0.83.5: + resolution: + { + integrity: sha512-VT9bb2KO2/4tWY9Z2yeZqTUao7CicKAOps9LUg2aQzsz+04QyuXL3qgf1cLUVRjA/D6G5u1RJAlN1w9VNHtODQ==, + } + engines: { node: '>=20.19.4' } + + metro-symbolicate@0.83.5: + resolution: + { + integrity: sha512-EMIkrjNRz/hF+p0RDdxoE60+dkaTLPN3vaaGkFmX5lvFdO6HPfHA/Ywznzkev+za0VhPQ5KSdz49/MALBRteHA==, + } + engines: { node: '>=20.19.4' } + hasBin: true + + metro-transform-plugins@0.83.5: + resolution: + { + integrity: sha512-KxYKzZL+lt3Os5H2nx7YkbkWVduLZL5kPrE/Yq+Prm/DE1VLhpfnO6HtPs8vimYFKOa58ncl60GpoX0h7Wm0Vw==, + } + engines: { node: '>=20.19.4' } + + metro-transform-worker@0.83.5: + resolution: + { + integrity: sha512-8N4pjkNXc6ytlP9oAM6MwqkvUepNSW39LKYl9NjUMpRDazBQ7oBpQDc8Sz4aI8jnH6AGhF7s1m/ayxkN1t04yA==, + } + engines: { node: '>=20.19.4' } + + metro@0.83.5: + resolution: + { + integrity: sha512-BgsXevY1MBac/3ZYv/RfNFf/4iuW9X7f4H8ZNkiH+r667HD9sVujxcmu4jvEzGCAm4/WyKdZCuyhAcyhTHOucQ==, + } + engines: { node: '>=20.19.4' } + hasBin: true + + micromark-util-character@2.1.1: + resolution: + { + integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==, + } + + micromark-util-encode@2.0.1: + resolution: + { + integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==, + } + + micromark-util-sanitize-uri@2.0.1: + resolution: + { + integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==, + } + + micromark-util-symbol@2.0.1: + resolution: + { + integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==, + } + + micromark-util-types@2.0.2: + resolution: + { + integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==, + } + + micromatch@4.0.8: + resolution: + { + integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==, + } + engines: { node: '>=8.6' } + + mime-db@1.52.0: + resolution: + { + integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==, + } + engines: { node: '>= 0.6' } + + mime-db@1.54.0: + resolution: + { + integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==, + } + engines: { node: '>= 0.6' } + + mime-types@2.1.35: + resolution: + { + integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==, + } + engines: { node: '>= 0.6' } + + mime-types@3.0.2: + resolution: + { + integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==, + } + engines: { node: '>=18' } + + mime@1.6.0: + resolution: + { + integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==, + } + engines: { node: '>=4' } + hasBin: true + + minimatch@10.2.1: + resolution: + { + integrity: sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==, + } + engines: { node: 20 || >=22 } + + minimatch@10.2.4: + resolution: + { + integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==, + } + engines: { node: 18 || 20 || >=22 } + + minimatch@3.1.5: + resolution: + { + integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==, + } + + minimatch@9.0.9: + resolution: + { + integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==, + } + engines: { node: '>=16 || 14 >=14.17' } + + minisearch@7.2.0: + resolution: + { + integrity: sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==, + } + + mitt@3.0.1: + resolution: + { + integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==, + } + + mkdirp@1.0.4: + resolution: + { + integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==, + } + engines: { node: '>=10' } + hasBin: true + + mlly@1.8.0: + resolution: + { + integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==, + } + + mri@1.2.0: + resolution: + { + integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==, + } + engines: { node: '>=4' } + + mrmime@2.0.1: + resolution: + { + integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==, + } + engines: { node: '>=10' } + + ms@2.0.0: + resolution: + { + integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==, + } + + ms@2.1.3: + resolution: + { + integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, + } + + muggle-string@0.4.1: + resolution: + { + integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==, + } + + nanoid@3.3.11: + resolution: + { + integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==, + } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + hasBin: true + + natural-compare@1.4.0: + resolution: + { + integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, + } + + negotiator@1.0.0: + resolution: + { + integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==, + } + engines: { node: '>= 0.6' } + + node-fetch-native@1.6.7: + resolution: + { + integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==, + } + + node-int64@0.4.0: + resolution: + { + integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==, + } + + node-releases@2.0.27: + resolution: + { + integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==, + } + + normalize-path@3.0.0: + resolution: + { + integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, + } + engines: { node: '>=0.10.0' } + + nullthrows@1.1.1: + resolution: + { + integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==, + } + + nwsapi@2.2.23: + resolution: + { + integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==, + } + + ob1@0.83.5: + resolution: + { + integrity: sha512-vNKPYC8L5ycVANANpF/S+WZHpfnRWKx/F3AYP4QMn6ZJTh+l2HOrId0clNkEmua58NB9vmI9Qh7YOoV/4folYg==, + } + engines: { node: '>=20.19.4' } + + obug@2.1.1: + resolution: + { + integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==, + } + + ofetch@1.5.1: + resolution: + { + integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==, + } + + on-finished@2.3.0: + resolution: + { + integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==, + } + engines: { node: '>= 0.8' } + + on-finished@2.4.1: + resolution: + { + integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==, + } + engines: { node: '>= 0.8' } + + once@1.4.0: + resolution: + { + integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, + } + + oniguruma-to-es@3.1.1: + resolution: + { + integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==, + } + + open@7.4.2: + resolution: + { + integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==, + } + engines: { node: '>=8' } + + optionator@0.9.4: + resolution: + { + integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==, + } + engines: { node: '>= 0.8.0' } + + outdent@0.5.0: + resolution: + { + integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==, + } + + oxc-parser@0.115.0: + resolution: + { + integrity: sha512-2w7Xn3CbS/zwzSY82S5WLemrRu3CT57uF7Lx8llrE/2bul6iMTcJE4Rbls7GDNbLn3ttATI68PfOz2Pt3KZ2cQ==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + + oxc-walker@0.7.0: + resolution: + { + integrity: sha512-54B4KUhrzbzc4sKvKwVYm7E2PgeROpGba0/2nlNZMqfDyca+yOor5IMb4WLGBatGDT0nkzYdYuzylg7n3YfB7A==, + } + peerDependencies: + oxc-parser: '>=0.98.0' + + p-filter@2.1.0: + resolution: + { + integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==, + } + engines: { node: '>=8' } + + p-limit@2.3.0: + resolution: + { + integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==, + } + engines: { node: '>=6' } + + p-limit@3.1.0: + resolution: + { + integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, + } + engines: { node: '>=10' } + + p-locate@4.1.0: + resolution: + { + integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==, + } + engines: { node: '>=8' } + + p-locate@5.0.0: + resolution: + { + integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==, + } + engines: { node: '>=10' } + + p-map@2.1.0: + resolution: + { + integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==, + } + engines: { node: '>=6' } + + p-try@2.2.0: + resolution: + { + integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==, + } + engines: { node: '>=6' } + + package-manager-detector@0.2.11: + resolution: + { + integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==, + } + + package-manager-detector@1.6.0: + resolution: + { + integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==, + } + + parent-module@1.0.1: + resolution: + { + integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, + } + engines: { node: '>=6' } + + parse5@7.3.0: + resolution: + { + integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==, + } + + parseurl@1.3.3: + resolution: + { + integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==, + } + engines: { node: '>= 0.8' } + + path-browserify@1.0.1: + resolution: + { + integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==, + } + + path-exists@4.0.0: + resolution: + { + integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, + } + engines: { node: '>=8' } + + path-is-absolute@1.0.1: + resolution: + { + integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==, + } + engines: { node: '>=0.10.0' } + + path-key@3.1.1: + resolution: + { + integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, + } + engines: { node: '>=8' } + + path-parse@1.0.7: + resolution: + { + integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, + } + + path-type@4.0.0: + resolution: + { + integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==, + } + engines: { node: '>=8' } + + pathe@2.0.3: + resolution: + { + integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, + } + + patronum@2.3.0: + resolution: + { + integrity: sha512-BfKIOpoymVz6XnkOn8Fi5QZ1a3r/3lXdd8BcdHmYDbIXPTIRnD1EPFBFev/DheWnOge6/ZswEqgNF2ANLGOxLw==, + } + peerDependencies: + effector: ^23 + + perfect-debounce@1.0.0: + resolution: + { + integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==, + } + + perfect-debounce@2.1.0: + resolution: + { + integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==, + } + + picocolors@1.1.1: + resolution: + { + integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, + } + + picomatch@2.3.1: + resolution: + { + integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, + } + engines: { node: '>=8.6' } + + picomatch@4.0.3: + resolution: + { + integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==, + } + engines: { node: '>=12' } + + pify@4.0.1: + resolution: + { + integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==, + } + engines: { node: '>=6' } + + pirates@4.0.7: + resolution: + { + integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==, + } + engines: { node: '>= 6' } + + pkg-types@1.3.1: + resolution: + { + integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==, + } + + pkg-types@2.3.0: + resolution: + { + integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==, + } + + postcss@8.5.8: + resolution: + { + integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==, + } + engines: { node: ^10 || ^12 || >=14 } + + preact@10.28.4: + resolution: + { + integrity: sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ==, + } + + prelude-ls@1.2.1: + resolution: + { + integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, + } + engines: { node: '>= 0.8.0' } + + prettier-linter-helpers@1.0.1: + resolution: + { + integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==, + } + engines: { node: '>=6.0.0' } + + prettier@2.8.8: + resolution: + { + integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==, + } + engines: { node: '>=10.13.0' } + hasBin: true + + prettier@3.8.1: + resolution: + { + integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==, + } + engines: { node: '>=14' } + hasBin: true + + pretty-format@27.5.1: + resolution: + { + integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==, + } + engines: { node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0 } + + pretty-format@29.7.0: + resolution: + { + integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + promise@8.3.0: + resolution: + { + integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==, + } + + property-information@7.1.0: + resolution: + { + integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==, + } + + punycode@2.3.1: + resolution: + { + integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, + } + engines: { node: '>=6' } + + quansync@0.2.11: + resolution: + { + integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==, + } + + quansync@1.0.0: + resolution: + { + integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==, + } + + query-string@7.1.3: + resolution: + { + integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==, + } + engines: { node: '>=6' } + + query-string@9.3.1: + resolution: + { + integrity: sha512-5fBfMOcDi5SA9qj5jZhWAcTtDfKF5WFdd2uD9nVNlbxVv1baq65aALy6qofpNEGELHvisjjasxQp7BlM9gvMzw==, + } + engines: { node: '>=18' } + + queue-microtask@1.2.3: + resolution: + { + integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, + } + + queue@6.0.2: + resolution: + { + integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==, + } + + range-parser@1.2.1: + resolution: + { + integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==, + } + engines: { node: '>= 0.6' } + + react-devtools-core@6.1.5: + resolution: + { + integrity: sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==, + } + + react-dom@19.2.4: + resolution: + { + integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==, + } + peerDependencies: + react: ^19.2.4 + + react-freeze@1.0.4: + resolution: + { + integrity: sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==, + } + engines: { node: '>=10' } + peerDependencies: + react: '>=17.0.0' + + react-is@16.13.1: + resolution: + { + integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==, + } + + react-is@17.0.2: + resolution: + { + integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==, + } + + react-is@18.3.1: + resolution: + { + integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==, + } + + react-is@19.2.4: + resolution: + { + integrity: sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==, + } + + react-native-gesture-handler@2.30.0: + resolution: + { + integrity: sha512-5YsnKHGa0X9C8lb5oCnKm0fLUPM6CRduvUUw2Bav4RIj/C3HcFh4RIUnF8wgG6JQWCL1//gRx4v+LVWgcIQdGA==, + } + peerDependencies: + react: '*' + react-native: '*' + + react-native-safe-area-context@5.7.0: + resolution: + { + integrity: sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ==, + } + peerDependencies: + react: '*' + react-native: '*' + + react-native-screens@4.24.0: + resolution: + { + integrity: sha512-SyoiGaDofiyGPFrUkn1oGsAzkRuX1JUvTD9YQQK3G1JGQ5VWkvHgYSsc1K9OrLsDQxN7NmV71O0sHCAh8cBetA==, + } + peerDependencies: + react: '*' + react-native: '*' + + react-native@0.84.1: + resolution: + { + integrity: sha512-0PjxOyXRu3tZ8EobabxSukvhKje2HJbsZikR0U+pvS0pYZza2hXKjcSBiBdFN4h9D0S3v6a8kkrDK6WTRKMwzg==, + } + engines: { node: '>= 20.19.4' } + hasBin: true + peerDependencies: + '@types/react': ^19.1.1 + react: ^19.2.3 + peerDependenciesMeta: + '@types/react': + optional: true + + react-refresh@0.14.2: + resolution: + { + integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==, + } + engines: { node: '>=0.10.0' } + + react-refresh@0.17.0: + resolution: + { + integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==, + } + engines: { node: '>=0.10.0' } + + react@19.2.4: + resolution: + { + integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==, + } + engines: { node: '>=0.10.0' } + + read-yaml-file@1.1.0: + resolution: + { + integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==, + } + engines: { node: '>=6' } + + readdirp@5.0.0: + resolution: + { + integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==, + } + engines: { node: '>= 20.19.0' } + + regenerate-unicode-properties@10.2.2: + resolution: + { + integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==, + } + engines: { node: '>=4' } + + regenerate@1.4.2: + resolution: + { + integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==, + } + + regenerator-runtime@0.13.11: + resolution: + { + integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==, + } + + regex-recursion@6.0.2: + resolution: + { + integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==, + } + + regex-utilities@2.3.0: + resolution: + { + integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==, + } + + regex@6.1.0: + resolution: + { + integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==, + } + + regexp-tree@0.1.27: + resolution: + { + integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==, + } + hasBin: true + + regexpu-core@6.4.0: + resolution: + { + integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==, + } + engines: { node: '>=4' } + + regjsgen@0.8.0: + resolution: + { + integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==, + } + + regjsparser@0.13.0: + resolution: + { + integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==, + } + hasBin: true + + require-directory@2.1.1: + resolution: + { + integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==, + } + engines: { node: '>=0.10.0' } + + require-from-string@2.0.2: + resolution: + { + integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==, + } + engines: { node: '>=0.10.0' } + + resolve-from@4.0.0: + resolution: + { + integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, + } + engines: { node: '>=4' } + + resolve-from@5.0.0: + resolution: + { + integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==, + } + engines: { node: '>=8' } + + resolve@1.22.11: + resolution: + { + integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==, + } + engines: { node: '>= 0.4' } + hasBin: true + + reusify@1.1.0: + resolution: + { + integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==, + } + engines: { iojs: '>=1.0.0', node: '>=0.10.0' } + + rfdc@1.4.1: + resolution: + { + integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==, + } + + rimraf@3.0.2: + resolution: + { + integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==, + } + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup@4.59.0: + resolution: + { + integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==, + } + engines: { node: '>=18.0.0', npm: '>=8.0.0' } + hasBin: true + + rrweb-cssom@0.7.1: + resolution: + { + integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==, + } + + rrweb-cssom@0.8.0: + resolution: + { + integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==, + } + + run-parallel@1.2.0: + resolution: + { + integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, + } + + safer-buffer@2.1.2: + resolution: + { + integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, + } + + saxes@6.0.0: + resolution: + { + integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==, + } + engines: { node: '>=v12.22.7' } + + scheduler@0.27.0: + resolution: + { + integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==, + } + + search-insights@2.17.3: + resolution: + { + integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==, + } + + semver@6.3.1: + resolution: + { + integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==, + } + hasBin: true + + semver@7.5.4: + resolution: + { + integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==, + } + engines: { node: '>=10' } + hasBin: true + + semver@7.7.4: + resolution: + { + integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==, + } + engines: { node: '>=10' } + hasBin: true + + send@0.19.2: + resolution: + { + integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==, + } + engines: { node: '>= 0.8.0' } + + serialize-error@2.1.0: + resolution: + { + integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==, + } + engines: { node: '>=0.10.0' } + + serve-static@1.16.3: + resolution: + { + integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==, + } + engines: { node: '>= 0.8.0' } + + setprototypeof@1.2.0: + resolution: + { + integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==, + } + + sf-symbols-typescript@2.2.0: + resolution: + { + integrity: sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==, + } + engines: { node: '>=10' } + + shebang-command@2.0.0: + resolution: + { + integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, + } + engines: { node: '>=8' } + + shebang-regex@3.0.0: + resolution: + { + integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, + } + engines: { node: '>=8' } + + shell-quote@1.8.3: + resolution: + { + integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==, + } + engines: { node: '>= 0.4' } + + shiki@2.5.0: + resolution: + { + integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==, + } + + siginfo@2.0.0: + resolution: + { + integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, + } + + signal-exit@3.0.7: + resolution: + { + integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, + } + + signal-exit@4.1.0: + resolution: + { + integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==, + } + engines: { node: '>=14' } + + simple-swizzle@0.2.4: + resolution: + { + integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==, + } + + sirv@3.0.2: + resolution: + { + integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==, + } + engines: { node: '>=18' } + + slash@3.0.0: + resolution: + { + integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==, + } + engines: { node: '>=8' } + + source-map-js@1.2.1: + resolution: + { + integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, + } + engines: { node: '>=0.10.0' } + + source-map-support@0.5.21: + resolution: + { + integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==, + } + + source-map@0.5.7: + resolution: + { + integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==, + } + engines: { node: '>=0.10.0' } + + source-map@0.6.1: + resolution: + { + integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, + } + engines: { node: '>=0.10.0' } + + space-separated-tokens@2.0.2: + resolution: + { + integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==, + } + + spawndamnit@3.0.1: + resolution: + { + integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==, + } + + speakingurl@14.0.1: + resolution: + { + integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==, + } + engines: { node: '>=0.10.0' } + + split-on-first@1.1.0: + resolution: + { + integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==, + } + engines: { node: '>=6' } + + split-on-first@3.0.0: + resolution: + { + integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==, + } + engines: { node: '>=12' } + + sprintf-js@1.0.3: + resolution: + { + integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==, + } + + stack-utils@2.0.6: + resolution: + { + integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==, + } + engines: { node: '>=10' } + + stackback@0.0.2: + resolution: + { + integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==, + } + + stackframe@1.3.4: + resolution: + { + integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==, + } + + stacktrace-parser@0.1.11: + resolution: + { + integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==, + } + engines: { node: '>=6' } + + statuses@1.5.0: + resolution: + { + integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==, + } + engines: { node: '>= 0.6' } + + statuses@2.0.2: + resolution: + { + integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==, + } + engines: { node: '>= 0.8' } + + std-env@3.10.0: + resolution: + { + integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==, + } + + strict-uri-encode@2.0.0: + resolution: + { + integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==, + } + engines: { node: '>=4' } + + string-argv@0.3.2: + resolution: + { + integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==, + } + engines: { node: '>=0.6.19' } + + string-width@4.2.3: + resolution: + { + integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, + } + engines: { node: '>=8' } + + stringify-entities@4.0.4: + resolution: + { + integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==, + } + + strip-ansi@6.0.1: + resolution: + { + integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, + } + engines: { node: '>=8' } + + strip-bom@3.0.0: + resolution: + { + integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==, + } + engines: { node: '>=4' } + + strip-json-comments@3.1.1: + resolution: + { + integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, + } + engines: { node: '>=8' } + + superjson@2.2.6: + resolution: + { + integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==, + } + engines: { node: '>=16' } + + supports-color@7.2.0: + resolution: + { + integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, + } + engines: { node: '>=8' } + + supports-color@8.1.1: + resolution: + { + integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==, + } + engines: { node: '>=10' } + + supports-preserve-symlinks-flag@1.0.0: + resolution: + { + integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, + } + engines: { node: '>= 0.4' } + + symbol-tree@3.2.4: + resolution: + { + integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==, + } + + synckit@0.11.12: + resolution: + { + integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==, + } + engines: { node: ^14.18.0 || >=16.0.0 } + + tabbable@6.4.0: + resolution: + { + integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==, + } + + term-size@2.2.1: + resolution: + { + integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==, + } + engines: { node: '>=8' } + + terser@5.46.0: + resolution: + { + integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==, + } + engines: { node: '>=10' } + hasBin: true + + test-exclude@6.0.0: + resolution: + { + integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==, + } + engines: { node: '>=8' } + + throat@5.0.0: + resolution: + { + integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==, + } + + tinybench@2.9.0: + resolution: + { + integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==, + } + + tinyexec@1.0.2: + resolution: + { + integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==, + } + engines: { node: '>=18' } + + tinyglobby@0.2.15: + resolution: + { + integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==, + } + engines: { node: '>=12.0.0' } + + tinyrainbow@3.0.3: + resolution: + { + integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==, + } + engines: { node: '>=14.0.0' } + + tldts-core@6.1.86: + resolution: + { + integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==, + } + + tldts@6.1.86: + resolution: + { + integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==, + } + hasBin: true + + tmpl@1.0.5: + resolution: + { + integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==, + } + + to-regex-range@5.0.1: + resolution: + { + integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, + } + engines: { node: '>=8.0' } + + toidentifier@1.0.1: + resolution: + { + integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==, + } + engines: { node: '>=0.6' } + + totalist@3.0.1: + resolution: + { + integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==, + } + engines: { node: '>=6' } + + tough-cookie@5.1.2: + resolution: + { + integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==, + } + engines: { node: '>=16' } + + tr46@5.1.1: + resolution: + { + integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==, + } + engines: { node: '>=18' } + + trim-lines@3.0.1: + resolution: + { + integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==, + } + + ts-api-utils@2.4.0: + resolution: + { + integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==, + } + engines: { node: '>=18.12' } + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.8.1: + resolution: + { + integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, + } + + type-check@0.4.0: + resolution: + { + integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, + } + engines: { node: '>= 0.8.0' } + + type-detect@4.0.8: + resolution: + { + integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==, + } + engines: { node: '>=4' } + + type-fest@0.7.1: + resolution: + { + integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==, + } + engines: { node: '>=8' } + + type-level-regexp@0.1.17: + resolution: + { + integrity: sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg==, + } + + typescript-eslint@8.56.1: + resolution: + { + integrity: sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.8.2: + resolution: + { + integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==, + } + engines: { node: '>=14.17' } + hasBin: true + + ufo@1.6.3: + resolution: + { + integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==, + } + + unconfig-core@7.5.0: + resolution: + { + integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==, + } + + unconfig@7.5.0: + resolution: + { + integrity: sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA==, + } + + undici-types@7.18.2: + resolution: + { + integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==, + } + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: + { + integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==, + } + engines: { node: '>=4' } + + unicode-match-property-ecmascript@2.0.0: + resolution: + { + integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==, + } + engines: { node: '>=4' } + + unicode-match-property-value-ecmascript@2.2.1: + resolution: + { + integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==, + } + engines: { node: '>=4' } + + unicode-property-aliases-ecmascript@2.2.0: + resolution: + { + integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==, + } + engines: { node: '>=4' } + + unist-util-is@6.0.1: + resolution: + { + integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==, + } + + unist-util-position@5.0.0: + resolution: + { + integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==, + } + + unist-util-stringify-position@4.0.0: + resolution: + { + integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==, + } + + unist-util-visit-parents@6.0.2: + resolution: + { + integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==, + } + + unist-util-visit@5.1.0: + resolution: + { + integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==, + } + + universalify@0.1.2: + resolution: + { + integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==, + } + engines: { node: '>= 4.0.0' } + + universalify@2.0.1: + resolution: + { + integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==, + } + engines: { node: '>= 10.0.0' } + + unocss@66.6.4: + resolution: + { + integrity: sha512-W7BfUX2pw4cvUB8kq5CZro/TWM0LcXTjgwwmjowK5B/KVs0Sgc8vTaCr5wuyqNcDLLGAe/9oNPGsVgVBJQN6kQ==, + } + engines: { node: '>=14' } + peerDependencies: + '@unocss/astro': 66.6.4 + '@unocss/postcss': 66.6.4 + '@unocss/webpack': 66.6.4 + peerDependenciesMeta: + '@unocss/astro': + optional: true + '@unocss/postcss': + optional: true + '@unocss/webpack': + optional: true + + unpipe@1.0.0: + resolution: + { + integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==, + } + engines: { node: '>= 0.8' } + + unplugin-utils@0.3.1: + resolution: + { + integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==, + } + engines: { node: '>=20.19.0' } + + unplugin@2.3.11: + resolution: + { + integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==, + } + engines: { node: '>=18.12.0' } + + update-browserslist-db@1.2.3: + resolution: + { + integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==, + } + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: + { + integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, + } + + use-latest-callback@0.2.6: + resolution: + { + integrity: sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==, + } + peerDependencies: + react: '>=16.8' + + use-sync-external-store@1.6.0: + resolution: + { + integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==, + } + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + utils-merge@1.0.1: + resolution: + { + integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==, + } + engines: { node: '>= 0.4.0' } + + vfile-message@4.0.3: + resolution: + { + integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==, + } + + vfile@6.0.3: + resolution: + { + integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==, + } + + vite-plugin-babel@1.5.1: + resolution: + { + integrity: sha512-TBBBsAYYg7V5yR+xPeZYHwritMmc2QvZrZKFSS26it7ZQ0Y8ESKwJJm2KUUcmHQZU/owvA4yKk4ibPVrfhlwJw==, + } + peerDependencies: + '@babel/core': ^7.0.0 + vite: ^2.7.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + vite-plugin-dts@4.5.4: + resolution: + { + integrity: sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==, + } + peerDependencies: + typescript: '*' + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + vite@5.4.21: + resolution: + { + integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==, + } + engines: { node: ^18.0.0 || >=20.0.0 } + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vite@7.3.1: + resolution: + { + integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitepress@1.6.4: + resolution: + { + integrity: sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==, + } + hasBin: true + peerDependencies: + markdown-it-mathjax3: ^4 + postcss: ^8 + peerDependenciesMeta: + markdown-it-mathjax3: + optional: true + postcss: + optional: true + + vitest@4.0.18: + resolution: + { + integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==, + } + engines: { node: ^20.0.0 || ^22.0.0 || >=24.0.0 } + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.18 + '@vitest/browser-preview': 4.0.18 + '@vitest/browser-webdriverio': 4.0.18 + '@vitest/ui': 4.0.18 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + vlq@1.0.1: + resolution: + { + integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==, + } + + vscode-uri@3.1.0: + resolution: + { + integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==, + } + + vue@3.5.29: + resolution: + { + integrity: sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==, + } + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: + { + integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==, + } + engines: { node: '>=18' } + + walker@1.0.8: + resolution: + { + integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==, + } + + warn-once@0.1.1: + resolution: + { + integrity: sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==, + } + + webidl-conversions@7.0.0: + resolution: + { + integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==, + } + engines: { node: '>=12' } + + webpack-virtual-modules@0.6.2: + resolution: + { + integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==, + } + + whatwg-encoding@3.1.1: + resolution: + { + integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==, + } + engines: { node: '>=18' } + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + + whatwg-fetch@3.6.20: + resolution: + { + integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==, + } + + whatwg-mimetype@3.0.0: + resolution: + { + integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==, + } + engines: { node: '>=12' } + + whatwg-mimetype@4.0.0: + resolution: + { + integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==, + } + engines: { node: '>=18' } + + whatwg-url@14.2.0: + resolution: + { + integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==, + } + engines: { node: '>=18' } + + which@2.0.2: + resolution: + { + integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, + } + engines: { node: '>= 8' } + hasBin: true + + why-is-node-running@2.3.0: + resolution: + { + integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==, + } + engines: { node: '>=8' } + hasBin: true + + word-wrap@1.2.5: + resolution: + { + integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==, + } + engines: { node: '>=0.10.0' } + + wrap-ansi@7.0.0: + resolution: + { + integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==, + } + engines: { node: '>=10' } + + wrappy@1.0.2: + resolution: + { + integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, + } + + write-file-atomic@4.0.2: + resolution: + { + integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==, + } + engines: { node: ^12.13.0 || ^14.15.0 || >=16.0.0 } + + ws@7.5.10: + resolution: + { + integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==, + } + engines: { node: '>=8.3.0' } + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.19.0: + resolution: + { + integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==, + } + engines: { node: '>=10.0.0' } + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: + { + integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==, + } + engines: { node: '>=18' } + + xmlchars@2.2.0: + resolution: + { + integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==, + } + + y18n@5.0.8: + resolution: + { + integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==, + } + engines: { node: '>=10' } + + yallist@3.1.1: + resolution: + { + integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, + } + + yallist@4.0.0: + resolution: + { + integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==, + } + + yaml@2.8.2: + resolution: + { + integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==, + } + engines: { node: '>= 14.6' } + hasBin: true + + yargs-parser@21.1.1: + resolution: + { + integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==, + } + engines: { node: '>=12' } + + yargs@17.7.2: + resolution: + { + integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==, + } + engines: { node: '>=12' } + + yocto-queue@0.1.0: + resolution: + { + integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, + } + engines: { node: '>=10' } + + zod@4.3.6: + resolution: + { + integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==, + } + + zwitch@2.0.4: + resolution: + { + integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==, + } + +snapshots: + '@algolia/abtesting@1.15.1': + dependencies: + '@algolia/client-common': 5.49.1 + '@algolia/requester-browser-xhr': 5.49.1 + '@algolia/requester-fetch': 5.49.1 + '@algolia/requester-node-http': 5.49.1 + + '@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.49.1)(algoliasearch@5.49.1)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.49.1)(algoliasearch@5.49.1)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.49.1)(algoliasearch@5.49.1) + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + - search-insights + + '@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.49.1)(algoliasearch@5.49.1)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.49.1)(algoliasearch@5.49.1) + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + + '@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.49.1)(algoliasearch@5.49.1)': + dependencies: + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.49.1)(algoliasearch@5.49.1) + '@algolia/client-search': 5.49.1 + algoliasearch: 5.49.1 + + '@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.49.1)(algoliasearch@5.49.1)': + dependencies: + '@algolia/client-search': 5.49.1 + algoliasearch: 5.49.1 + + '@algolia/client-abtesting@5.49.1': + dependencies: + '@algolia/client-common': 5.49.1 + '@algolia/requester-browser-xhr': 5.49.1 + '@algolia/requester-fetch': 5.49.1 + '@algolia/requester-node-http': 5.49.1 + + '@algolia/client-analytics@5.49.1': + dependencies: + '@algolia/client-common': 5.49.1 + '@algolia/requester-browser-xhr': 5.49.1 + '@algolia/requester-fetch': 5.49.1 + '@algolia/requester-node-http': 5.49.1 + + '@algolia/client-common@5.49.1': {} + + '@algolia/client-insights@5.49.1': + dependencies: + '@algolia/client-common': 5.49.1 + '@algolia/requester-browser-xhr': 5.49.1 + '@algolia/requester-fetch': 5.49.1 + '@algolia/requester-node-http': 5.49.1 + + '@algolia/client-personalization@5.49.1': + dependencies: + '@algolia/client-common': 5.49.1 + '@algolia/requester-browser-xhr': 5.49.1 + '@algolia/requester-fetch': 5.49.1 + '@algolia/requester-node-http': 5.49.1 + + '@algolia/client-query-suggestions@5.49.1': + dependencies: + '@algolia/client-common': 5.49.1 + '@algolia/requester-browser-xhr': 5.49.1 + '@algolia/requester-fetch': 5.49.1 + '@algolia/requester-node-http': 5.49.1 + + '@algolia/client-search@5.49.1': + dependencies: + '@algolia/client-common': 5.49.1 + '@algolia/requester-browser-xhr': 5.49.1 + '@algolia/requester-fetch': 5.49.1 + '@algolia/requester-node-http': 5.49.1 + + '@algolia/ingestion@1.49.1': + dependencies: + '@algolia/client-common': 5.49.1 + '@algolia/requester-browser-xhr': 5.49.1 + '@algolia/requester-fetch': 5.49.1 + '@algolia/requester-node-http': 5.49.1 + + '@algolia/monitoring@1.49.1': + dependencies: + '@algolia/client-common': 5.49.1 + '@algolia/requester-browser-xhr': 5.49.1 + '@algolia/requester-fetch': 5.49.1 + '@algolia/requester-node-http': 5.49.1 + + '@algolia/recommend@5.49.1': + dependencies: + '@algolia/client-common': 5.49.1 + '@algolia/requester-browser-xhr': 5.49.1 + '@algolia/requester-fetch': 5.49.1 + '@algolia/requester-node-http': 5.49.1 + + '@algolia/requester-browser-xhr@5.49.1': + dependencies: + '@algolia/client-common': 5.49.1 + + '@algolia/requester-fetch@5.49.1': + dependencies: + '@algolia/client-common': 5.49.1 + + '@algolia/requester-node-http@5.49.1': + dependencies: + '@algolia/client-common': 5.49.1 + + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.6.0 + tinyexec: 1.0.2 + + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + optional: true + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + regexpu-core: 6.4.0 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + debug: 4.4.3 + lodash.debounce: 4.0.8 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-wrap-function': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helper-wrap-function@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/template': 7.28.6 + + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/preset-env@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.15(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.14.0(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.6(@babel/core@7.29.0) + core-js-compat: 3.48.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/types': 7.29.0 + esutils: 2.0.3 + + '@babel/preset-react@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.28.6': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@changesets/apply-release-plan@7.1.0': + dependencies: + '@changesets/config': 3.1.3 + '@changesets/get-version-range-type': 0.4.0 + '@changesets/git': 3.0.4 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.8.8 + resolve-from: 5.0.0 + semver: 7.7.4 + + '@changesets/assemble-release-plan@6.0.9': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + semver: 7.7.4 + + '@changesets/changelog-git@0.2.1': + dependencies: + '@changesets/types': 6.1.0 + + '@changesets/cli@2.30.0(@types/node@25.3.3)': + dependencies: + '@changesets/apply-release-plan': 7.1.0 + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/changelog-git': 0.2.1 + '@changesets/config': 3.1.3 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/get-release-plan': 4.0.15 + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.7 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@changesets/write': 0.4.0 + '@inquirer/external-editor': 1.0.3(@types/node@25.3.3) + '@manypkg/get-packages': 1.1.3 + ansi-colors: 4.1.3 + enquirer: 2.4.1 + fs-extra: 7.0.1 + mri: 1.2.0 + package-manager-detector: 0.2.11 + picocolors: 1.1.1 + resolve-from: 5.0.0 + semver: 7.7.4 + spawndamnit: 3.0.1 + term-size: 2.2.1 + transitivePeerDependencies: + - '@types/node' + + '@changesets/config@3.1.3': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/logger': 0.1.1 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.8 + + '@changesets/errors@0.2.0': + dependencies: + extendable-error: 0.1.7 + + '@changesets/get-dependents-graph@2.1.3': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + picocolors: 1.1.1 + semver: 7.7.4 + + '@changesets/get-release-plan@4.0.15': + dependencies: + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/config': 3.1.3 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.7 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/get-version-range-type@0.4.0': {} + + '@changesets/git@3.0.4': + dependencies: + '@changesets/errors': 0.2.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.8 + spawndamnit: 3.0.1 + + '@changesets/logger@0.1.1': + dependencies: + picocolors: 1.1.1 + + '@changesets/parse@0.4.3': + dependencies: + '@changesets/types': 6.1.0 + js-yaml: 4.1.1 + + '@changesets/pre@2.0.2': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + + '@changesets/read@0.6.7': + dependencies: + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/parse': 0.4.3 + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + p-filter: 2.1.0 + picocolors: 1.1.1 + + '@changesets/should-skip-package@0.1.2': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/types@4.1.0': {} + + '@changesets/types@6.1.0': {} + + '@changesets/write@0.4.0': + dependencies: + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + human-id: 4.1.3 + prettier: 2.8.8 + + '@csstools/color-helpers@5.1.0': + optional: true + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + optional: true + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + optional: true + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + optional: true + + '@csstools/css-tokenizer@3.0.4': + optional: true + + '@docsearch/css@3.8.2': {} + + '@docsearch/js@3.8.2(@algolia/client-search@5.49.1)(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(search-insights@2.17.3)': + dependencies: + '@docsearch/react': 3.8.2(@algolia/client-search@5.49.1)(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(search-insights@2.17.3) + preact: 10.28.4 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/react' + - react + - react-dom + - search-insights + + '@docsearch/react@3.8.2(@algolia/client-search@5.49.1)(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.49.1)(algoliasearch@5.49.1)(search-insights@2.17.3) + '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.49.1)(algoliasearch@5.49.1) + '@docsearch/css': 3.8.2 + algoliasearch: 5.49.1 + optionalDependencies: + '@types/react': 19.2.14 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + + '@egjs/hammerjs@2.0.17': + dependencies: + '@types/hammerjs': 2.0.46 + + '@emnapi/core@1.8.1': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.27.3': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.27.3': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.27.3': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.27.3': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.27.3': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.27.3': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.27.3': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.27.3': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.27.3': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.27.3': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.27.3': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.27.3': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.27.3': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.27.3': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.27.3': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.27.3': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.27.3': + optional: true + + '@esbuild/netbsd-arm64@0.27.3': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.27.3': + optional: true + + '@esbuild/openbsd-arm64@0.27.3': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.27.3': + optional: true + + '@esbuild/openharmony-arm64@0.27.3': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.27.3': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.27.3': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.27.3': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.27.3': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@10.0.2(jiti@2.6.1))': + dependencies: + eslint: 10.0.2(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.2': + dependencies: + '@eslint/object-schema': 3.0.2 + debug: 4.4.3 + minimatch: 10.2.4 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.5.2': + dependencies: + '@eslint/core': 1.1.0 + + '@eslint/core@1.1.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.4': + dependencies: + ajv: 6.14.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@10.0.1(eslint@10.0.2(jiti@2.6.1))': + optionalDependencies: + eslint: 10.0.2(jiti@2.6.1) + + '@eslint/object-schema@3.0.2': {} + + '@eslint/plugin-kit@0.6.0': + dependencies: + '@eslint/core': 1.1.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@iconify-json/logos@1.2.10': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify-json/simple-icons@1.2.72': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.1.0': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + mlly: 1.8.0 + + '@inquirer/external-editor@1.0.3(@types/node@25.3.3)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 25.3.3 + + '@isaacs/ttlcache@1.4.1': {} + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.2 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jest/create-cache-key-function@29.7.0': + dependencies: + '@jest/types': 29.6.3 + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 25.3.3 + jest-mock: 29.7.0 + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 25.3.3 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.10 + + '@jest/transform@29.7.0': + dependencies: + '@babel/core': 7.29.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.31 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 25.3.3 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@manypkg/find-root@1.1.0': + dependencies: + '@babel/runtime': 7.28.6 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + + '@manypkg/get-packages@1.1.3': + dependencies: + '@babel/runtime': 7.28.6 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + + '@microsoft/api-extractor-model@7.33.4(@types/node@25.3.3)': + dependencies: + '@microsoft/tsdoc': 0.16.0 + '@microsoft/tsdoc-config': 0.18.1 + '@rushstack/node-core-library': 5.20.3(@types/node@25.3.3) + transitivePeerDependencies: + - '@types/node' + + '@microsoft/api-extractor@7.57.6(@types/node@25.3.3)': + dependencies: + '@microsoft/api-extractor-model': 7.33.4(@types/node@25.3.3) + '@microsoft/tsdoc': 0.16.0 + '@microsoft/tsdoc-config': 0.18.1 + '@rushstack/node-core-library': 5.20.3(@types/node@25.3.3) + '@rushstack/rig-package': 0.7.2 + '@rushstack/terminal': 0.22.3(@types/node@25.3.3) + '@rushstack/ts-command-line': 5.3.3(@types/node@25.3.3) + diff: 8.0.3 + lodash: 4.17.23 + minimatch: 10.2.1 + resolve: 1.22.11 + semver: 7.5.4 + source-map: 0.6.1 + typescript: 5.8.2 + transitivePeerDependencies: + - '@types/node' + + '@microsoft/tsdoc-config@0.18.1': + dependencies: + '@microsoft/tsdoc': 0.16.0 + ajv: 8.18.0 + jju: 1.4.0 + resolve: 1.22.11 + + '@microsoft/tsdoc@0.16.0': {} + + '@napi-rs/wasm-runtime@1.1.1': + dependencies: + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@oxc-parser/binding-android-arm-eabi@0.115.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.115.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.115.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.115.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.115.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.115.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.115.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.115.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.115.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.115.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.115.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.115.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.115.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.115.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.115.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.115.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.115.0': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.115.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.115.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.115.0': + optional: true + + '@oxc-project/types@0.115.0': {} + + '@pkgr/core@0.2.9': {} + + '@polka/url@1.0.0-next.29': {} + + '@quansync/fs@1.0.0': + dependencies: + quansync: 1.0.0 + + '@react-native/assets-registry@0.84.1': {} + + '@react-native/codegen@0.84.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + hermes-parser: 0.32.0 + invariant: 2.2.4 + nullthrows: 1.1.1 + tinyglobby: 0.2.15 + yargs: 17.7.2 + + '@react-native/community-cli-plugin@0.84.1': + dependencies: + '@react-native/dev-middleware': 0.84.1 + debug: 4.4.3 + invariant: 2.2.4 + metro: 0.83.5 + metro-config: 0.83.5 + metro-core: 0.83.5 + semver: 7.7.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@react-native/debugger-frontend@0.84.1': {} + + '@react-native/debugger-shell@0.84.1': + dependencies: + cross-spawn: 7.0.6 + debug: 4.4.3 + fb-dotslash: 0.5.8 + transitivePeerDependencies: + - supports-color + + '@react-native/dev-middleware@0.84.1': + dependencies: + '@isaacs/ttlcache': 1.4.1 + '@react-native/debugger-frontend': 0.84.1 + '@react-native/debugger-shell': 0.84.1 + chrome-launcher: 0.15.2 + chromium-edge-launcher: 0.2.0 + connect: 3.7.0 + debug: 4.4.3 + invariant: 2.2.4 + nullthrows: 1.1.1 + open: 7.4.2 + serve-static: 1.16.3 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@react-native/gradle-plugin@0.84.1': {} + + '@react-native/js-polyfills@0.84.1': {} + + '@react-native/normalize-colors@0.84.1': {} + + '@react-native/virtualized-lists@0.84.1(@types/react@19.2.14)(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)': + dependencies: + invariant: 2.2.4 + nullthrows: 1.1.1 + react: 19.2.4 + react-native: 0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + + '@react-navigation/bottom-tabs@7.15.5(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)': + dependencies: + '@react-navigation/elements': 2.9.10(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + '@react-navigation/native': 7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + color: 4.2.3 + react: 19.2.4 + react-native: 0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4) + react-native-safe-area-context: 5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + react-native-screens: 4.24.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + sf-symbols-typescript: 2.2.0 + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + + '@react-navigation/core@7.16.1(react@19.2.4)': + dependencies: + '@react-navigation/routers': 7.5.3 + escape-string-regexp: 4.0.0 + fast-deep-equal: 3.1.3 + nanoid: 3.3.11 + query-string: 7.1.3 + react: 19.2.4 + react-is: 19.2.4 + use-latest-callback: 0.2.6(react@19.2.4) + use-sync-external-store: 1.6.0(react@19.2.4) + + '@react-navigation/elements@2.9.10(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)': + dependencies: + '@react-navigation/native': 7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + color: 4.2.3 + react: 19.2.4 + react-native: 0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4) + react-native-safe-area-context: 5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + use-latest-callback: 0.2.6(react@19.2.4) + use-sync-external-store: 1.6.0(react@19.2.4) + + '@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)': + dependencies: + '@react-navigation/core': 7.16.1(react@19.2.4) + escape-string-regexp: 4.0.0 + fast-deep-equal: 3.1.3 + nanoid: 3.3.11 + react: 19.2.4 + react-native: 0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4) + use-latest-callback: 0.2.6(react@19.2.4) + + '@react-navigation/routers@7.5.3': + dependencies: + nanoid: 3.3.11 + + '@react-navigation/stack@7.8.4(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-gesture-handler@2.30.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)': + dependencies: + '@react-navigation/elements': 2.9.10(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + '@react-navigation/native': 7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + color: 4.2.3 + react: 19.2.4 + react-native: 0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4) + react-native-gesture-handler: 2.30.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + react-native-safe-area-context: 5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + react-native-screens: 4.24.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + use-latest-callback: 0.2.6(react@19.2.4) + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/pluginutils@5.3.0(rollup@4.59.0)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.59.0 + + '@rollup/rollup-android-arm-eabi@4.59.0': + optional: true + + '@rollup/rollup-android-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-x64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.59.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.59.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.59.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.59.0': + optional: true + + '@rushstack/node-core-library@5.20.3(@types/node@25.3.3)': + dependencies: + ajv: 8.18.0 + ajv-draft-04: 1.0.0(ajv@8.18.0) + ajv-formats: 3.0.1(ajv@8.18.0) + fs-extra: 11.3.4 + import-lazy: 4.0.0 + jju: 1.4.0 + resolve: 1.22.11 + semver: 7.5.4 + optionalDependencies: + '@types/node': 25.3.3 + + '@rushstack/problem-matcher@0.2.1(@types/node@25.3.3)': + optionalDependencies: + '@types/node': 25.3.3 + + '@rushstack/rig-package@0.7.2': + dependencies: + resolve: 1.22.11 + strip-json-comments: 3.1.1 + + '@rushstack/terminal@0.22.3(@types/node@25.3.3)': + dependencies: + '@rushstack/node-core-library': 5.20.3(@types/node@25.3.3) + '@rushstack/problem-matcher': 0.2.1(@types/node@25.3.3) + supports-color: 8.1.1 + optionalDependencies: + '@types/node': 25.3.3 + + '@rushstack/ts-command-line@5.3.3(@types/node@25.3.3)': + dependencies: + '@rushstack/terminal': 0.22.3(@types/node@25.3.3) + '@types/argparse': 1.0.38 + argparse: 1.0.10 + string-argv: 0.3.2 + transitivePeerDependencies: + - '@types/node' + + '@shikijs/core@2.5.0': + dependencies: + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 3.1.1 + + '@shikijs/engine-oniguruma@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + + '@shikijs/themes@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + + '@shikijs/transformers@2.5.0': + dependencies: + '@shikijs/core': 2.5.0 + '@shikijs/types': 2.5.0 + + '@shikijs/types@2.5.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@sinclair/typebox@0.27.10': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@standard-schema/spec@1.1.0': {} + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.28.6 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.28.6 + '@testing-library/dom': 10.4.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/argparse@1.0.38': {} + + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.8': {} + + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 25.3.3 + + '@types/hammerjs@2.0.46': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/json-schema@7.0.15': {} + + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdurl@2.0.0': {} + + '@types/node@12.20.55': {} + + '@types/node@25.3.3': + dependencies: + undici-types: 7.18.2 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + + '@types/stack-utils@2.0.3': {} + + '@types/unist@3.0.3': {} + + '@types/web-bluetooth@0.0.21': {} + + '@types/whatwg-mimetype@3.0.2': {} + + '@types/ws@8.18.1': + dependencies: + '@types/node': 25.3.3 + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.8.2))(eslint@10.0.2(jiti@2.6.1))(typescript@5.8.2)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.8.2) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/type-utils': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.8.2) + '@typescript-eslint/utils': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.8.2) + '@typescript-eslint/visitor-keys': 8.56.1 + eslint: 10.0.2(jiti@2.6.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.8.2) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.8.2)': + dependencies: + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.8.2) + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + eslint: 10.0.2(jiti@2.6.1) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.56.1(typescript@5.8.2)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.8.2) + '@typescript-eslint/types': 8.56.1 + debug: 4.4.3 + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + + '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.8.2)': + dependencies: + typescript: 5.8.2 + + '@typescript-eslint/type-utils@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.8.2)': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.8.2) + '@typescript-eslint/utils': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.8.2) + debug: 4.4.3 + eslint: 10.0.2(jiti@2.6.1) + ts-api-utils: 2.4.0(typescript@5.8.2) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.56.1': {} + + '@typescript-eslint/typescript-estree@8.56.1(typescript@5.8.2)': + dependencies: + '@typescript-eslint/project-service': 8.56.1(typescript@5.8.2) + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.8.2) + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + minimatch: 10.2.4 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.8.2) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.8.2)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.8.2) + eslint: 10.0.2(jiti@2.6.1) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + eslint-visitor-keys: 5.0.1 + + '@ungap/structured-clone@1.3.0': {} + + '@unocss/cli@66.6.4': + dependencies: + '@jridgewell/remapping': 2.3.5 + '@unocss/config': 66.6.4 + '@unocss/core': 66.6.4 + '@unocss/preset-wind3': 66.6.4 + '@unocss/preset-wind4': 66.6.4 + '@unocss/transformer-directives': 66.6.4 + cac: 6.7.14 + chokidar: 5.0.0 + colorette: 2.0.20 + consola: 3.4.2 + magic-string: 0.30.21 + pathe: 2.0.3 + perfect-debounce: 2.1.0 + tinyglobby: 0.2.15 + unplugin-utils: 0.3.1 + + '@unocss/config@66.6.4': + dependencies: + '@unocss/core': 66.6.4 + colorette: 2.0.20 + consola: 3.4.2 + unconfig: 7.5.0 + + '@unocss/core@66.6.4': {} + + '@unocss/extractor-arbitrary-variants@66.6.4': + dependencies: + '@unocss/core': 66.6.4 + + '@unocss/inspector@66.6.4': + dependencies: + '@unocss/core': 66.6.4 + '@unocss/rule-utils': 66.6.4 + colorette: 2.0.20 + gzip-size: 6.0.0 + sirv: 3.0.2 + + '@unocss/preset-attributify@66.6.4': + dependencies: + '@unocss/core': 66.6.4 + + '@unocss/preset-icons@66.6.4': + dependencies: + '@iconify/utils': 3.1.0 + '@unocss/core': 66.6.4 + ofetch: 1.5.1 + + '@unocss/preset-mini@66.6.4': + dependencies: + '@unocss/core': 66.6.4 + '@unocss/extractor-arbitrary-variants': 66.6.4 + '@unocss/rule-utils': 66.6.4 + + '@unocss/preset-tagify@66.6.4': + dependencies: + '@unocss/core': 66.6.4 + + '@unocss/preset-typography@66.6.4': + dependencies: + '@unocss/core': 66.6.4 + '@unocss/rule-utils': 66.6.4 + + '@unocss/preset-uno@66.6.4': + dependencies: + '@unocss/core': 66.6.4 + '@unocss/preset-wind3': 66.6.4 + + '@unocss/preset-web-fonts@66.6.4': + dependencies: + '@unocss/core': 66.6.4 + ofetch: 1.5.1 + + '@unocss/preset-wind3@66.6.4': + dependencies: + '@unocss/core': 66.6.4 + '@unocss/preset-mini': 66.6.4 + '@unocss/rule-utils': 66.6.4 + + '@unocss/preset-wind4@66.6.4': + dependencies: + '@unocss/core': 66.6.4 + '@unocss/extractor-arbitrary-variants': 66.6.4 + '@unocss/rule-utils': 66.6.4 + + '@unocss/preset-wind@66.6.4': + dependencies: + '@unocss/core': 66.6.4 + '@unocss/preset-wind3': 66.6.4 + + '@unocss/rule-utils@66.6.4': + dependencies: + '@unocss/core': 66.6.4 + magic-string: 0.30.21 + + '@unocss/transformer-attributify-jsx@66.6.4': + dependencies: + '@unocss/core': 66.6.4 + oxc-parser: 0.115.0 + oxc-walker: 0.7.0(oxc-parser@0.115.0) + + '@unocss/transformer-compile-class@66.6.4': + dependencies: + '@unocss/core': 66.6.4 + + '@unocss/transformer-directives@66.6.4': + dependencies: + '@unocss/core': 66.6.4 + '@unocss/rule-utils': 66.6.4 + css-tree: 3.1.0 + + '@unocss/transformer-variant-group@66.6.4': + dependencies: + '@unocss/core': 66.6.4 + + '@unocss/vite@66.6.4(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))': + dependencies: + '@jridgewell/remapping': 2.3.5 + '@unocss/config': 66.6.4 + '@unocss/core': 66.6.4 + '@unocss/inspector': 66.6.4 + chokidar: 5.0.0 + magic-string: 0.30.21 + pathe: 2.0.3 + tinyglobby: 0.2.15 + unplugin-utils: 0.3.1 + vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2) + + '@vitejs/plugin-react@4.7.0(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@25.3.3)(terser@5.46.0))(vue@3.5.29(typescript@5.8.2))': + dependencies: + vite: 5.4.21(@types/node@25.3.3)(terser@5.46.0) + vue: 3.5.29(typescript@5.8.2) + + '@vitest/expect@4.0.18': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + chai: 6.2.2 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))': + dependencies: + '@vitest/spy': 4.0.18 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2) + + '@vitest/pretty-format@4.0.18': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.18': + dependencies: + '@vitest/utils': 4.0.18 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.0.18': {} + + '@vitest/utils@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + tinyrainbow: 3.0.3 + + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/compiler-core@3.5.29': + dependencies: + '@babel/parser': 7.29.0 + '@vue/shared': 3.5.29 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.29': + dependencies: + '@vue/compiler-core': 3.5.29 + '@vue/shared': 3.5.29 + + '@vue/compiler-sfc@3.5.29': + dependencies: + '@babel/parser': 7.29.0 + '@vue/compiler-core': 3.5.29 + '@vue/compiler-dom': 3.5.29 + '@vue/compiler-ssr': 3.5.29 + '@vue/shared': 3.5.29 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.8 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.29': + dependencies: + '@vue/compiler-dom': 3.5.29 + '@vue/shared': 3.5.29 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/devtools-api@7.7.9': + dependencies: + '@vue/devtools-kit': 7.7.9 + + '@vue/devtools-kit@7.7.9': + dependencies: + '@vue/devtools-shared': 7.7.9 + birpc: 2.9.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.6 + + '@vue/devtools-shared@7.7.9': + dependencies: + rfdc: 1.4.1 + + '@vue/language-core@2.2.0(typescript@5.8.2)': + dependencies: + '@volar/language-core': 2.4.28 + '@vue/compiler-dom': 3.5.29 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.29 + alien-signals: 0.4.14 + minimatch: 9.0.9 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.8.2 + + '@vue/reactivity@3.5.29': + dependencies: + '@vue/shared': 3.5.29 + + '@vue/runtime-core@3.5.29': + dependencies: + '@vue/reactivity': 3.5.29 + '@vue/shared': 3.5.29 + + '@vue/runtime-dom@3.5.29': + dependencies: + '@vue/reactivity': 3.5.29 + '@vue/runtime-core': 3.5.29 + '@vue/shared': 3.5.29 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.29(vue@3.5.29(typescript@5.8.2))': + dependencies: + '@vue/compiler-ssr': 3.5.29 + '@vue/shared': 3.5.29 + vue: 3.5.29(typescript@5.8.2) + + '@vue/shared@3.5.29': {} + + '@vueuse/core@12.8.2(typescript@5.8.2)': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 12.8.2 + '@vueuse/shared': 12.8.2(typescript@5.8.2) + vue: 3.5.29(typescript@5.8.2) + transitivePeerDependencies: + - typescript + + '@vueuse/integrations@12.8.2(focus-trap@7.8.0)(typescript@5.8.2)': + dependencies: + '@vueuse/core': 12.8.2(typescript@5.8.2) + '@vueuse/shared': 12.8.2(typescript@5.8.2) + vue: 3.5.29(typescript@5.8.2) + optionalDependencies: + focus-trap: 7.8.0 + transitivePeerDependencies: + - typescript + + '@vueuse/metadata@12.8.2': {} + + '@vueuse/shared@12.8.2(typescript@5.8.2)': + dependencies: + vue: 3.5.29(typescript@5.8.2) + transitivePeerDependencies: + - typescript + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + agent-base@7.1.4: {} + + ajv-draft-04@1.0.0(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv-formats@3.0.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + algoliasearch@5.49.1: + dependencies: + '@algolia/abtesting': 1.15.1 + '@algolia/client-abtesting': 5.49.1 + '@algolia/client-analytics': 5.49.1 + '@algolia/client-common': 5.49.1 + '@algolia/client-insights': 5.49.1 + '@algolia/client-personalization': 5.49.1 + '@algolia/client-query-suggestions': 5.49.1 + '@algolia/client-search': 5.49.1 + '@algolia/ingestion': 1.49.1 + '@algolia/monitoring': 1.49.1 + '@algolia/recommend': 5.49.1 + '@algolia/requester-browser-xhr': 5.49.1 + '@algolia/requester-fetch': 5.49.1 + '@algolia/requester-node-http': 5.49.1 + + alien-signals@0.4.14: {} + + anser@1.4.10: {} + + ansi-colors@4.1.3: {} + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + array-union@2.1.0: {} + + asap@2.0.6: {} + + assertion-error@2.0.1: {} + + asynckit@0.4.0: + optional: true + + babel-jest@29.7.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.29.0) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@6.1.1: + dependencies: + '@babel/helper-plugin-utils': 7.28.6 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 + + babel-plugin-polyfill-corejs2@0.4.15(@babel/core@7.29.0): + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.0) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.14.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.0) + core-js-compat: 3.48.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.6(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + babel-plugin-syntax-hermes-parser@0.32.0: + dependencies: + hermes-parser: 0.32.0 + + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) + + babel-preset-jest@29.6.3(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.10.0: {} + + better-path-resolve@1.0.0: + dependencies: + is-windows: 1.0.2 + + birpc@2.9.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + brace-expansion@5.0.4: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001776 + electron-to-chromium: 1.5.307 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + optional: true + + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001776: {} + + ccount@2.0.1: {} + + chai@6.2.2: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + chardet@2.1.1: {} + + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + chrome-launcher@0.15.2: + dependencies: + '@types/node': 25.3.3 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2 + transitivePeerDependencies: + - supports-color + + chromium-edge-launcher@0.2.0: + dependencies: + '@types/node': 25.3.3 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2 + mkdirp: 1.0.4 + rimraf: 3.0.2 + transitivePeerDependencies: + - supports-color + + ci-info@2.0.0: {} + + ci-info@3.9.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + + colorette@2.0.20: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + optional: true + + comma-separated-tokens@2.0.3: {} + + commander@12.1.0: {} + + commander@2.20.3: {} + + compare-versions@6.1.1: {} + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + confbox@0.2.4: {} + + connect@3.7.0: + dependencies: + debug: 2.6.9 + finalhandler: 1.1.2 + parseurl: 1.3.3 + utils-merge: 1.0.1 + transitivePeerDependencies: + - supports-color + + consola@3.4.2: {} + + convert-source-map@2.0.0: {} + + copy-anything@4.0.5: + dependencies: + is-what: 5.5.0 + + core-js-compat@3.48.0: + dependencies: + browserslist: 4.28.1 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + cssstyle@4.6.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + optional: true + + csstype@3.2.3: {} + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + optional: true + + de-indent@1.0.2: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: + optional: true + + decode-uri-component@0.2.2: {} + + decode-uri-component@0.4.1: {} + + deep-is@0.1.4: {} + + defu@6.1.4: {} + + delayed-stream@1.0.0: + optional: true + + depd@2.0.0: {} + + dequal@2.0.3: {} + + destr@2.0.5: {} + + destroy@1.2.0: {} + + detect-indent@6.1.0: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + diff@8.0.3: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dom-accessibility-api@0.5.16: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + optional: true + + duplexer@0.1.2: {} + + ee-first@1.1.1: {} + + effector-action@1.2.2(effector@23.4.4)(patronum@2.3.0(effector@23.4.4)): + dependencies: + effector: 23.4.4 + patronum: 2.3.0(effector@23.4.4) + + effector-react@23.3.0(effector@23.4.4)(react@19.2.4): + dependencies: + effector: 23.4.4 + react: 19.2.4 + use-sync-external-store: 1.6.0(react@19.2.4) + + effector@23.4.4: {} + + electron-to-chromium@1.5.307: {} + + emoji-regex-xs@1.0.0: {} + + emoji-regex@8.0.0: {} + + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + + entities@6.0.1: + optional: true + + entities@7.0.1: {} + + error-stack-parser@2.1.4: + dependencies: + stackframe: 1.3.4 + + es-define-property@1.0.1: + optional: true + + es-errors@1.3.0: + optional: true + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + optional: true + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + optional: true + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.27.3: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@10.1.8(eslint@10.0.2(jiti@2.6.1)): + dependencies: + eslint: 10.0.2(jiti@2.6.1) + + eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@10.0.2(jiti@2.6.1)))(eslint@10.0.2(jiti@2.6.1))(prettier@3.8.1): + dependencies: + eslint: 10.0.2(jiti@2.6.1) + prettier: 3.8.1 + prettier-linter-helpers: 1.0.1 + synckit: 0.11.12 + optionalDependencies: + eslint-config-prettier: 10.1.8(eslint@10.0.2(jiti@2.6.1)) + + eslint-scope@9.1.1: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.0.2(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.2 + '@eslint/config-helpers': 0.5.2 + '@eslint/core': 1.1.0 + '@eslint/plugin-kit': 0.6.0 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.14.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.1 + eslint-visitor-keys: 5.0.1 + espree: 11.1.1 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.4 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + + espree@11.1.1: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esprima@4.0.1: {} + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + etag@1.8.1: {} + + event-target-shim@5.0.1: {} + + expect-type@1.3.0: {} + + exponential-backoff@3.1.3: {} + + exsolve@1.0.8: {} + + extendable-error@0.1.7: {} + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-uri@3.1.0: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fb-dotslash@0.5.8: {} + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + filter-obj@1.1.0: {} + + filter-obj@5.1.0: {} + + finalhandler@1.1.2: + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.5.0 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.4 + keyv: 4.5.4 + + flatted@3.3.4: {} + + flow-enums-runtime@0.0.6: {} + + focus-trap@7.8.0: + dependencies: + tabbable: 6.4.0 + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + optional: true + + fresh@0.5.2: {} + + fs-extra@11.3.4: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-extra@7.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + optional: true + + get-package-type@0.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + optional: true + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@14.0.0: {} + + globals@17.4.0: {} + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: + optional: true + + graceful-fs@4.2.11: {} + + gzip-size@6.0.0: + dependencies: + duplexer: 0.1.2 + + happy-dom@20.8.3: + dependencies: + '@types/node': 25.3.3 + '@types/whatwg-mimetype': 3.0.2 + '@types/ws': 8.18.1 + entities: 7.0.1 + whatwg-mimetype: 3.0.0 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + has-flag@4.0.0: {} + + has-symbols@1.1.0: + optional: true + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + optional: true + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + he@1.2.0: {} + + hermes-compiler@250829098.0.9: {} + + hermes-estree@0.32.0: {} + + hermes-estree@0.33.3: {} + + hermes-parser@0.32.0: + dependencies: + hermes-estree: 0.32.0 + + hermes-parser@0.33.3: + dependencies: + hermes-estree: 0.33.3 + + history@5.3.0: + dependencies: + '@babel/runtime': 7.28.6 + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + + hookable@5.5.3: {} + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + optional: true + + html-void-elements@3.0.0: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + human-id@4.1.3: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + optional: true + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + image-size@1.2.1: + dependencies: + queue: 6.0.2 + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-lazy@4.0.0: {} + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + invariant@2.2.4: + dependencies: + loose-envify: 1.4.0 + + is-arrayish@0.3.4: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-docker@2.2.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-potential-custom-element-name@1.0.1: + optional: true + + is-subdir@1.2.0: + dependencies: + better-path-resolve: 1.0.0 + + is-what@5.5.0: {} + + is-windows@1.0.2: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 25.3.3 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 25.3.3 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.29.0 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 25.3.3 + jest-util: 29.7.0 + + jest-regex-util@29.6.3: {} + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 25.3.3 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-worker@29.7.0: + dependencies: + '@types/node': 25.3.3 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jiti@2.6.1: {} + + jju@1.4.0: {} + + js-tokens@4.0.0: {} + + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsc-safe-url@0.2.4: {} + + jsdom@25.0.1: + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + form-data: 4.0.5 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.23 + parse5: 7.3.0 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.19.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + optional: true + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kolorist@1.8.0: {} + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lighthouse-logger@1.4.2: + dependencies: + debug: 2.6.9 + marky: 1.3.0 + transitivePeerDependencies: + - supports-color + + local-pkg@1.1.2: + dependencies: + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.debounce@4.0.8: {} + + lodash.startcase@4.4.0: {} + + lodash.throttle@4.1.1: {} + + lodash@4.17.23: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.4.3: + optional: true + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + lz-string@1.5.0: {} + + magic-regexp@0.10.0: + dependencies: + estree-walker: 3.0.3 + magic-string: 0.30.21 + mlly: 1.8.0 + regexp-tree: 0.1.27 + type-level-regexp: 0.1.17 + ufo: 1.6.3 + unplugin: 2.3.11 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + mark.js@8.11.1: {} + + marky@1.3.0: {} + + math-intrinsics@1.1.0: + optional: true + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdn-data@2.12.2: {} + + memoize-one@5.2.1: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + metro-babel-transformer@0.83.5: + dependencies: + '@babel/core': 7.29.0 + flow-enums-runtime: 0.0.6 + hermes-parser: 0.33.3 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-cache-key@0.83.5: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-cache@0.83.5: + dependencies: + exponential-backoff: 3.1.3 + flow-enums-runtime: 0.0.6 + https-proxy-agent: 7.0.6 + metro-core: 0.83.5 + transitivePeerDependencies: + - supports-color + + metro-config@0.83.5: + dependencies: + connect: 3.7.0 + flow-enums-runtime: 0.0.6 + jest-validate: 29.7.0 + metro: 0.83.5 + metro-cache: 0.83.5 + metro-core: 0.83.5 + metro-runtime: 0.83.5 + yaml: 2.8.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro-core@0.83.5: + dependencies: + flow-enums-runtime: 0.0.6 + lodash.throttle: 4.1.1 + metro-resolver: 0.83.5 + + metro-file-map@0.83.5: + dependencies: + debug: 4.4.3 + fb-watchman: 2.0.2 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + invariant: 2.2.4 + jest-worker: 29.7.0 + micromatch: 4.0.8 + nullthrows: 1.1.1 + walker: 1.0.8 + transitivePeerDependencies: + - supports-color + + metro-minify-terser@0.83.5: + dependencies: + flow-enums-runtime: 0.0.6 + terser: 5.46.0 + + metro-resolver@0.83.5: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-runtime@0.83.5: + dependencies: + '@babel/runtime': 7.28.6 + flow-enums-runtime: 0.0.6 + + metro-source-map@0.83.5: + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-symbolicate: 0.83.5 + nullthrows: 1.1.1 + ob1: 0.83.5 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-symbolicate@0.83.5: + dependencies: + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-source-map: 0.83.5 + nullthrows: 1.1.1 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-transform-plugins@0.83.5: + dependencies: + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + flow-enums-runtime: 0.0.6 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-transform-worker@0.83.5: + dependencies: + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + flow-enums-runtime: 0.0.6 + metro: 0.83.5 + metro-babel-transformer: 0.83.5 + metro-cache: 0.83.5 + metro-cache-key: 0.83.5 + metro-minify-terser: 0.83.5 + metro-source-map: 0.83.5 + metro-transform-plugins: 0.83.5 + nullthrows: 1.1.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro@0.83.5: + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + accepts: 2.0.0 + chalk: 4.1.2 + ci-info: 2.0.0 + connect: 3.7.0 + debug: 4.4.3 + error-stack-parser: 2.1.4 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + hermes-parser: 0.33.3 + image-size: 1.2.1 + invariant: 2.2.4 + jest-worker: 29.7.0 + jsc-safe-url: 0.2.4 + lodash.throttle: 4.1.1 + metro-babel-transformer: 0.83.5 + metro-cache: 0.83.5 + metro-cache-key: 0.83.5 + metro-config: 0.83.5 + metro-core: 0.83.5 + metro-file-map: 0.83.5 + metro-resolver: 0.83.5 + metro-runtime: 0.83.5 + metro-source-map: 0.83.5 + metro-symbolicate: 0.83.5 + metro-transform-plugins: 0.83.5 + metro-transform-worker: 0.83.5 + mime-types: 3.0.2 + nullthrows: 1.1.1 + serialize-error: 2.1.0 + source-map: 0.5.7 + throat: 5.0.0 + ws: 7.5.10 + yargs: 17.7.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-encode@2.0.1: {} + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: + optional: true + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + optional: true + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mime@1.6.0: {} + + minimatch@10.2.1: + dependencies: + brace-expansion: 5.0.4 + + minimatch@10.2.4: + dependencies: + brace-expansion: 5.0.4 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.0.2 + + minisearch@7.2.0: {} + + mitt@3.0.1: {} + + mkdirp@1.0.4: {} + + mlly@1.8.0: + dependencies: + acorn: 8.16.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + + mri@1.2.0: {} + + mrmime@2.0.1: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + negotiator@1.0.0: {} + + node-fetch-native@1.6.7: {} + + node-int64@0.4.0: {} + + node-releases@2.0.27: {} + + normalize-path@3.0.0: {} + + nullthrows@1.1.1: {} + + nwsapi@2.2.23: + optional: true + + ob1@0.83.5: + dependencies: + flow-enums-runtime: 0.0.6 + + obug@2.1.1: {} + + ofetch@1.5.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.3 + + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + oniguruma-to-es@3.1.1: + dependencies: + emoji-regex-xs: 1.0.0 + regex: 6.1.0 + regex-recursion: 6.0.2 + + open@7.4.2: + dependencies: + is-docker: 2.2.1 + is-wsl: 2.2.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + outdent@0.5.0: {} + + oxc-parser@0.115.0: + dependencies: + '@oxc-project/types': 0.115.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.115.0 + '@oxc-parser/binding-android-arm64': 0.115.0 + '@oxc-parser/binding-darwin-arm64': 0.115.0 + '@oxc-parser/binding-darwin-x64': 0.115.0 + '@oxc-parser/binding-freebsd-x64': 0.115.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.115.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.115.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.115.0 + '@oxc-parser/binding-linux-arm64-musl': 0.115.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.115.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.115.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.115.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.115.0 + '@oxc-parser/binding-linux-x64-gnu': 0.115.0 + '@oxc-parser/binding-linux-x64-musl': 0.115.0 + '@oxc-parser/binding-openharmony-arm64': 0.115.0 + '@oxc-parser/binding-wasm32-wasi': 0.115.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.115.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.115.0 + '@oxc-parser/binding-win32-x64-msvc': 0.115.0 + + oxc-walker@0.7.0(oxc-parser@0.115.0): + dependencies: + magic-regexp: 0.10.0 + oxc-parser: 0.115.0 + + p-filter@2.1.0: + dependencies: + p-map: 2.1.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map@2.1.0: {} + + p-try@2.2.0: {} + + package-manager-detector@0.2.11: + dependencies: + quansync: 0.2.11 + + package-manager-detector@1.6.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + optional: true + + parseurl@1.3.3: {} + + path-browserify@1.0.1: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + patronum@2.3.0(effector@23.4.4): + dependencies: + effector: 23.4.4 + + perfect-debounce@1.0.0: {} + + perfect-debounce@2.1.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pify@4.0.1: {} + + pirates@4.0.7: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.4 + exsolve: 1.0.8 + pathe: 2.0.3 + + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + preact@10.28.4: {} + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.1: + dependencies: + fast-diff: 1.3.0 + + prettier@2.8.8: {} + + prettier@3.8.1: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + promise@8.3.0: + dependencies: + asap: 2.0.6 + + property-information@7.1.0: {} + + punycode@2.3.1: {} + + quansync@0.2.11: {} + + quansync@1.0.0: {} + + query-string@7.1.3: + dependencies: + decode-uri-component: 0.2.2 + filter-obj: 1.1.0 + split-on-first: 1.1.0 + strict-uri-encode: 2.0.0 + + query-string@9.3.1: + dependencies: + decode-uri-component: 0.4.1 + filter-obj: 5.1.0 + split-on-first: 3.0.0 + + queue-microtask@1.2.3: {} + + queue@6.0.2: + dependencies: + inherits: 2.0.4 + + range-parser@1.2.1: {} + + react-devtools-core@6.1.5: + dependencies: + shell-quote: 1.8.3 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react-freeze@1.0.4(react@19.2.4): + dependencies: + react: 19.2.4 + + react-is@16.13.1: {} + + react-is@17.0.2: {} + + react-is@18.3.1: {} + + react-is@19.2.4: {} + + react-native-gesture-handler@2.30.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4): + dependencies: + '@egjs/hammerjs': 2.0.17 + hoist-non-react-statics: 3.3.2 + invariant: 2.2.4 + react: 19.2.4 + react-native: 0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4) + + react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4): + dependencies: + react: 19.2.4 + react-native: 0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4) + + react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4): + dependencies: + react: 19.2.4 + react-freeze: 1.0.4(react@19.2.4) + react-native: 0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4) + warn-once: 0.1.1 + + react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4): + dependencies: + '@jest/create-cache-key-function': 29.7.0 + '@react-native/assets-registry': 0.84.1 + '@react-native/codegen': 0.84.1(@babel/core@7.29.0) + '@react-native/community-cli-plugin': 0.84.1 + '@react-native/gradle-plugin': 0.84.1 + '@react-native/js-polyfills': 0.84.1 + '@react-native/normalize-colors': 0.84.1 + '@react-native/virtualized-lists': 0.84.1(@types/react@19.2.14)(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + abort-controller: 3.0.0 + anser: 1.4.10 + ansi-regex: 5.0.1 + babel-jest: 29.7.0(@babel/core@7.29.0) + babel-plugin-syntax-hermes-parser: 0.32.0 + base64-js: 1.5.1 + commander: 12.1.0 + flow-enums-runtime: 0.0.6 + hermes-compiler: 250829098.0.9 + invariant: 2.2.4 + jest-environment-node: 29.7.0 + memoize-one: 5.2.1 + metro-runtime: 0.83.5 + metro-source-map: 0.83.5 + nullthrows: 1.1.1 + pretty-format: 29.7.0 + promise: 8.3.0 + react: 19.2.4 + react-devtools-core: 6.1.5 + react-refresh: 0.14.2 + regenerator-runtime: 0.13.11 + scheduler: 0.27.0 + semver: 7.7.4 + stacktrace-parser: 0.1.11 + tinyglobby: 0.2.15 + whatwg-fetch: 3.6.20 + ws: 7.5.10 + yargs: 17.7.2 + optionalDependencies: + '@types/react': 19.2.14 + transitivePeerDependencies: + - '@babel/core' + - '@react-native-community/cli' + - '@react-native/metro-config' + - bufferutil + - supports-color + - utf-8-validate + + react-refresh@0.14.2: {} + + react-refresh@0.17.0: {} + + react@19.2.4: {} + + read-yaml-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.2 + pify: 4.0.1 + strip-bom: 3.0.0 + + readdirp@5.0.0: {} + + regenerate-unicode-properties@10.2.2: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regenerator-runtime@0.13.11: {} + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + regexp-tree@0.1.27: {} + + regexpu-core@6.4.0: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.2 + regjsgen: 0.8.0 + regjsparser: 0.13.0 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.1 + + regjsgen@0.8.0: {} + + regjsparser@0.13.0: + dependencies: + jsesc: 3.1.0 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup@4.59.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.59.0 + '@rollup/rollup-android-arm64': 4.59.0 + '@rollup/rollup-darwin-arm64': 4.59.0 + '@rollup/rollup-darwin-x64': 4.59.0 + '@rollup/rollup-freebsd-arm64': 4.59.0 + '@rollup/rollup-freebsd-x64': 4.59.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 + '@rollup/rollup-linux-arm-musleabihf': 4.59.0 + '@rollup/rollup-linux-arm64-gnu': 4.59.0 + '@rollup/rollup-linux-arm64-musl': 4.59.0 + '@rollup/rollup-linux-loong64-gnu': 4.59.0 + '@rollup/rollup-linux-loong64-musl': 4.59.0 + '@rollup/rollup-linux-ppc64-gnu': 4.59.0 + '@rollup/rollup-linux-ppc64-musl': 4.59.0 + '@rollup/rollup-linux-riscv64-gnu': 4.59.0 + '@rollup/rollup-linux-riscv64-musl': 4.59.0 + '@rollup/rollup-linux-s390x-gnu': 4.59.0 + '@rollup/rollup-linux-x64-gnu': 4.59.0 + '@rollup/rollup-linux-x64-musl': 4.59.0 + '@rollup/rollup-openbsd-x64': 4.59.0 + '@rollup/rollup-openharmony-arm64': 4.59.0 + '@rollup/rollup-win32-arm64-msvc': 4.59.0 + '@rollup/rollup-win32-ia32-msvc': 4.59.0 + '@rollup/rollup-win32-x64-gnu': 4.59.0 + '@rollup/rollup-win32-x64-msvc': 4.59.0 + fsevents: 2.3.3 + + rrweb-cssom@0.7.1: + optional: true + + rrweb-cssom@0.8.0: + optional: true + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + optional: true + + scheduler@0.27.0: {} + + search-insights@2.17.3: {} + + semver@6.3.1: {} + + semver@7.5.4: + dependencies: + lru-cache: 6.0.0 + + semver@7.7.4: {} + + send@0.19.2: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serialize-error@2.1.0: {} + + serve-static@1.16.3: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + sf-symbols-typescript@2.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.8.3: {} + + shiki@2.5.0: + dependencies: + '@shikijs/core': 2.5.0 + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/langs': 2.5.0 + '@shikijs/themes': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + siginfo@2.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + slash@3.0.0: {} + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.5.7: {} + + source-map@0.6.1: {} + + space-separated-tokens@2.0.2: {} + + spawndamnit@3.0.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + speakingurl@14.0.1: {} + + split-on-first@1.1.0: {} + + split-on-first@3.0.0: {} + + sprintf-js@1.0.3: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + stackback@0.0.2: {} + + stackframe@1.3.4: {} + + stacktrace-parser@0.1.11: + dependencies: + type-fest: 0.7.1 + + statuses@1.5.0: {} + + statuses@2.0.2: {} + + std-env@3.10.0: {} + + strict-uri-encode@2.0.0: {} + + string-argv@0.3.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-bom@3.0.0: {} + + strip-json-comments@3.1.1: {} + + superjson@2.2.6: + dependencies: + copy-anything: 4.0.5 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + symbol-tree@3.2.4: + optional: true + + synckit@0.11.12: + dependencies: + '@pkgr/core': 0.2.9 + + tabbable@6.4.0: {} + + term-size@2.2.1: {} + + terser@5.46.0: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.16.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.5 + + throat@5.0.0: {} + + tinybench@2.9.0: {} + + tinyexec@1.0.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyrainbow@3.0.3: {} + + tldts-core@6.1.86: + optional: true + + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + optional: true + + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + totalist@3.0.1: {} + + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + optional: true + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + optional: true + + trim-lines@3.0.1: {} + + ts-api-utils@2.4.0(typescript@5.8.2): + dependencies: + typescript: 5.8.2 + + tslib@2.8.1: + optional: true + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.0.8: {} + + type-fest@0.7.1: {} + + type-level-regexp@0.1.17: {} + + typescript-eslint@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.8.2): + dependencies: + '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.8.2))(eslint@10.0.2(jiti@2.6.1))(typescript@5.8.2) + '@typescript-eslint/parser': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.8.2) + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.8.2) + '@typescript-eslint/utils': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.8.2) + eslint: 10.0.2(jiti@2.6.1) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + typescript@5.8.2: {} + + ufo@1.6.3: {} + + unconfig-core@7.5.0: + dependencies: + '@quansync/fs': 1.0.0 + quansync: 1.0.0 + + unconfig@7.5.0: + dependencies: + '@quansync/fs': 1.0.0 + defu: 6.1.4 + jiti: 2.6.1 + quansync: 1.0.0 + unconfig-core: 7.5.0 + + undici-types@7.18.2: {} + + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.2.0 + + unicode-match-property-value-ecmascript@2.2.1: {} + + unicode-property-aliases-ecmascript@2.2.0: {} + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + universalify@0.1.2: {} + + universalify@2.0.1: {} + + unocss@66.6.4(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2)): + dependencies: + '@unocss/cli': 66.6.4 + '@unocss/core': 66.6.4 + '@unocss/preset-attributify': 66.6.4 + '@unocss/preset-icons': 66.6.4 + '@unocss/preset-mini': 66.6.4 + '@unocss/preset-tagify': 66.6.4 + '@unocss/preset-typography': 66.6.4 + '@unocss/preset-uno': 66.6.4 + '@unocss/preset-web-fonts': 66.6.4 + '@unocss/preset-wind': 66.6.4 + '@unocss/preset-wind3': 66.6.4 + '@unocss/preset-wind4': 66.6.4 + '@unocss/transformer-attributify-jsx': 66.6.4 + '@unocss/transformer-compile-class': 66.6.4 + '@unocss/transformer-directives': 66.6.4 + '@unocss/transformer-variant-group': 66.6.4 + '@unocss/vite': 66.6.4(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2)) + transitivePeerDependencies: + - vite + + unpipe@1.0.0: {} + + unplugin-utils@0.3.1: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.3 + + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.16.0 + picomatch: 4.0.3 + webpack-virtual-modules: 0.6.2 + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-latest-callback@0.2.6(react@19.2.4): + dependencies: + react: 19.2.4 + + use-sync-external-store@1.6.0(react@19.2.4): + dependencies: + react: 19.2.4 + + utils-merge@1.0.1: {} + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite-plugin-babel@1.5.1(@babel/core@7.29.0)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2)): + dependencies: + '@babel/core': 7.29.0 + vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2) + + vite-plugin-dts@4.5.4(@types/node@25.3.3)(rollup@4.59.0)(typescript@5.8.2)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2)): + dependencies: + '@microsoft/api-extractor': 7.57.6(@types/node@25.3.3) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) + '@volar/typescript': 2.4.28 + '@vue/language-core': 2.2.0(typescript@5.8.2) + compare-versions: 6.1.1 + debug: 4.4.3 + kolorist: 1.8.0 + local-pkg: 1.1.2 + magic-string: 0.30.21 + typescript: 5.8.2 + optionalDependencies: + vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2) + transitivePeerDependencies: + - '@types/node' + - rollup + - supports-color + + vite@5.4.21(@types/node@25.3.3)(terser@5.46.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.8 + rollup: 4.59.0 + optionalDependencies: + '@types/node': 25.3.3 + fsevents: 2.3.3 + terser: 5.46.0 + + vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2): + dependencies: + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.8 + rollup: 4.59.0 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 25.3.3 + fsevents: 2.3.3 + jiti: 2.6.1 + terser: 5.46.0 + yaml: 2.8.2 + + vitepress@1.6.4(@algolia/client-search@5.49.1)(@types/node@25.3.3)(@types/react@19.2.14)(postcss@8.5.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(search-insights@2.17.3)(terser@5.46.0)(typescript@5.8.2): + dependencies: + '@docsearch/css': 3.8.2 + '@docsearch/js': 3.8.2(@algolia/client-search@5.49.1)(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(search-insights@2.17.3) + '@iconify-json/simple-icons': 1.2.72 + '@shikijs/core': 2.5.0 + '@shikijs/transformers': 2.5.0 + '@shikijs/types': 2.5.0 + '@types/markdown-it': 14.1.2 + '@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@25.3.3)(terser@5.46.0))(vue@3.5.29(typescript@5.8.2)) + '@vue/devtools-api': 7.7.9 + '@vue/shared': 3.5.29 + '@vueuse/core': 12.8.2(typescript@5.8.2) + '@vueuse/integrations': 12.8.2(focus-trap@7.8.0)(typescript@5.8.2) + focus-trap: 7.8.0 + mark.js: 8.11.1 + minisearch: 7.2.0 + shiki: 2.5.0 + vite: 5.4.21(@types/node@25.3.3)(terser@5.46.0) + vue: 3.5.29(typescript@5.8.2) + optionalDependencies: + postcss: 8.5.8 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/node' + - '@types/react' + - async-validator + - axios + - change-case + - drauu + - fuse.js + - idb-keyval + - jwt-decode + - less + - lightningcss + - nprogress + - qrcode + - react + - react-dom + - sass + - sass-embedded + - search-insights + - sortablejs + - stylus + - sugarss + - terser + - typescript + - universal-cookie + + vitest@4.0.18(@types/node@25.3.3)(happy-dom@20.8.3)(jiti@2.6.1)(jsdom@25.0.1)(terser@5.46.0)(yaml@2.8.2): + dependencies: + '@vitest/expect': 4.0.18 + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2)) + '@vitest/pretty-format': 4.0.18 + '@vitest/runner': 4.0.18 + '@vitest/snapshot': 4.0.18 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.3.3 + happy-dom: 20.8.3 + jsdom: 25.0.1 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + + vlq@1.0.1: {} + + vscode-uri@3.1.0: {} + + vue@3.5.29(typescript@5.8.2): + dependencies: + '@vue/compiler-dom': 3.5.29 + '@vue/compiler-sfc': 3.5.29 + '@vue/runtime-dom': 3.5.29 + '@vue/server-renderer': 3.5.29(vue@3.5.29(typescript@5.8.2)) + '@vue/shared': 3.5.29 + optionalDependencies: + typescript: 5.8.2 + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + optional: true + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + warn-once@0.1.1: {} + + webidl-conversions@7.0.0: + optional: true + + webpack-virtual-modules@0.6.2: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + optional: true + + whatwg-fetch@3.6.20: {} + + whatwg-mimetype@3.0.0: {} + + whatwg-mimetype@4.0.0: + optional: true + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + optional: true + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + ws@7.5.10: {} + + ws@8.19.0: {} + + xml-name-validator@5.0.0: + optional: true + + xmlchars@2.2.0: + optional: true + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yallist@4.0.0: {} + + yaml@2.8.2: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + zod@4.3.6: {} + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..4879875 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +packages: + - 'packages/*' + - 'docs' +allowBuilds: + esbuild: true diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1549df7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,109 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "jsx": "react-jsx" /* Specify what JSX code is generated. */, + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "esnext" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "bundler" /* Specify how TypeScript looks up a file from a given module specifier. */, + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + "noImplicitThis": true /* Enable error reporting when 'this' is given the type 'any'. */, + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +}