diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..6c8122d
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,57 @@
+name: Test
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+ workflow_dispatch:
+
+jobs:
+ test:
+ name: Lint, typecheck, build & test
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ cache: yarn
+
+ - name: Install dependencies
+ run: yarn install --frozen-lockfile
+
+ # Lint only. The repo is not fully Biome-formatted yet, so `biome ci`
+ # (which also enforces formatting/import order) is not used here.
+ - name: Lint
+ run: npx biome lint .
+
+ - name: Type check
+ run: npx tsc --noEmit
+
+ - name: Build
+ run: yarn build
+
+ - name: Cache Playwright browsers
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/ms-playwright
+ key: playwright-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
+
+ - name: Install Playwright Chromium
+ run: npx playwright install --with-deps chromium
+
+ - name: Run tests with coverage
+ run: yarn coverage
+
+ - name: Upload coverage report
+ if: ${{ !cancelled() }}
+ uses: actions/upload-artifact@v4
+ with:
+ name: coverage-report
+ path: coverage/
+ retention-days: 14
diff --git a/.gitignore b/.gitignore
index c71fa1c..e078bc0 100755
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
# testing
/coverage
+**/__screenshots__
# build
/dist
@@ -21,8 +22,12 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
-# storybook
-storybook-static
-
# cache
.eslintcache
+.gstack/
+
+# demo app
+/demo/node_modules
+/demo/dist
+/demo/.vercel
+.vercel
diff --git a/.npmignore b/.npmignore
index 8b415d9..2e63a12 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,5 +1,7 @@
/*
!/dist
-/dist/**/stories
!/src
-/src/**/stories
+/src/**/*.test.*
+/src/**/__screenshots__
+/src/jest-dom.d.ts
+/demo
diff --git a/.storybook/main.js b/.storybook/main.js
deleted file mode 100644
index 748ccb6..0000000
--- a/.storybook/main.js
+++ /dev/null
@@ -1,30 +0,0 @@
-module.exports = {
- stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
- addons: ['@storybook/addon-links'],
- framework: {
- name: '@storybook/react-vite',
- options: {},
- },
- viteFinal: async (config) => {
- config.assetsInclude = config.assetsInclude || [];
- config.assetsInclude.push('**/*.md');
-
- // Configure esbuild to handle TypeScript properly
- config.esbuild = {
- target: 'es2020',
- loader: 'tsx',
- include: /src\/.*\.[jt]sx?$/,
- };
-
- config.optimizeDeps = config.optimizeDeps || {};
- config.optimizeDeps.esbuildOptions = {
- target: 'es2020',
- loader: {
- '.ts': 'tsx',
- '.tsx': 'tsx',
- },
- };
-
- return config;
- },
-};
diff --git a/.storybook/preview.js b/.storybook/preview.js
deleted file mode 100644
index b285aac..0000000
--- a/.storybook/preview.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export const parameters = {
- actions: { argTypesRegex: '^on[A-Z].*' },
-};
diff --git a/README.md b/README.md
index 7bcc399..95ef358 100755
--- a/README.md
+++ b/README.md
@@ -5,7 +5,15 @@
A React cron editor built with [material ui](https://material-ui.com/)
-For **demo** and **usage** clone repo and run `yarn && yarn dev`.
+For a **live demo**, run the demo app in [`demo/`](./demo):
+
+```bash
+yarn demo:install # one-time: install the demo app's deps
+yarn demo # http://localhost:5173
+```
+
+The demo is a small [TanStack Router](https://tanstack.com/router) + Vite SPA that
+imports the component straight from `src/`, so it always reflects the working tree.

@@ -103,6 +111,72 @@ We are welcoming translation contributions from the community.
/>
```
+## Testing
+
+Tests run on [Vitest](https://vitest.dev/) and are split into two projects:
+
+- **`unit`** — pure-logic tests that run in Node (cron validation in `utils.ts`,
+ the cron ⇄ field-atom derivations in `selector.ts`).
+- **`browser`** — component tests that render the real `` in a
+ headless Chromium via [Playwright](https://playwright.dev/). A real browser is
+ required: MUI's `Autocomplete` triggers an infinite update loop under jsdom.
+
+Tests live next to the code they cover:
+
+| File | Project | What it covers |
+| --- | --- | --- |
+| `src/utils.test.ts` | unit | every cron-part validator + helpers |
+| `src/selector.test.ts` | unit | all cron-part derivation atoms and the writer |
+| `src/scheduler.browser.test.tsx` | browser | end-to-end `` behaviour |
+
+### Commands
+
+```bash
+# Install the Chromium browser once (needed for the browser project)
+npx playwright install chromium
+
+yarn test # run the whole suite once (unit + browser)
+yarn test:unit # run only the Node unit project
+yarn test:browser # run only the Chromium browser project
+yarn test:watch # watch mode
+yarn coverage # run everything and emit a coverage report
+```
+
+`yarn coverage` uses the V8 provider and writes a report to `coverage/`
+(`text` summary in the console, plus `html` and `json-summary`).
+
+### Continuous integration
+
+`.github/workflows/test.yml` runs on pushes and pull requests to `main`. It
+lints (`biome lint`), type-checks (`tsc --noEmit`), builds, installs Chromium,
+runs `yarn coverage`, and uploads the coverage report as a build artifact.
+
+## Demo & deployment
+
+The [`demo/`](./demo) app is deployed to Vercel, which builds a fresh preview for
+**every branch and pull request** and comments the live URL on the PR.
+
+The build is driven by [`vercel.json`](./vercel.json) at the repo root:
+
+| Step | Command |
+| --- | --- |
+| Install | `yarn --cwd demo install` |
+| Build | `yarn --cwd demo build` |
+| Output | `demo/dist` |
+
+### One-time Vercel setup
+
+1. Create a Vercel project and link it to this GitHub repository
+ (Vercel dashboard → *Add New… → Project*, or `npx vercel link`).
+2. Leave the **Root Directory** as the repo root — `vercel.json` already points
+ the build at `demo/`. (The demo aliases the library from `../src`, so the whole
+ repo must be present at build time.)
+3. That's it. Vercel's Git integration produces a Preview Deployment for each push
+ to a branch / PR and a Production Deployment for `main`.
+
+No secrets or GitHub Actions workflow are required — preview URLs come from
+Vercel's native Git integration.
+
## Acknowledgement
This library was developed as a part of [Udaan](https://udaan.com/)'s Data
diff --git a/debug-storybook.log b/debug-storybook.log
deleted file mode 100644
index 6ec10e0..0000000
--- a/debug-storybook.log
+++ /dev/null
@@ -1,19 +0,0 @@
-[14:02:54.129] [INFO] Checking the health of your Storybook..
-[14:02:54.129] [DEBUG] Getting Storybook info...
-[14:02:54.130] [DEBUG] Loading main config...
-[14:02:54.256] [DEBUG] Getting stories paths...
-[14:02:54.269] [DEBUG] Getting package manager...
-[14:02:54.427] [DEBUG] Getting Storybook version...
-[14:02:54.686] [WARN] .storybook: 1 issue found
-[14:02:54.686] [INFO] You are currently using Storybook 9.1.10 but you have packages which are incompatible with it:
-
-- @storybook/addon-essentials@8.6.14 which depends on 8.6.14
- Repo: https://github.com/storybookjs/storybook/tree/next/code/addons/essentials
-
-Please consider updating your packages or contacting the maintainers for compatibility details.
-
-For more on Storybook 9 compatibility, see the linked GitHub issue:
-https://github.com/storybookjs/storybook/issues/30944
-[14:02:54.692] [INFO] Storybook doctor is complete!
-[14:02:54.692] [INFO] You can always recheck the health of your project(s) by running:
-npx storybook doctor
\ No newline at end of file
diff --git a/demo/README.md b/demo/README.md
new file mode 100644
index 0000000..b7f5706
--- /dev/null
+++ b/demo/README.md
@@ -0,0 +1,40 @@
+# material-ui-cron · demo
+
+A small [TanStack Router](https://tanstack.com/router) + [Vite](https://vitejs.dev/)
+single-page app that demos the `material-ui-cron` component.
+
+It imports the library **straight from the repo source** (`../src`) via a Vite
+alias, so the demo always reflects the current working tree — no build or
+`npm link` step required.
+
+## Run locally
+
+```bash
+# from the repo root
+yarn demo # installs nothing; runs `yarn --cwd demo dev`
+
+# or directly
+cd demo
+yarn install
+yarn dev # http://localhost:5173
+```
+
+Other scripts (run inside `demo/`):
+
+```bash
+yarn build # production build → demo/dist
+yarn preview # preview the production build
+yarn typecheck # tsc --noEmit
+```
+
+## Deployment (Vercel)
+
+Preview deployments for every branch and pull request are produced by Vercel's
+native Git integration. The build is configured by `vercel.json` at the repo
+root:
+
+- **Install:** `yarn --cwd demo install`
+- **Build:** `yarn --cwd demo build`
+- **Output:** `demo/dist`
+
+See the root `README.md` → *Demo & deployment* for the one-time Vercel setup.
diff --git a/demo/index.html b/demo/index.html
new file mode 100644
index 0000000..326d3da
--- /dev/null
+++ b/demo/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ material-ui-cron · demo
+
+
+
+
+
+
diff --git a/demo/package.json b/demo/package.json
new file mode 100644
index 0000000..66dd405
--- /dev/null
+++ b/demo/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "material-ui-cron-demo",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "description": "Live demo app for the material-ui-cron library (TanStack Router + Vite).",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@emotion/react": "^11.14.0",
+ "@emotion/styled": "^11.14.1",
+ "@mui/icons-material": "^7.3.4",
+ "@mui/material": "^7.3.4",
+ "@mui/system": "^7.3.3",
+ "@tanstack/react-router": "^1",
+ "cronstrue": "^3.3.0",
+ "jotai": "^2.15.0",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0"
+ },
+ "devDependencies": {
+ "@types/node": "^24.7.1",
+ "@types/react": "^19.2.2",
+ "@types/react-dom": "^19.2.1",
+ "@vitejs/plugin-react": "^5.0.4",
+ "typescript": "^5.9.3",
+ "vite": "^7.1.9"
+ }
+}
diff --git a/demo/src/main.tsx b/demo/src/main.tsx
new file mode 100644
index 0000000..1b49921
--- /dev/null
+++ b/demo/src/main.tsx
@@ -0,0 +1,28 @@
+import CssBaseline from '@mui/material/CssBaseline';
+import { ThemeProvider, createTheme } from '@mui/material/styles';
+import { RouterProvider } from '@tanstack/react-router';
+import { createRoot } from 'react-dom/client';
+import { router } from './router';
+
+const theme = createTheme({
+ palette: {
+ mode: 'light',
+ primary: { main: '#1565c0' },
+ },
+});
+
+const container = document.getElementById('root');
+if (!container) {
+ throw new Error('Root container #root not found');
+}
+
+// NOTE: React.StrictMode is intentionally omitted. The Scheduler keeps its cron
+// state in module-level Jotai atoms and resets them in an unmount effect, so
+// StrictMode's mount→unmount→remount cycle wipes the initial `cron` prop before
+// the first paint (the field would show `0 0 * * *` instead of the passed value).
+createRoot(container).render(
+
+
+
+ ,
+);
diff --git a/demo/src/pages/DemoPage.tsx b/demo/src/pages/DemoPage.tsx
new file mode 100644
index 0000000..81247f8
--- /dev/null
+++ b/demo/src/pages/DemoPage.tsx
@@ -0,0 +1,185 @@
+import GitHubIcon from '@mui/icons-material/GitHub';
+import {
+ Alert,
+ AppBar,
+ Box,
+ Chip,
+ Container,
+ FormControlLabel,
+ Link,
+ MenuItem,
+ Paper,
+ Stack,
+ Switch,
+ TextField,
+ Toolbar,
+ Typography,
+} from '@mui/material';
+import cronstrue from 'cronstrue';
+import 'cronstrue/locales/zh_CN';
+import { useState } from 'react';
+import Scheduler, { type definedLocales } from 'material-ui-cron';
+
+const PRESETS: { label: string; cron: string }[] = [
+ { label: 'Every minute', cron: '* * * * *' },
+ { label: 'Every 15 minutes', cron: '*/15 * * * *' },
+ { label: 'Hourly', cron: '0 * * * *' },
+ { label: 'Daily at 09:00', cron: '0 9 * * *' },
+ { label: 'Weekdays at 18:30', cron: '30 18 * * 1-5' },
+ { label: 'Monthly (1st)', cron: '0 0 1 * *' },
+ { label: 'Yearly (Jan 1)', cron: '0 0 1 1 *' },
+];
+
+const LOCALES: { value: definedLocales; label: string }[] = [
+ { value: 'en', label: 'English (en)' },
+ { value: 'zh_CN', label: '中文 (zh_CN)' },
+];
+
+function describe(cron: string, locale: definedLocales): string {
+ try {
+ return cronstrue.toString(cron, {
+ locale: locale === 'zh_CN' ? 'zh_CN' : 'en',
+ throwExceptionOnParseError: true,
+ });
+ } catch {
+ return '—';
+ }
+}
+
+export function DemoPage() {
+ const [cron, setCron] = useState('0 9 * * *');
+ const [cronError, setCronError] = useState('');
+ const [isAdmin, setIsAdmin] = useState(true);
+ const [locale, setLocale] = useState('en');
+
+ return (
+
+
+
+
+ material-ui-cron
+
+
+ npm
+
+
+
+
+
+
+
+
+
+
+ A React cron editor, built with Material UI
+
+
+ Edit the schedule below, try a preset, toggle admin mode, or switch the locale. The
+ component is rendered straight from this repository's src/.
+
+
+
+
+
+ setIsAdmin(e.target.checked)} />}
+ label="Admin (allow sub-daily frequency)"
+ />
+ setLocale(e.target.value as definedLocales)}
+ sx={{ minWidth: 180 }}
+ >
+ {LOCALES.map((l) => (
+
+ ))}
+
+
+
+
+
+
+
+
+ Presets
+
+
+ {PRESETS.map((p) => (
+ setCron(p.cron)}
+ color={cron === p.cron ? 'primary' : 'default'}
+ variant={cron === p.cron ? 'filled' : 'outlined'}
+ />
+ ))}
+
+
+
+
+
+ Current value
+
+
+ {cron}
+
+
+ {describe(cron, locale)}
+
+ {cronError ? (
+
+ {cronError}
+
+ ) : null}
+
+
+
+
+ Read the usage docs →
+
+
+
+
+ );
+}
diff --git a/demo/src/router.tsx b/demo/src/router.tsx
new file mode 100644
index 0000000..b2d6dac
--- /dev/null
+++ b/demo/src/router.tsx
@@ -0,0 +1,27 @@
+import {
+ Outlet,
+ createRootRoute,
+ createRoute,
+ createRouter,
+} from '@tanstack/react-router';
+import { DemoPage } from './pages/DemoPage';
+
+const rootRoute = createRootRoute({
+ component: () => ,
+});
+
+const indexRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/',
+ component: DemoPage,
+});
+
+const routeTree = rootRoute.addChildren([indexRoute]);
+
+export const router = createRouter({ routeTree });
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: typeof router;
+ }
+}
diff --git a/demo/tsconfig.json b/demo/tsconfig.json
new file mode 100644
index 0000000..49f269e
--- /dev/null
+++ b/demo/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "baseUrl": ".",
+ // For type-checking, resolve the library against its built public types
+ // (like a real consumer). The Vite *runtime* alias still points at ../src,
+ // so the dev server and build use live source — run `yarn build` in the repo
+ // root first if dist/ is missing. This keeps `yarn typecheck` from pulling
+ // the library internals in under the demo's stricter compiler flags.
+ "paths": {
+ "material-ui-cron": ["../dist/index.d.ts"]
+ }
+ },
+ "include": ["src", "vite.config.ts"]
+}
diff --git a/demo/vite.config.ts b/demo/vite.config.ts
new file mode 100644
index 0000000..484f208
--- /dev/null
+++ b/demo/vite.config.ts
@@ -0,0 +1,26 @@
+import { resolve } from 'node:path';
+import react from '@vitejs/plugin-react';
+import { defineConfig } from 'vite';
+
+// The demo consumes the library straight from source (`../src`) rather than the
+// published package. This keeps the demo always in sync with the working tree,
+// gives full HMR while editing the library, and needs no build step.
+export default defineConfig({
+ plugins: [react()],
+ resolve: {
+ alias: {
+ 'material-ui-cron': resolve(__dirname, '../src/index.ts'),
+ },
+ // The library source imports these; make sure a single copy is used so React
+ // hooks and Emotion's cache behave correctly.
+ dedupe: [
+ 'react',
+ 'react-dom',
+ 'jotai',
+ '@emotion/react',
+ '@emotion/styled',
+ '@mui/material',
+ '@mui/system',
+ ],
+ },
+});
diff --git a/demo/yarn.lock b/demo/yarn.lock
new file mode 100644
index 0000000..23c6475
--- /dev/null
+++ b/demo/yarn.lock
@@ -0,0 +1,1292 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.7.tgz#f2fbbfea87c44a21590ec515b778b2c26d8866e7"
+ integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.29.7"
+ js-tokens "^4.0.0"
+ picocolors "^1.1.1"
+
+"@babel/compat-data@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.7.tgz#6f0237f0f36d2e51c0570a636faed9d2d0efe629"
+ integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==
+
+"@babel/core@^7.29.0":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.7.tgz#80c10b17248082968b57a857b91640971f2070f7"
+ integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==
+ dependencies:
+ "@babel/code-frame" "^7.29.7"
+ "@babel/generator" "^7.29.7"
+ "@babel/helper-compilation-targets" "^7.29.7"
+ "@babel/helper-module-transforms" "^7.29.7"
+ "@babel/helpers" "^7.29.7"
+ "@babel/parser" "^7.29.7"
+ "@babel/template" "^7.29.7"
+ "@babel/traverse" "^7.29.7"
+ "@babel/types" "^7.29.7"
+ "@jridgewell/remapping" "^2.3.5"
+ convert-source-map "^2.0.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.3"
+ semver "^6.3.1"
+
+"@babel/generator@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.7.tgz#cca0b8827e6bcf3ba176788e7f3b180ad6db2fa3"
+ integrity sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==
+ dependencies:
+ "@babel/parser" "^7.29.7"
+ "@babel/types" "^7.29.7"
+ "@jridgewell/gen-mapping" "^0.3.12"
+ "@jridgewell/trace-mapping" "^0.3.28"
+ jsesc "^3.0.2"
+
+"@babel/helper-compilation-targets@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz#7a1def704302401c47f64fa85589e974ae217042"
+ integrity sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==
+ dependencies:
+ "@babel/compat-data" "^7.29.7"
+ "@babel/helper-validator-option" "^7.29.7"
+ browserslist "^4.24.0"
+ lru-cache "^5.1.1"
+ semver "^6.3.1"
+
+"@babel/helper-globals@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz#f04a96fbd8473241b1079243f5b3f03a3010ab7b"
+ integrity sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==
+
+"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz#ef25048a518e828d7393fac5882ddd73921d7396"
+ integrity sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==
+ dependencies:
+ "@babel/traverse" "^7.29.7"
+ "@babel/types" "^7.29.7"
+
+"@babel/helper-module-transforms@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz#b062747a5997ba138637201328bbff77960574ae"
+ integrity sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==
+ dependencies:
+ "@babel/helper-module-imports" "^7.29.7"
+ "@babel/helper-validator-identifier" "^7.29.7"
+ "@babel/traverse" "^7.29.7"
+
+"@babel/helper-plugin-utils@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz#c0a0766f1a13617d8a17407d7ab8f9d486225ea4"
+ integrity sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==
+
+"@babel/helper-string-parser@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz#7f0871d99824d23137d60f86fcf6130fd5a1b51f"
+ integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==
+
+"@babel/helper-validator-identifier@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2"
+ integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==
+
+"@babel/helper-validator-option@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz#cf315be940213b354eb4abcc0bd01ebe3f73bc2a"
+ integrity sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==
+
+"@babel/helpers@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.7.tgz#45abfde7548997e34376c3e69feb475cffb4a607"
+ integrity sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==
+ dependencies:
+ "@babel/template" "^7.29.7"
+ "@babel/types" "^7.29.7"
+
+"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334"
+ integrity sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==
+ dependencies:
+ "@babel/types" "^7.29.7"
+
+"@babel/plugin-transform-react-jsx-self@^7.27.1":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz#c24424527858220624fd59a5b1eab4fa413c803a"
+ integrity sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.29.7"
+
+"@babel/plugin-transform-react-jsx-source@^7.27.1":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz#5cf25a3689906b58e2f0a2f2b374789e6627b15f"
+ integrity sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.29.7"
+
+"@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.28.6", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.29.7.tgz#12022450c45a4da6d8d8287b18a4ff2ddb23f768"
+ integrity sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==
+
+"@babel/template@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.29.7.tgz#4d9d4004f645cdd304de958c725162784ecac700"
+ integrity sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==
+ dependencies:
+ "@babel/code-frame" "^7.29.7"
+ "@babel/parser" "^7.29.7"
+ "@babel/types" "^7.29.7"
+
+"@babel/traverse@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.7.tgz#c47b07a41b95da0907d026b5dd894d98de7d2f2d"
+ integrity sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==
+ dependencies:
+ "@babel/code-frame" "^7.29.7"
+ "@babel/generator" "^7.29.7"
+ "@babel/helper-globals" "^7.29.7"
+ "@babel/parser" "^7.29.7"
+ "@babel/template" "^7.29.7"
+ "@babel/types" "^7.29.7"
+ debug "^4.3.1"
+
+"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.28.2", "@babel/types@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92"
+ integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==
+ dependencies:
+ "@babel/helper-string-parser" "^7.29.7"
+ "@babel/helper-validator-identifier" "^7.29.7"
+
+"@emotion/babel-plugin@^11.13.5":
+ version "11.13.5"
+ resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz#eab8d65dbded74e0ecfd28dc218e75607c4e7bc0"
+ integrity sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==
+ dependencies:
+ "@babel/helper-module-imports" "^7.16.7"
+ "@babel/runtime" "^7.18.3"
+ "@emotion/hash" "^0.9.2"
+ "@emotion/memoize" "^0.9.0"
+ "@emotion/serialize" "^1.3.3"
+ babel-plugin-macros "^3.1.0"
+ convert-source-map "^1.5.0"
+ escape-string-regexp "^4.0.0"
+ find-root "^1.1.0"
+ source-map "^0.5.7"
+ stylis "4.2.0"
+
+"@emotion/cache@^11.14.0":
+ version "11.14.0"
+ resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.14.0.tgz#ee44b26986eeb93c8be82bb92f1f7a9b21b2ed76"
+ integrity sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==
+ dependencies:
+ "@emotion/memoize" "^0.9.0"
+ "@emotion/sheet" "^1.4.0"
+ "@emotion/utils" "^1.4.2"
+ "@emotion/weak-memoize" "^0.4.0"
+ stylis "4.2.0"
+
+"@emotion/hash@^0.9.2":
+ version "0.9.2"
+ resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.2.tgz#ff9221b9f58b4dfe61e619a7788734bd63f6898b"
+ integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==
+
+"@emotion/is-prop-valid@^1.3.0":
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz#e9ad47adff0b5c94c72db3669ce46de33edf28c0"
+ integrity sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==
+ dependencies:
+ "@emotion/memoize" "^0.9.0"
+
+"@emotion/memoize@^0.9.0":
+ version "0.9.0"
+ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102"
+ integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==
+
+"@emotion/react@^11.14.0":
+ version "11.14.0"
+ resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.14.0.tgz#cfaae35ebc67dd9ef4ea2e9acc6cd29e157dd05d"
+ integrity sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==
+ dependencies:
+ "@babel/runtime" "^7.18.3"
+ "@emotion/babel-plugin" "^11.13.5"
+ "@emotion/cache" "^11.14.0"
+ "@emotion/serialize" "^1.3.3"
+ "@emotion/use-insertion-effect-with-fallbacks" "^1.2.0"
+ "@emotion/utils" "^1.4.2"
+ "@emotion/weak-memoize" "^0.4.0"
+ hoist-non-react-statics "^3.3.1"
+
+"@emotion/serialize@^1.3.3":
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.3.3.tgz#d291531005f17d704d0463a032fe679f376509e8"
+ integrity sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==
+ dependencies:
+ "@emotion/hash" "^0.9.2"
+ "@emotion/memoize" "^0.9.0"
+ "@emotion/unitless" "^0.10.0"
+ "@emotion/utils" "^1.4.2"
+ csstype "^3.0.2"
+
+"@emotion/sheet@^1.4.0":
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c"
+ integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==
+
+"@emotion/styled@^11.14.1":
+ version "11.14.1"
+ resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.14.1.tgz#8c34bed2948e83e1980370305614c20955aacd1c"
+ integrity sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==
+ dependencies:
+ "@babel/runtime" "^7.18.3"
+ "@emotion/babel-plugin" "^11.13.5"
+ "@emotion/is-prop-valid" "^1.3.0"
+ "@emotion/serialize" "^1.3.3"
+ "@emotion/use-insertion-effect-with-fallbacks" "^1.2.0"
+ "@emotion/utils" "^1.4.2"
+
+"@emotion/unitless@^0.10.0":
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.10.0.tgz#2af2f7c7e5150f497bdabd848ce7b218a27cf745"
+ integrity sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==
+
+"@emotion/use-insertion-effect-with-fallbacks@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz#8a8cb77b590e09affb960f4ff1e9a89e532738bf"
+ integrity sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==
+
+"@emotion/utils@^1.4.2":
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.2.tgz#6df6c45881fcb1c412d6688a311a98b7f59c1b52"
+ integrity sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==
+
+"@emotion/weak-memoize@^0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6"
+ integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==
+
+"@esbuild/aix-ppc64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz#82b74f92aa78d720b714162939fb248c90addf53"
+ integrity sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==
+
+"@esbuild/android-arm64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz#f78cb8a3121fc205a53285adb24972db385d185d"
+ integrity sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==
+
+"@esbuild/android-arm@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.7.tgz#593e10a1450bbfcac6cb321f61f468453bac209d"
+ integrity sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==
+
+"@esbuild/android-x64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.7.tgz#453143d073326033d2d22caf9e48de4bae274b07"
+ integrity sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==
+
+"@esbuild/darwin-arm64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz#6f23000fb9b40b7e04b7d0606c0693bd0632f322"
+ integrity sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==
+
+"@esbuild/darwin-x64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz#27393dd18bb1263c663979c5f1576e00c2d024be"
+ integrity sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==
+
+"@esbuild/freebsd-arm64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz#22e4638fa502d1c0027077324c97640e3adf3a62"
+ integrity sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==
+
+"@esbuild/freebsd-x64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz#9224b8e4fea924ce2194e3efc3e9aebf822192d6"
+ integrity sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==
+
+"@esbuild/linux-arm64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz#4f5d1c27527d817b35684ae21419e57c2bda0966"
+ integrity sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==
+
+"@esbuild/linux-arm@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz#b9e9d070c8c1c0449cf12b20eac37d70a4595921"
+ integrity sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==
+
+"@esbuild/linux-ia32@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz#3f80fb696aa96051a94047f35c85b08b21c36f9e"
+ integrity sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==
+
+"@esbuild/linux-loong64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz#9be1f2c28210b13ebb4156221bba356fe1675205"
+ integrity sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==
+
+"@esbuild/linux-mips64el@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz#4ab5ee67a3dfcbcb5e8fd7883dae6e735b1163b8"
+ integrity sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==
+
+"@esbuild/linux-ppc64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz#dac78c689f6499459c4321e5c15032c12307e7ea"
+ integrity sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==
+
+"@esbuild/linux-riscv64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz#050f7d3b355c3a98308e935bc4d6325da91b0027"
+ integrity sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==
+
+"@esbuild/linux-s390x@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz#d61f715ce61d43fe5844ad0d8f463f88cbe4fef6"
+ integrity sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==
+
+"@esbuild/linux-x64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz#ca8e1aa478fc8209257bf3ac8f79c4dc2982f32a"
+ integrity sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==
+
+"@esbuild/netbsd-arm64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz#1650f2c1b948deeb3ef948f2fc30614723c09690"
+ integrity sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==
+
+"@esbuild/netbsd-x64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz#65772ab342c4b3319bf0705a211050aac1b6e320"
+ integrity sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==
+
+"@esbuild/openbsd-arm64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz#37ed7cfa66549d7955852fce37d0c3de4e715ea1"
+ integrity sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==
+
+"@esbuild/openbsd-x64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz#01bf3d385855ef50cb33db7c4b52f957c34cd179"
+ integrity sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==
+
+"@esbuild/openharmony-arm64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz#6c1f94b34086599aabda4eac8f638294b9877410"
+ integrity sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==
+
+"@esbuild/sunos-x64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz#4b0dd17ae0a6941d2d0fd35a906392517071a90d"
+ integrity sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==
+
+"@esbuild/win32-arm64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz#34193ab5565d6ff68ca928ac04be75102ccb2e77"
+ integrity sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==
+
+"@esbuild/win32-ia32@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz#eb67f0e4482515d8c1894ede631c327a4da9fc4d"
+ integrity sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==
+
+"@esbuild/win32-x64@0.27.7":
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz#8fe30b3088b89b4873c3a6cc87597ae3920c0a8b"
+ integrity sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==
+
+"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5":
+ version "0.3.13"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f"
+ integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==
+ dependencies:
+ "@jridgewell/sourcemap-codec" "^1.5.0"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
+"@jridgewell/remapping@^2.3.5":
+ version "2.3.5"
+ resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1"
+ integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
+"@jridgewell/resolve-uri@^3.1.0":
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
+ integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
+
+"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0":
+ version "1.5.5"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba"
+ integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
+
+"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28":
+ version "0.3.31"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0"
+ integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.1.0"
+ "@jridgewell/sourcemap-codec" "^1.4.14"
+
+"@mui/core-downloads-tracker@^7.3.11":
+ version "7.3.11"
+ resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.11.tgz#93251cf192641b9582641991feea60149159902c"
+ integrity sha512-a7I/b/nBTdXYz2cOSlEmkQ9WWE1x8FHpqMhFPp+Y1VPFxcOw91G5ELOHARQAGSPy5V+UCgJua6K/1x70bAtQPw==
+
+"@mui/icons-material@^7.3.4":
+ version "7.3.11"
+ resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-7.3.11.tgz#dfea40829d1ca96c074c85093fdfe0de170af5ad"
+ integrity sha512-+hz5ilwHZ3djd5es3sCErLioqe/NhZcYTsV/TNXZAMdJdb23F4xzJjqnnZdnurc3S1+ietcssRNqieOhPQLZ7Q==
+ dependencies:
+ "@babel/runtime" "^7.28.6"
+
+"@mui/material@^7.3.4":
+ version "7.3.11"
+ resolved "https://registry.yarnpkg.com/@mui/material/-/material-7.3.11.tgz#aa6e0640bb29bd01e6c353ac3436fa0b20e5a36b"
+ integrity sha512-yq8bPc3LxOwKRWpcjRgDkYFmpM6aKlARfESTmOQcvLYFeJwtHte2tw6hJDrb8sk8wcvpDprHEHVaoUU0MslIkw==
+ dependencies:
+ "@babel/runtime" "^7.28.6"
+ "@mui/core-downloads-tracker" "^7.3.11"
+ "@mui/system" "^7.3.11"
+ "@mui/types" "^7.4.12"
+ "@mui/utils" "^7.3.11"
+ "@popperjs/core" "^2.11.8"
+ "@types/react-transition-group" "^4.4.12"
+ clsx "^2.1.1"
+ csstype "^3.2.3"
+ prop-types "^15.8.1"
+ react-is "^19.2.3"
+ react-transition-group "^4.4.5"
+
+"@mui/private-theming@^7.3.11":
+ version "7.3.11"
+ resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-7.3.11.tgz#96d4cde586624916816f5a97fef3c808cf562fb0"
+ integrity sha512-9B+YKms0fRHbNrqp9tOT/DNbNnU5gyvJ1o3qAGXfq8GmZcbJnE3At9x07Zr/o0pkhzg4aDdwXVqe4+AcgtOCPA==
+ dependencies:
+ "@babel/runtime" "^7.28.6"
+ "@mui/utils" "^7.3.11"
+ prop-types "^15.8.1"
+
+"@mui/styled-engine@^7.3.10":
+ version "7.3.10"
+ resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-7.3.10.tgz#53e98c1fdeda972b5932c76f6a2a29faf33f0d11"
+ integrity sha512-WxE9SiF8xskAQqGjsp0poXCkCqsoXFEsSr0HBXfApmGHR+DBnXRp+z46Vsltg4gpPM4Z96DeAQRpeAOnhNg7Ng==
+ dependencies:
+ "@babel/runtime" "^7.28.6"
+ "@emotion/cache" "^11.14.0"
+ "@emotion/serialize" "^1.3.3"
+ "@emotion/sheet" "^1.4.0"
+ csstype "^3.2.3"
+ prop-types "^15.8.1"
+
+"@mui/system@^7.3.11", "@mui/system@^7.3.3":
+ version "7.3.11"
+ resolved "https://registry.yarnpkg.com/@mui/system/-/system-7.3.11.tgz#ffb8ba06f43d697db80257b9a2dfc8042b18554a"
+ integrity sha512-7izwGWdNawAKpBKcRlx7f2gFnAAjmASBWvMcyX4YYEeLOFsbfGRbUYGInvnAcUeql3rPxI7F9Ft4oY2OLRz44g==
+ dependencies:
+ "@babel/runtime" "^7.28.6"
+ "@mui/private-theming" "^7.3.11"
+ "@mui/styled-engine" "^7.3.10"
+ "@mui/types" "^7.4.12"
+ "@mui/utils" "^7.3.11"
+ clsx "^2.1.1"
+ csstype "^3.2.3"
+ prop-types "^15.8.1"
+
+"@mui/types@^7.4.12":
+ version "7.4.12"
+ resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.4.12.tgz#e4eba37a7506419ea5c5e0604322ba82b271bf46"
+ integrity sha512-iKNAF2u9PzSIj40CjvKJWxFXJo122jXVdrmdh0hMYd+FR+NuJMkr/L88XwWLCRiJ5P1j+uyac25+Kp6YC4hu6w==
+ dependencies:
+ "@babel/runtime" "^7.28.6"
+
+"@mui/utils@^7.3.11":
+ version "7.3.11"
+ resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-7.3.11.tgz#493f46f053fe3a692e041b1b6b8295e2f46d9448"
+ integrity sha512-XTjGnifwteg71/ij+0e7Y7d+hwyntMYP5wPoA/g2drdGH+Flkvjwy0OfrVpKBbaOvofq4zU/LIyUZyKgmWu18g==
+ dependencies:
+ "@babel/runtime" "^7.28.6"
+ "@mui/types" "^7.4.12"
+ "@types/prop-types" "^15.7.15"
+ clsx "^2.1.1"
+ prop-types "^15.8.1"
+ react-is "^19.2.3"
+
+"@popperjs/core@^2.11.8":
+ version "2.11.8"
+ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
+ integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
+
+"@rolldown/pluginutils@1.0.0-rc.3":
+ version "1.0.0-rc.3"
+ resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz#8a88cc92a0f741befc7bc109cb1a4c6b9408e1c5"
+ integrity sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==
+
+"@rollup/rollup-android-arm-eabi@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz#3a04f01e9f01392bbef5920b94aa3b88794be7ab"
+ integrity sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==
+
+"@rollup/rollup-android-arm64@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz#e371b653ceabc900790ae73f5548a0fd7cd63a70"
+ integrity sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==
+
+"@rollup/rollup-darwin-arm64@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz#2a5aa70432e39816d666d79287a7324cfc3b4e72"
+ integrity sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==
+
+"@rollup/rollup-darwin-x64@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz#c3b5b49629379cd9cdc5d841bf00ed44ebf393dd"
+ integrity sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==
+
+"@rollup/rollup-freebsd-arm64@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz#f929d8e0462fae6602fc960beeabd7287d859283"
+ integrity sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==
+
+"@rollup/rollup-freebsd-x64@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz#c01cb58031226f95d0900b1ec847f4fb32c6e809"
+ integrity sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==
+
+"@rollup/rollup-linux-arm-gnueabihf@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz#f29d890c4858c8e0d3be01677eef4f6a359eed9d"
+ integrity sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==
+
+"@rollup/rollup-linux-arm-musleabihf@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz#1ebfc8eb9f66136ed2faae5f44995add5ca3c964"
+ integrity sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==
+
+"@rollup/rollup-linux-arm64-gnu@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz#c1fa823c2c4ce46ba7f61de1a4c3fdadd4fb4e7b"
+ integrity sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==
+
+"@rollup/rollup-linux-arm64-musl@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz#a7f18854d0471b78bda8ea38f0891a4e059b571d"
+ integrity sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==
+
+"@rollup/rollup-linux-loong64-gnu@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz#83658a9a4576bcce8cef85b2c78b9b649d2200c4"
+ integrity sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==
+
+"@rollup/rollup-linux-loong64-musl@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz#fd2af677ae3417bb58d57ae37dd0d84686e40244"
+ integrity sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==
+
+"@rollup/rollup-linux-ppc64-gnu@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz#6481647181c4cf8f1ddbd99f62c84cfc56c1a94a"
+ integrity sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==
+
+"@rollup/rollup-linux-ppc64-musl@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz#18610a1a1550e28a5042ca916f898419540f17f4"
+ integrity sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==
+
+"@rollup/rollup-linux-riscv64-gnu@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz#597bb80465a2621dbe0de0a41c66394a8a7e9a6e"
+ integrity sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==
+
+"@rollup/rollup-linux-riscv64-musl@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz#a2a919a9f927ef7f24a60af77e3cb55f1ad59e4d"
+ integrity sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==
+
+"@rollup/rollup-linux-s390x-gnu@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz#3166f6ceae7df9bbfddf9f36be1937231e13e3c6"
+ integrity sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==
+
+"@rollup/rollup-linux-x64-gnu@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz#23c9bf79771d804fb87415eb0767569f273261e5"
+ integrity sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==
+
+"@rollup/rollup-linux-x64-musl@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz#97941c6b94d67fe25cde0f027c10a19f2d1fdd39"
+ integrity sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==
+
+"@rollup/rollup-openbsd-x64@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz#7aeb7d92e2cd1d399f56daf75c39040b777b6c77"
+ integrity sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==
+
+"@rollup/rollup-openharmony-arm64@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz#925de61ae83bf99aa636e8acea87432e8c0ffaab"
+ integrity sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==
+
+"@rollup/rollup-win32-arm64-msvc@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz#888ab83842721491044c46a7407e1f38f3235bb4"
+ integrity sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==
+
+"@rollup/rollup-win32-ia32-msvc@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz#fa30ac24e3f0232139d2a47500560a28695764d4"
+ integrity sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==
+
+"@rollup/rollup-win32-x64-gnu@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz#223e2bc93f86e0707568e1fadb5b537e50c976c7"
+ integrity sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==
+
+"@rollup/rollup-win32-x64-msvc@4.60.4":
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz#da4f1676d87e2bdf744291b504b0ab79550c3e61"
+ integrity sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==
+
+"@tanstack/history@1.162.0":
+ version "1.162.0"
+ resolved "https://registry.yarnpkg.com/@tanstack/history/-/history-1.162.0.tgz#5e12a52edd82a5d6d3b081255ec899ccca335443"
+ integrity sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA==
+
+"@tanstack/react-router@^1":
+ version "1.170.9"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-router/-/react-router-1.170.9.tgz#40db6e8a23daead3f655c9ea5da0b494e5e5fe61"
+ integrity sha512-rXXztp6GyXFwResQR0jdvkf52wy/sAQqze3OR08+8uNM7nHir1P46qBrwg2TL5EEtX+0WUho4BZ/nMpAgQVB6A==
+ dependencies:
+ "@tanstack/history" "1.162.0"
+ "@tanstack/react-store" "^0.9.3"
+ "@tanstack/router-core" "1.171.7"
+ isbot "^5.1.22"
+
+"@tanstack/react-store@^0.9.3":
+ version "0.9.3"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-store/-/react-store-0.9.3.tgz#4d19d982a4612cc21cedf5ab25a62617323509c9"
+ integrity sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==
+ dependencies:
+ "@tanstack/store" "0.9.3"
+ use-sync-external-store "^1.6.0"
+
+"@tanstack/router-core@1.171.7":
+ version "1.171.7"
+ resolved "https://registry.yarnpkg.com/@tanstack/router-core/-/router-core-1.171.7.tgz#ff2edaa37e25cd8954d0e92d81534b493c7f3df7"
+ integrity sha512-AboyQD0OPIu0adJi6HeCupTU9Wx7zr4q4ceJYQdBL3mLH18m4M57XXdzJdtzOg/twl8DtWej0RGJ12ma8CV1iQ==
+ dependencies:
+ "@tanstack/history" "1.162.0"
+ cookie-es "^3.0.0"
+ seroval "^1.5.4"
+ seroval-plugins "^1.5.4"
+
+"@tanstack/store@0.9.3":
+ version "0.9.3"
+ resolved "https://registry.yarnpkg.com/@tanstack/store/-/store-0.9.3.tgz#0f9ebca34d194442fe559afce1e8a7327f6d3eb4"
+ integrity sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==
+
+"@types/babel__core@^7.20.5":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
+ integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==
+ dependencies:
+ "@babel/parser" "^7.20.7"
+ "@babel/types" "^7.20.7"
+ "@types/babel__generator" "*"
+ "@types/babel__template" "*"
+ "@types/babel__traverse" "*"
+
+"@types/babel__generator@*":
+ version "7.27.0"
+ resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9"
+ integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==
+ dependencies:
+ "@babel/types" "^7.0.0"
+
+"@types/babel__template@*":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f"
+ integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==
+ dependencies:
+ "@babel/parser" "^7.1.0"
+ "@babel/types" "^7.0.0"
+
+"@types/babel__traverse@*":
+ version "7.28.0"
+ resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74"
+ integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==
+ dependencies:
+ "@babel/types" "^7.28.2"
+
+"@types/estree@1.0.8":
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
+ integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
+
+"@types/node@^24.7.1":
+ version "24.12.4"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-24.12.4.tgz#2709745569811dcbdc57c097fafdd387c6330382"
+ integrity sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==
+ dependencies:
+ undici-types "~7.16.0"
+
+"@types/parse-json@^4.0.0":
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
+ integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==
+
+"@types/prop-types@^15.7.15":
+ version "15.7.15"
+ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.15.tgz#e6e5a86d602beaca71ce5163fadf5f95d70931c7"
+ integrity sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==
+
+"@types/react-dom@^19.2.1":
+ version "19.2.3"
+ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c"
+ integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==
+
+"@types/react-transition-group@^4.4.12":
+ version "4.4.12"
+ resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044"
+ integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==
+
+"@types/react@^19.2.2":
+ version "19.2.15"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.15.tgz#9e2c6a4251a290f5c525c3143caa872fa6e01e0d"
+ integrity sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==
+ dependencies:
+ csstype "^3.2.2"
+
+"@vitejs/plugin-react@^5.0.4":
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz#108bd0f566f288ce3566982df4eff137ded7b15f"
+ integrity sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==
+ dependencies:
+ "@babel/core" "^7.29.0"
+ "@babel/plugin-transform-react-jsx-self" "^7.27.1"
+ "@babel/plugin-transform-react-jsx-source" "^7.27.1"
+ "@rolldown/pluginutils" "1.0.0-rc.3"
+ "@types/babel__core" "^7.20.5"
+ react-refresh "^0.18.0"
+
+babel-plugin-macros@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1"
+ integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==
+ dependencies:
+ "@babel/runtime" "^7.12.5"
+ cosmiconfig "^7.0.0"
+ resolve "^1.19.0"
+
+baseline-browser-mapping@^2.10.12:
+ version "2.10.32"
+ resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz#b6b553a4285fdd606327a617de36a5351e3aaa64"
+ integrity sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==
+
+browserslist@^4.24.0:
+ version "4.28.2"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.2.tgz#f50b65362ef48974ca9f50b3680566d786b811d2"
+ integrity sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==
+ dependencies:
+ baseline-browser-mapping "^2.10.12"
+ caniuse-lite "^1.0.30001782"
+ electron-to-chromium "^1.5.328"
+ node-releases "^2.0.36"
+ update-browserslist-db "^1.2.3"
+
+callsites@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
+ integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+
+caniuse-lite@^1.0.30001782:
+ version "1.0.30001793"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz#238887ddf5fcfc8c36d872394d0a78a517312a72"
+ integrity sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==
+
+clsx@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
+ integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
+
+convert-source-map@^1.5.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
+ integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
+
+convert-source-map@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
+ integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
+
+cookie-es@^3.0.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/cookie-es/-/cookie-es-3.1.1.tgz#c4a8a16cf88cb5a185b23f4a61d6e9a85eb53287"
+ integrity sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==
+
+cosmiconfig@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
+ integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==
+ dependencies:
+ "@types/parse-json" "^4.0.0"
+ import-fresh "^3.2.1"
+ parse-json "^5.0.0"
+ path-type "^4.0.0"
+ yaml "^1.10.0"
+
+cronstrue@^3.3.0:
+ version "3.14.0"
+ resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-3.14.0.tgz#bc7844ae67959b90742b298aabe7c6d49cdbf931"
+ integrity sha512-XnW4vuK/jPJjmTyDWiej1Zq36Od7ITwxaV2O1pzHZuyMVvdy7NAvyvIBzybt+idqSpfqYuoDG7uf/ocGtJVWxA==
+
+csstype@^3.0.2, csstype@^3.2.2, csstype@^3.2.3:
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a"
+ integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==
+
+debug@^4.1.0, debug@^4.3.1:
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
+ integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
+ dependencies:
+ ms "^2.1.3"
+
+dom-helpers@^5.0.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
+ integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
+ dependencies:
+ "@babel/runtime" "^7.8.7"
+ csstype "^3.0.2"
+
+electron-to-chromium@^1.5.328:
+ version "1.5.364"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.364.tgz#38af950b724b22b937c342c0c4b361bd2fb50669"
+ integrity sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==
+
+error-ex@^1.3.1:
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414"
+ integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==
+ dependencies:
+ is-arrayish "^0.2.1"
+
+es-errors@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
+ integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
+
+esbuild@^0.27.0:
+ version "0.27.7"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.7.tgz#bcadce22b2f3fd76f257e3a64f83a64986fea11f"
+ integrity sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==
+ optionalDependencies:
+ "@esbuild/aix-ppc64" "0.27.7"
+ "@esbuild/android-arm" "0.27.7"
+ "@esbuild/android-arm64" "0.27.7"
+ "@esbuild/android-x64" "0.27.7"
+ "@esbuild/darwin-arm64" "0.27.7"
+ "@esbuild/darwin-x64" "0.27.7"
+ "@esbuild/freebsd-arm64" "0.27.7"
+ "@esbuild/freebsd-x64" "0.27.7"
+ "@esbuild/linux-arm" "0.27.7"
+ "@esbuild/linux-arm64" "0.27.7"
+ "@esbuild/linux-ia32" "0.27.7"
+ "@esbuild/linux-loong64" "0.27.7"
+ "@esbuild/linux-mips64el" "0.27.7"
+ "@esbuild/linux-ppc64" "0.27.7"
+ "@esbuild/linux-riscv64" "0.27.7"
+ "@esbuild/linux-s390x" "0.27.7"
+ "@esbuild/linux-x64" "0.27.7"
+ "@esbuild/netbsd-arm64" "0.27.7"
+ "@esbuild/netbsd-x64" "0.27.7"
+ "@esbuild/openbsd-arm64" "0.27.7"
+ "@esbuild/openbsd-x64" "0.27.7"
+ "@esbuild/openharmony-arm64" "0.27.7"
+ "@esbuild/sunos-x64" "0.27.7"
+ "@esbuild/win32-arm64" "0.27.7"
+ "@esbuild/win32-ia32" "0.27.7"
+ "@esbuild/win32-x64" "0.27.7"
+
+escalade@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
+ integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
+
+escape-string-regexp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
+fdir@^6.5.0:
+ version "6.5.0"
+ resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350"
+ integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
+
+find-root@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
+ integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
+
+fsevents@~2.3.2, fsevents@~2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
+ integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
+function-bind@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
+ integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+
+gensync@^1.0.0-beta.2:
+ version "1.0.0-beta.2"
+ resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
+ integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
+
+hasown@^2.0.3:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003"
+ integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==
+ dependencies:
+ function-bind "^1.1.2"
+
+hoist-non-react-statics@^3.3.1:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
+ integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
+ dependencies:
+ react-is "^16.7.0"
+
+import-fresh@^3.2.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf"
+ integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==
+ dependencies:
+ parent-module "^1.0.0"
+ resolve-from "^4.0.0"
+
+is-arrayish@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
+ integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
+
+is-core-module@^2.16.1:
+ version "2.16.2"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.2.tgz#3e07450a8080ebce3fbf0cac494f4d2ab324e082"
+ integrity sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==
+ dependencies:
+ hasown "^2.0.3"
+
+isbot@^5.1.22:
+ version "5.1.40"
+ resolved "https://registry.yarnpkg.com/isbot/-/isbot-5.1.40.tgz#2878f52e9e4118b4466c268aaf7502dcfd38375b"
+ integrity sha512-yNeeynhhtIVRBk12tBV4eHNxwB42HzR4Q3Ea7vCOiJhImGaAIdIMrbJtacQlBizGLjUPw+akkFI5Dn9T70XoVQ==
+
+jotai@^2.15.0:
+ version "2.20.0"
+ resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.20.0.tgz#0e3422f6f7141758e8212d72338f9cf3bef5fd45"
+ integrity sha512-b5GAqgmXmXzB4WPaTH26ppk9Sl7AA9WSQX7yfdM+gJ1rFROiWcVbi97gFuN/yVCojOcbcvop2sfLL+fjxW0JVg==
+
+"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+ integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+jsesc@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
+ integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==
+
+json-parse-even-better-errors@^2.3.0:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
+ integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
+
+json5@^2.2.3:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
+ integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
+
+lines-and-columns@^1.1.6:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
+ integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
+
+loose-envify@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
+ integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
+ dependencies:
+ js-tokens "^3.0.0 || ^4.0.0"
+
+lru-cache@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
+ integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
+ dependencies:
+ yallist "^3.0.2"
+
+ms@^2.1.3:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+nanoid@^3.3.12:
+ version "3.3.12"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.12.tgz#ab3d912e217a6d0a514f00a72a16543a28982c05"
+ integrity sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==
+
+node-releases@^2.0.36:
+ version "2.0.46"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.46.tgz#d188a129a83f5e03a101aacb58f260f2ee8faaa1"
+ integrity sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==
+
+object-assign@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+ integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
+
+parent-module@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
+ integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+ dependencies:
+ callsites "^3.0.0"
+
+parse-json@^5.0.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
+ integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ error-ex "^1.3.1"
+ json-parse-even-better-errors "^2.3.0"
+ lines-and-columns "^1.1.6"
+
+path-parse@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
+ integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+
+path-type@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
+ integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
+
+picocolors@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
+ integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
+
+picomatch@^4.0.3, picomatch@^4.0.4:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589"
+ integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==
+
+postcss@^8.5.6:
+ version "8.5.15"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.15.tgz#d1eaf677a324e9ec02196da2d3fecf4a0b9a735c"
+ integrity sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==
+ dependencies:
+ nanoid "^3.3.12"
+ picocolors "^1.1.1"
+ source-map-js "^1.2.1"
+
+prop-types@^15.6.2, prop-types@^15.8.1:
+ version "15.8.1"
+ resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
+ integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
+ dependencies:
+ loose-envify "^1.4.0"
+ object-assign "^4.1.1"
+ react-is "^16.13.1"
+
+react-dom@^19.2.0:
+ version "19.2.6"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.6.tgz#44a81b0bcca22da814c00847d09d01c8615529b7"
+ integrity sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==
+ dependencies:
+ scheduler "^0.27.0"
+
+react-is@^16.13.1, react-is@^16.7.0:
+ version "16.13.1"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
+ integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
+
+react-is@^19.2.3:
+ version "19.2.6"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.2.6.tgz#aeee6159b159eb7f520d672cffcc69e7052d288f"
+ integrity sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==
+
+react-refresh@^0.18.0:
+ version "0.18.0"
+ resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.18.0.tgz#2dce97f4fe932a4d8142fa1630e475c1729c8062"
+ integrity sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==
+
+react-transition-group@^4.4.5:
+ version "4.4.5"
+ resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
+ integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
+ dependencies:
+ "@babel/runtime" "^7.5.5"
+ dom-helpers "^5.0.1"
+ loose-envify "^1.4.0"
+ prop-types "^15.6.2"
+
+react@^19.2.0:
+ version "19.2.6"
+ resolved "https://registry.yarnpkg.com/react/-/react-19.2.6.tgz#3dadb8e12b2a7934c1d5317973e5dce1301f9a4d"
+ integrity sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==
+
+resolve-from@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
+ integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
+resolve@^1.19.0:
+ version "1.22.12"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.12.tgz#f5b2a680897c69c238a13cd16b15671f8b73549f"
+ integrity sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==
+ dependencies:
+ es-errors "^1.3.0"
+ is-core-module "^2.16.1"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
+rollup@^4.43.0:
+ version "4.60.4"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.60.4.tgz#ca3814f5900da3ac3981d2e0c61944b7e6e0cb09"
+ integrity sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==
+ dependencies:
+ "@types/estree" "1.0.8"
+ optionalDependencies:
+ "@rollup/rollup-android-arm-eabi" "4.60.4"
+ "@rollup/rollup-android-arm64" "4.60.4"
+ "@rollup/rollup-darwin-arm64" "4.60.4"
+ "@rollup/rollup-darwin-x64" "4.60.4"
+ "@rollup/rollup-freebsd-arm64" "4.60.4"
+ "@rollup/rollup-freebsd-x64" "4.60.4"
+ "@rollup/rollup-linux-arm-gnueabihf" "4.60.4"
+ "@rollup/rollup-linux-arm-musleabihf" "4.60.4"
+ "@rollup/rollup-linux-arm64-gnu" "4.60.4"
+ "@rollup/rollup-linux-arm64-musl" "4.60.4"
+ "@rollup/rollup-linux-loong64-gnu" "4.60.4"
+ "@rollup/rollup-linux-loong64-musl" "4.60.4"
+ "@rollup/rollup-linux-ppc64-gnu" "4.60.4"
+ "@rollup/rollup-linux-ppc64-musl" "4.60.4"
+ "@rollup/rollup-linux-riscv64-gnu" "4.60.4"
+ "@rollup/rollup-linux-riscv64-musl" "4.60.4"
+ "@rollup/rollup-linux-s390x-gnu" "4.60.4"
+ "@rollup/rollup-linux-x64-gnu" "4.60.4"
+ "@rollup/rollup-linux-x64-musl" "4.60.4"
+ "@rollup/rollup-openbsd-x64" "4.60.4"
+ "@rollup/rollup-openharmony-arm64" "4.60.4"
+ "@rollup/rollup-win32-arm64-msvc" "4.60.4"
+ "@rollup/rollup-win32-ia32-msvc" "4.60.4"
+ "@rollup/rollup-win32-x64-gnu" "4.60.4"
+ "@rollup/rollup-win32-x64-msvc" "4.60.4"
+ fsevents "~2.3.2"
+
+scheduler@^0.27.0:
+ version "0.27.0"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd"
+ integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==
+
+semver@^6.3.1:
+ version "6.3.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
+ integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
+
+seroval-plugins@^1.5.4:
+ version "1.5.4"
+ resolved "https://registry.yarnpkg.com/seroval-plugins/-/seroval-plugins-1.5.4.tgz#3e7d1910b5a516684046770d201b993c81b1b95a"
+ integrity sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==
+
+seroval@^1.5.4:
+ version "1.5.4"
+ resolved "https://registry.yarnpkg.com/seroval/-/seroval-1.5.4.tgz#9d0cedae244f8213bbbbbcc99c497eb7c945d961"
+ integrity sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==
+
+source-map-js@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
+ integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
+
+source-map@^0.5.7:
+ version "0.5.7"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
+ integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
+
+stylis@4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51"
+ integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==
+
+supports-preserve-symlinks-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
+ integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
+tinyglobby@^0.2.15:
+ version "0.2.16"
+ resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.16.tgz#1c3b7eb953fce42b226bc5a1ee06428281aff3d6"
+ integrity sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==
+ dependencies:
+ fdir "^6.5.0"
+ picomatch "^4.0.4"
+
+typescript@^5.9.3:
+ version "5.9.3"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
+ integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
+
+undici-types@~7.16.0:
+ version "7.16.0"
+ resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46"
+ integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==
+
+update-browserslist-db@^1.2.3:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d"
+ integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==
+ dependencies:
+ escalade "^3.2.0"
+ picocolors "^1.1.1"
+
+use-sync-external-store@^1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d"
+ integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==
+
+vite@^7.1.9:
+ version "7.3.3"
+ resolved "https://registry.yarnpkg.com/vite/-/vite-7.3.3.tgz#d7e07a52b5873fb86f902a3f4b3d17410337450f"
+ integrity sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==
+ dependencies:
+ esbuild "^0.27.0"
+ fdir "^6.5.0"
+ picomatch "^4.0.3"
+ postcss "^8.5.6"
+ rollup "^4.43.0"
+ tinyglobby "^0.2.15"
+ optionalDependencies:
+ fsevents "~2.3.3"
+
+yallist@^3.0.2:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
+ integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
+
+yaml@^1.10.0:
+ version "1.10.3"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.3.tgz#76e407ed95c42684fb8e14641e5de62fe65bbcb3"
+ integrity sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==
diff --git a/package.json b/package.json
index bcf903a..3bb3b74 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "material-ui-cron",
- "version": "1.1.1",
+ "version": "1.1.2",
"description": "A React cron editor using material ui",
"author": "Parichay Barpanda (https://github.com/baymac/)",
"license": "MIT",
@@ -29,30 +29,39 @@
"types": "dist/index.d.ts",
"scripts": {
"build": "vite build",
+ "test": "vitest run",
+ "test:unit": "vitest run --project unit",
+ "test:browser": "vitest run --project browser",
+ "test:watch": "vitest",
+ "coverage": "vitest run --coverage",
"lint": "biome lint --write .",
"format": "biome format --write .",
- "dev": "storybook dev -p 6006",
- "storybook": "storybook dev -p 6006",
- "build-storybook": "storybook build"
+ "demo": "yarn --cwd demo dev",
+ "demo:install": "yarn --cwd demo install",
+ "demo:build": "yarn --cwd demo build"
},
"devDependencies": {
"@biomejs/biome": "^2.2.5",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
- "@storybook/addon-links": "^9.1.10",
- "@storybook/react": "^9.1.10",
- "@storybook/react-vite": "^9.1.10",
- "@storybook/test": "^8.6.14",
+ "@testing-library/dom": "^10",
+ "@testing-library/jest-dom": "^6",
+ "@testing-library/react": "^16",
+ "@testing-library/user-event": "^14",
"@types/node": "^24.7.1",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.1",
"@vitejs/plugin-react": "^5.0.4",
+ "@vitest/browser": "^3",
+ "@vitest/coverage-v8": "^3",
+ "playwright": "^1",
"react": "^19.2.0",
"react-dom": "^19.2.0",
- "storybook": "^9.1.10",
"typescript": "^5.9.3",
"vite": "^7.1.9",
- "vite-plugin-dts": "^4.5.4"
+ "vite-plugin-dts": "^4.5.4",
+ "vitest": "^3",
+ "vitest-browser-react": "^1"
},
"peerDependencies": {
"@emotion/react": "^11.11.0",
@@ -67,5 +76,8 @@
"cronstrue": "^3.3.0",
"jotai": "^2.15.0"
},
- "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
+ "resolutions": {
+ "vite": "7.1.9"
+ }
}
diff --git a/src/Scheduler.tsx b/src/Scheduler.tsx
index cfa0f44..41d7c6c 100644
--- a/src/Scheduler.tsx
+++ b/src/Scheduler.tsx
@@ -14,10 +14,13 @@ import { supportedLanguages } from './i18n';
import {
cronExpInputState,
cronValidationErrorMessageState,
+ dayOfMonthAtEveryState,
dayOfMonthState,
+ hourAtEveryState,
hourState,
isAdminState,
localeState,
+ minuteAtEveryState,
minuteState,
monthState,
periodState,
@@ -26,13 +29,16 @@ import {
import type { SchedulerProps } from './types';
import { getPeriodIndex } from './utils';
import {
+ atEveryOptions,
DEFAULT_DAY_OF_MONTH_OPTS,
DEFAULT_HOUR_OPTS_EVERY,
DEFAULT_MINUTE_OPTS,
getPeriodOptions,
getMonthOptions,
+ onEveryOptions,
weekOptions,
} from './constants';
+import type { SelectOptions } from './types';
const StyledBox = styled(Box)({
minHeight: 'min-content',
@@ -63,6 +69,9 @@ export default function Scheduler(props: SchedulerProps) {
const setWeek = useSetAtom(weekState);
const setMonth = useSetAtom(monthState);
const setPeriod = useSetAtom(periodState);
+ const setMinuteAtEvery = useSetAtom(minuteAtEveryState);
+ const setHourAtEvery = useSetAtom(hourAtEveryState);
+ const setDayOfMonthAtEvery = useSetAtom(dayOfMonthAtEveryState);
React.useEffect(() => {
setCronError(cronError);
@@ -72,9 +81,37 @@ export default function Scheduler(props: SchedulerProps) {
setPeriodIndex(getPeriodIndex(period));
}, [period]);
+ // Two-way binding between the controlled `cron` prop and the internal
+ // `cronExpInput` atom. Splitting this into two opposing effects causes them
+ // to swap stale values on a single commit and ping-pong forever whenever the
+ // initial prop differs from the atom default (-> "Maximum update depth
+ // exceeded"). A single effect that propagates only the side that actually
+ // changed converges in one render.
+ const prevSync = React.useRef<{ cron: string; input: string } | null>(null);
React.useEffect(() => {
- setCron(cronExpInput);
- }, [cronExpInput, setCron]);
+ if (prevSync.current === null) {
+ // Initial mount: adopt a non-default prop, otherwise report the current
+ // expression back up so the parent always receives the (normalized)
+ // value at least once.
+ if (cron !== cronExpInput) {
+ setCronExpInput(cron);
+ } else {
+ setCron(cronExpInput);
+ }
+ prevSync.current = { cron, input: cronExpInput };
+ return;
+ }
+ const cronChanged = cron !== prevSync.current.cron;
+ const inputChanged = cronExpInput !== prevSync.current.input;
+ if (cronChanged && cron !== cronExpInput) {
+ // Parent pushed a new value down -> ingest it.
+ setCronExpInput(cron);
+ } else if (inputChanged && cronExpInput !== cron) {
+ // Internal value changed (user edit / field change) -> notify parent.
+ setCron(cronExpInput);
+ }
+ prevSync.current = { cron, input: cronExpInput };
+ }, [cron, cronExpInput, setCron, setCronExpInput]);
React.useEffect(() => {
if (isAdmin) {
@@ -84,31 +121,34 @@ export default function Scheduler(props: SchedulerProps) {
}
}, [isAdmin, setIsAdmin]);
+ // Only reset atoms on unmount. The atoms are module-level (shared) globals,
+ // so we restore their defaults when the component leaves the tree. Keep
+ // `currentLocale` out of the dependency array: with it in deps, React fires
+ // this effect's cleanup on every locale change (and re-mount via `key`),
+ // resetting the shared atoms to the *previously captured* locale's defaults.
+ // That is exactly the "period stays one language behind" bug — switch to
+ // Chinese and the period label resets to English; switch back and it shows
+ // Chinese. Read the latest locale through a ref so the cleanup runs only on
+ // a real unmount and uses the current locale.
+ const localeRef = React.useRef(currentLocale);
React.useEffect(() => {
- setCronExpInput(cron);
- }, [cron, setCronExpInput]);
-
- // Only reset atoms on unmount.
+ localeRef.current = currentLocale;
+ }, [currentLocale]);
React.useEffect(() => {
return () => {
+ const loc = localeRef.current;
setCronExpInput('0 0 * * *');
setMinute([DEFAULT_MINUTE_OPTS[0]]);
setHour([DEFAULT_HOUR_OPTS_EVERY[0]]);
setDayOfMonth(DEFAULT_DAY_OF_MONTH_OPTS);
- setWeek(weekOptions(currentLocale.weekDaysOptions));
- setMonth(getMonthOptions(currentLocale.shortMonthOptions));
- setPeriod(getPeriodOptions(currentLocale.periodOptions)[1]);
+ setWeek(weekOptions(loc.weekDaysOptions));
+ setMonth(getMonthOptions(loc.shortMonthOptions));
+ setPeriod(getPeriodOptions(loc.periodOptions)[1]);
};
- }, [
- setCronExpInput,
- setMinute,
- setHour,
- setDayOfMonth,
- setWeek,
- setMonth,
- setPeriod,
- currentLocale,
- ]);
+ // Setters from `useSetAtom` are stable; the locale is read via ref. Empty
+ // deps make this a true unmount-only cleanup.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
React.useEffect(() => {
if (customLocale) {
@@ -120,6 +160,39 @@ export default function Scheduler(props: SchedulerProps) {
}
}, [locale, customLocale, setResolvedLocale]);
+ // Re-localize the *selected* values when the locale changes. The option
+ // `value`s are locale-stable ('day', 'at', '0'...) while only the `label` is
+ // translated, so a stored selection keeps a stale label after a locale
+ // switch (e.g. period reads "day" under Chinese). Re-map each selection onto
+ // the matching option in the new locale, preserving the choice and
+ // refreshing its label. Falls back to the existing value if no match.
+ React.useEffect(() => {
+ const remap = (opts: SelectOptions[]) => (prev: SelectOptions) =>
+ opts.find((o) => o.value === prev.value) ?? prev;
+ const remapList = (opts: SelectOptions[]) => (prev: SelectOptions[]) =>
+ prev.map((item) => opts.find((o) => o.value === item.value) ?? item);
+
+ setPeriod(remap(getPeriodOptions(currentLocale.periodOptions)));
+
+ const atEvery = atEveryOptions(currentLocale.atOptionLabel, currentLocale.everyOptionLabel);
+ setMinuteAtEvery(remap(atEvery));
+ setHourAtEvery(remap(atEvery));
+ setDayOfMonthAtEvery(
+ remap(onEveryOptions(currentLocale.onOptionLabel, currentLocale.everyOptionLabel)),
+ );
+
+ setWeek(remapList(weekOptions(currentLocale.weekDaysOptions)));
+ setMonth(remapList(getMonthOptions(currentLocale.shortMonthOptions)));
+ }, [
+ currentLocale,
+ setPeriod,
+ setMinuteAtEvery,
+ setHourAtEvery,
+ setDayOfMonthAtEvery,
+ setWeek,
+ setMonth,
+ ]);
+
return (
<>
diff --git a/src/components/CronExp.tsx b/src/components/CronExp.tsx
index fc1c19a..1b377a3 100644
--- a/src/components/CronExp.tsx
+++ b/src/components/CronExp.tsx
@@ -78,9 +78,9 @@ export default function CronExp() {
React.useEffect(() => {
if (debouncedCronExpInput) {
- setCronExp(cronExpInput);
+ setCronExp(debouncedCronExpInput);
}
- }, [debouncedCronExpInput, setCronExp, cronExpInput]);
+ }, [debouncedCronExpInput, setCronExp]);
return (
diff --git a/src/components/CustomSelect.tsx b/src/components/CustomSelect.tsx
index e0537f8..4e714f1 100644
--- a/src/components/CustomSelect.tsx
+++ b/src/components/CustomSelect.tsx
@@ -82,6 +82,11 @@ export default function CustomSelect(props: CustomSelectProps) {
disableClearable={disableClearable}
autoComplete
disableCloseOnSelect={!single}
+ // Single-value selects must close on selection. Because these are
+ // rendered as `multiple` Autocompletes, re-render churn after a
+ // selection can otherwise leave the popup open — blurOnSelect forces
+ // it shut. Multi-selects keep the popup open for further picks.
+ blurOnSelect={single ? true : undefined}
sx={{
width: sizeConfig.width,
'& .MuiAutocomplete-inputRoot': {
diff --git a/src/jest-dom.d.ts b/src/jest-dom.d.ts
new file mode 100644
index 0000000..c77bb8b
--- /dev/null
+++ b/src/jest-dom.d.ts
@@ -0,0 +1,4 @@
+// Makes the @testing-library/jest-dom matchers (toBeInTheDocument, toBeDisabled,
+// ...) available on Vitest's `expect` for type-checking the test files. The
+// matching runtime augmentation is loaded via vitest.setup.browser.ts.
+import '@testing-library/jest-dom/vitest';
diff --git a/src/scheduler.browser.test.tsx b/src/scheduler.browser.test.tsx
new file mode 100644
index 0000000..888f39a
--- /dev/null
+++ b/src/scheduler.browser.test.tsx
@@ -0,0 +1,195 @@
+import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { afterEach, describe, expect, it, vi } from 'vitest';
+import Scheduler from './index';
+
+const noop = () => {};
+
+afterEach(() => {
+ // Unmounting runs Scheduler's reset effect, restoring the shared Jotai atoms.
+ cleanup();
+});
+
+const periodValue = () => (screen.getByLabelText('Period') as HTMLInputElement).value;
+
+describe('Scheduler (browser)', () => {
+ it('renders the default expression and core fields', async () => {
+ render();
+
+ expect(await screen.findByDisplayValue('0 0 * * *')).toBeInTheDocument();
+ await waitFor(() => expect(periodValue()).toBe('day'));
+ expect(screen.getByLabelText('Minute(s)')).toBeInTheDocument();
+ expect(screen.getByLabelText('Hour(s)')).toBeInTheDocument();
+ });
+
+ it('disables the cron input for non-admins', async () => {
+ render();
+ expect(await screen.findByDisplayValue('0 0 * * *')).toBeDisabled();
+ });
+
+ it('reports the cron expression back through setCron', async () => {
+ const setCron = vi.fn();
+ render();
+ await waitFor(() => expect(setCron).toHaveBeenCalledWith('0 0 * * *'));
+ });
+
+ // ---- #19: `0 1/4 * * *` must be accepted ----
+ it('accepts a plain n/m step expression without error (#19)', async () => {
+ const setCronError = vi.fn();
+ render();
+
+ const input = await screen.findByDisplayValue('0 0 * * *');
+ fireEvent.change(input, { target: { value: '0 1/4 * * *' } });
+
+ // The value is parsed and normalised (1/4 -> the equivalent 1-23/4). What
+ // matters for #19 is that it validates cleanly: no error surfaces.
+ await waitFor(() => expect(setCronError).toHaveBeenLastCalledWith(''), { timeout: 3000 });
+ expect(screen.queryByText(/Invalid .* cron part/i)).not.toBeInTheDocument();
+ });
+
+ it('still flags a genuinely invalid expression', async () => {
+ const setCronError = vi.fn();
+ render();
+
+ const input = await screen.findByDisplayValue('0 0 * * *');
+ fireEvent.change(input, { target: { value: '60 * * * *' } });
+
+ await waitFor(
+ () => expect(setCronError).toHaveBeenLastCalledWith(expect.stringMatching(/Invalid minute/i)),
+ { timeout: 3000 },
+ );
+ });
+
+ // ---- #20: changing the prop to a broader cron resets the period ----
+ it('resets the period when the cron prop broadens (#20)', async () => {
+ const { rerender } = render(
+ ,
+ );
+ await waitFor(() => expect(periodValue()).toBe('week'), { timeout: 3000 });
+
+ rerender();
+ await waitFor(() => expect(screen.getByDisplayValue('* * * * *')).toBeInTheDocument(), {
+ timeout: 3000,
+ });
+ await waitFor(() => expect(periodValue()).toBe('hour'), { timeout: 3000 });
+ });
+
+ it('still widens the period when the prop narrows', async () => {
+ const { rerender } = render(
+ ,
+ );
+ await waitFor(() => expect(periodValue()).toBe('day'), { timeout: 3000 });
+
+ rerender();
+ await waitFor(() => expect(periodValue()).toBe('month'), { timeout: 3000 });
+ });
+
+ it('renders month and day-of-month fields for a yearly expression', async () => {
+ render();
+ await waitFor(() => expect(periodValue()).toBe('year'), { timeout: 3000 });
+ expect(screen.getByLabelText('Month(s)')).toBeInTheDocument();
+ expect(screen.getByLabelText('Day of the Month')).toBeInTheDocument();
+ expect(screen.getByLabelText('Hour(s)')).toBeInTheDocument();
+ });
+
+ it('renders a day-of-month range expression', async () => {
+ render();
+ await waitFor(() => expect(periodValue()).toBe('month'), { timeout: 3000 });
+ expect(await screen.findByDisplayValue('0 0 1-15 * *')).toBeInTheDocument();
+ });
+
+ it('renders a weekday range expression', async () => {
+ render();
+ await waitFor(() => expect(periodValue()).toBe('week'), { timeout: 3000 });
+ expect(screen.getByLabelText('Week Days')).toBeInTheDocument();
+ });
+
+ // ---- #16: single-value autocompletes close after a selection ----
+ it('closes a single-value autocomplete after selecting an option (#16)', async () => {
+ const user = userEvent.setup();
+ render();
+
+ const periodInput = await screen.findByLabelText('Period');
+ await user.click(periodInput);
+
+ const listbox = await screen.findByRole('listbox');
+ const weekOption = within(listbox).getByText('week');
+ await user.click(weekOption);
+
+ // The popup must close on selection (blurOnSelect for single selects).
+ await waitFor(() => expect(screen.queryByRole('listbox')).not.toBeInTheDocument());
+ expect(periodValue()).toBe('week');
+ });
+
+ // #16 deeper audit: the issue specifically named the *time-range* selects.
+ // Unlike Period, these recompute their option arrays on every selection
+ // (Hour.tsx / Minute.tsx effects) — the re-render churn that used to keep the
+ // popup mounted. This guards that blurOnSelect closes them despite that churn.
+ it('closes a time-range autocomplete after selecting an option (#16)', async () => {
+ const user = userEvent.setup();
+ render();
+
+ // `0 */4 * * *` puts the hour field in "every" mode, revealing the
+ // between/and time-range selects (start defaults to 12:00 AM, end 11:00 PM).
+ const startInput = await screen.findByDisplayValue('12:00 AM');
+ await user.click(startInput);
+
+ const listbox = await screen.findByRole('listbox');
+ await user.click(within(listbox).getByText('02:00 AM'));
+
+ await waitFor(() => expect(screen.queryByRole('listbox')).not.toBeInTheDocument());
+ expect(screen.getByDisplayValue('02:00 AM')).toBeInTheDocument();
+ });
+
+ // #18: the linked upstream bug (mui/material-ui#27501) was a MUI 5 *beta*
+ // regression. On MUI v7 it does not reproduce; these assertions lock in the
+ // two styling behaviours the library actually maintains so a future MUI bump
+ // can't silently regress them.
+ it('keeps disabled cron-input text legible on the dark field (#18)', async () => {
+ render();
+ const input = (await screen.findByDisplayValue('0 0 * * *')) as HTMLInputElement;
+ expect(input).toBeDisabled();
+ // The component forces white text-fill on the disabled (read-only) input.
+ expect(getComputedStyle(input).webkitTextFillColor).toBe('rgb(255, 255, 255)');
+ });
+
+ it('renders multi-select values as chips (#18)', async () => {
+ render();
+ await waitFor(() => expect(periodValue()).toBe('week'), { timeout: 3000 });
+ // Mon–Fri render as MUI chips inside the Week Days field.
+ expect(document.querySelectorAll('.MuiChip-root').length).toBeGreaterThan(0);
+ });
+
+ // #17 kitchen-sink coverage: a provided locale localizes the field labels.
+ it('renders localized labels for a provided locale (zh_CN)', async () => {
+ render();
+ // Period label in zh_CN.
+ await waitFor(() => expect(screen.getByLabelText('时间间隔')).toBeInTheDocument(), {
+ timeout: 3000,
+ });
+ });
+
+ // Regression: switching locale left the *selected value* one language behind
+ // (period read "day" under Chinese; switching back read "天" under English)
+ // because the shared atoms kept their stale localized labels. The fix
+ // re-localizes selections onto the new locale's matching option by value.
+ // Found by /qa on 2026-05-29.
+ it('re-localizes the selected period when the locale changes (no stale lag)', async () => {
+ const periodValueZh = () =>
+ (screen.getByLabelText('时间间隔') as HTMLInputElement).value;
+
+ const { rerender } = render(
+ ,
+ );
+ // Month-level cron -> period reads "month" in English.
+ await waitFor(() => expect(periodValue()).toBe('month'), { timeout: 3000 });
+
+ // Switch to Chinese: the selection is preserved and its label translates.
+ rerender();
+ await waitFor(() => expect(periodValueZh()).toBe('月'), { timeout: 3000 });
+
+ // Switch back to English: no Chinese lag — it reads "month" again.
+ rerender();
+ await waitFor(() => expect(periodValue()).toBe('month'), { timeout: 3000 });
+ });
+});
diff --git a/src/selector.test.ts b/src/selector.test.ts
new file mode 100644
index 0000000..ad30fa8
--- /dev/null
+++ b/src/selector.test.ts
@@ -0,0 +1,232 @@
+import { createStore } from 'jotai';
+import { beforeEach, describe, expect, it } from 'vitest';
+import {
+ DEFAULT_DAY_OF_MONTH_OPTS,
+ DEFAULT_HOUR_OPTS_EVERY,
+ DEFAULT_MINUTE_OPTS,
+ getPeriodOptions,
+} from './constants';
+import {
+ cronExpState,
+ dayOfMonthCronState,
+ dayOfWeekCronState,
+ hourCronState,
+ minuteCronState,
+ monthCronState,
+} from './selector';
+import {
+ dayOfMonthState,
+ hourState,
+ minuteAtEveryState,
+ minuteRangeEndSchedulerState,
+ minuteRangeStartSchedulerState,
+ minuteState,
+ monthState,
+ periodState,
+ weekState,
+} from './store';
+import type { SelectOptions } from './types';
+
+type Store = ReturnType;
+let store: Store;
+
+const en = ['hour', 'day', 'week', 'month', 'year'];
+const period = (value: string): SelectOptions => getPeriodOptions(en)[en.indexOf(value)];
+
+beforeEach(() => {
+ store = createStore();
+});
+
+describe('minuteCronState', () => {
+ it('is `*` when every minute is selected', () => {
+ store.set(minuteState, DEFAULT_MINUTE_OPTS);
+ expect(store.get(minuteCronState)).toBe('*');
+ });
+
+ it('emits a single minute', () => {
+ store.set(minuteState, [DEFAULT_MINUTE_OPTS[5]]);
+ expect(store.get(minuteCronState)).toBe('5');
+ });
+
+ it('emits a comma list for a non-contiguous selection', () => {
+ store.set(minuteState, [DEFAULT_MINUTE_OPTS[1], DEFAULT_MINUTE_OPTS[5]]);
+ expect(store.get(minuteCronState)).toBe('1,5');
+ });
+
+ it('emits a hyphen range for a contiguous selection', () => {
+ store.set(minuteState, [
+ DEFAULT_MINUTE_OPTS[1],
+ DEFAULT_MINUTE_OPTS[2],
+ DEFAULT_MINUTE_OPTS[3],
+ ]);
+ expect(store.get(minuteCronState)).toBe('1-3');
+ });
+});
+
+describe('minuteCronState (every / step)', () => {
+ const every: SelectOptions = { value: 'every', label: 'every' };
+
+ it('emits `*/n` when the range spans the whole hour', () => {
+ store.set(minuteAtEveryState, every);
+ store.set(minuteRangeStartSchedulerState, { value: '0', label: '0' });
+ store.set(minuteRangeEndSchedulerState, { value: '59', label: '59' });
+ store.set(minuteState, [DEFAULT_MINUTE_OPTS[5]]);
+ expect(store.get(minuteCronState)).toBe('*/5');
+ });
+
+ it('emits `start-end/n` for a partial range', () => {
+ store.set(minuteAtEveryState, every);
+ store.set(minuteRangeStartSchedulerState, { value: '0', label: '0' });
+ store.set(minuteRangeEndSchedulerState, { value: '10', label: '10' });
+ store.set(minuteState, [DEFAULT_MINUTE_OPTS[5]]);
+ expect(store.get(minuteCronState)).toBe('0-10/5');
+ });
+});
+
+describe('dayOfMonthCronState (ranges, lists, last day)', () => {
+ it('emits a hyphen range', () => {
+ store.set(periodState, period('month'));
+ store.set(
+ dayOfMonthState,
+ [1, 2, 3].map((d) => ({ value: String(d), label: String(d) })),
+ );
+ expect(store.get(dayOfMonthCronState)).toBe('1-3');
+ });
+
+ it('emits a comma list', () => {
+ store.set(periodState, period('month'));
+ store.set(
+ dayOfMonthState,
+ [1, 5, 9].map((d) => ({ value: String(d), label: String(d) })),
+ );
+ expect(store.get(dayOfMonthCronState)).toBe('1,5,9');
+ });
+
+ it('emits L for the last day of month', () => {
+ store.set(periodState, period('month'));
+ store.set(dayOfMonthState, [{ value: 'L', label: 'Last day of month' }]);
+ expect(store.get(dayOfMonthCronState)).toBe('L');
+ });
+});
+
+describe('monthCronState (year period)', () => {
+ it('emits a hyphen range', () => {
+ store.set(periodState, period('year'));
+ store.set(
+ monthState,
+ [1, 2, 3].map((m) => ({ value: String(m), label: String(m) })),
+ );
+ expect(store.get(monthCronState)).toBe('1-3');
+ });
+
+ it('emits a comma list', () => {
+ store.set(periodState, period('year'));
+ store.set(
+ monthState,
+ [1, 6, 12].map((m) => ({ value: String(m), label: String(m) })),
+ );
+ expect(store.get(monthCronState)).toBe('1,6,12');
+ });
+});
+
+describe('dayOfWeekCronState (ranges and lists)', () => {
+ it('emits a hyphen range', () => {
+ store.set(periodState, period('week'));
+ store.set(
+ weekState,
+ [1, 2, 3, 4, 5].map((d) => ({ value: String(d), label: String(d) })),
+ );
+ expect(store.get(dayOfWeekCronState)).toBe('1-5');
+ });
+
+ it('emits a comma list', () => {
+ store.set(periodState, period('week'));
+ store.set(
+ weekState,
+ [1, 3, 5].map((d) => ({ value: String(d), label: String(d) })),
+ );
+ expect(store.get(dayOfWeekCronState)).toBe('1,3,5');
+ });
+});
+
+describe('hourCronState', () => {
+ it('is `*` when the period is narrower than a day', () => {
+ store.set(periodState, period('hour'));
+ expect(store.get(hourCronState)).toBe('*');
+ });
+
+ it('emits a single hour at day period', () => {
+ store.set(periodState, period('day'));
+ store.set(hourState, [DEFAULT_HOUR_OPTS_EVERY[9]]);
+ expect(store.get(hourCronState)).toBe('9');
+ });
+});
+
+describe('dayOfMonthCronState', () => {
+ it('is `*` until the period reaches month', () => {
+ store.set(periodState, period('week'));
+ expect(store.get(dayOfMonthCronState)).toBe('*');
+ });
+
+ it('emits a single day at month period', () => {
+ store.set(periodState, period('month'));
+ store.set(dayOfMonthState, [DEFAULT_DAY_OF_MONTH_OPTS[4]]); // 5th
+ expect(store.get(dayOfMonthCronState)).toBe('5');
+ });
+});
+
+describe('monthCronState', () => {
+ it('is `*` below the year period', () => {
+ store.set(periodState, period('month'));
+ expect(store.get(monthCronState)).toBe('*');
+ });
+});
+
+describe('dayOfWeekCronState', () => {
+ it('is `*` below the week period', () => {
+ store.set(periodState, period('day'));
+ expect(store.get(dayOfWeekCronState)).toBe('*');
+ });
+
+ it('emits a single weekday at week period', () => {
+ store.set(periodState, period('week'));
+ store.set(weekState, [{ value: '1', label: 'Monday' }]);
+ expect(store.get(dayOfWeekCronState)).toBe('1');
+ });
+});
+
+describe('cronExpState (read getter composition)', () => {
+ it('joins the five derived parts', () => {
+ expect(store.get(cronExpState)).toBe('0 0 * * *');
+ });
+
+ it('reflects field + period state', () => {
+ store.set(periodState, period('week'));
+ store.set(minuteState, [DEFAULT_MINUTE_OPTS[30]]);
+ store.set(hourState, [DEFAULT_HOUR_OPTS_EVERY[9]]);
+ store.set(weekState, [{ value: '1', label: 'Monday' }]);
+ expect(store.get(cronExpState)).toBe('30 9 * * 1');
+ });
+});
+
+describe('cronExpState writer (parsing into field atoms)', () => {
+ // The period atom is `atomWithDefault`; its override does not persist in an
+ // unmounted Node store (it persists once mounted, as in the browser). So these
+ // assertions check the plain field atoms that the writer parses into.
+ it('parses minutes/hours into the field atoms', () => {
+ store.set(cronExpState, '30 9 * * *');
+ expect(store.get(minuteState).map((m) => m.value)).toEqual(['30']);
+ expect(store.get(hourState).map((h) => h.value)).toEqual(['9']);
+ });
+
+ it('parses a day-of-month value', () => {
+ store.set(cronExpState, '0 0 5 * *');
+ expect(store.get(dayOfMonthState).map((d) => d.value)).toEqual(['5']);
+ });
+
+ it('does not mutate field atoms for an invalid expression', () => {
+ store.set(minuteState, [DEFAULT_MINUTE_OPTS[7]]);
+ store.set(cronExpState, '99 99 * * *'); // invalid
+ expect(store.get(minuteState).map((m) => m.value)).toEqual(['7']);
+ });
+});
diff --git a/src/selector.ts b/src/selector.ts
index a1b746f..dadceeb 100644
--- a/src/selector.ts
+++ b/src/selector.ts
@@ -68,7 +68,7 @@ export const hourCronState = atom((get) => {
if (
getPeriodIndex(get(periodState)) < 1 ||
hours.map((hour) => hour.value).join('') ===
- DEFAULT_HOUR_OPTS_EVERY.map((hour) => hour.value).join('')
+ DEFAULT_HOUR_OPTS_EVERY.map((hour) => hour.value).join('')
) {
return '*';
} else if (get(hourAtEveryState).value === 'every') {
@@ -90,7 +90,7 @@ export const dayOfMonthCronState = atom((get) => {
if (
getPeriodIndex(get(periodState)) < 3 ||
dayOfMonth.map((dayOfMonth) => dayOfMonth.value).join('') ===
- DEFAULT_DAY_OF_MONTH_OPTS.map((day) => day.value).join('')
+ DEFAULT_DAY_OF_MONTH_OPTS.map((day) => day.value).join('')
) {
return '*';
} else if (get(dayOfMonthAtEveryState).value === 'every') {
@@ -99,9 +99,7 @@ export const dayOfMonthCronState = atom((get) => {
if (Number(endIndex) - Number(startIndex) === 30) {
return `*/${dayOfMonth.map((dayOfMonth) => dayOfMonth.value).join('')}`;
}
- return `${startIndex}-${endIndex}/${dayOfMonth
- .map((dayOfMonth) => dayOfMonth.value)
- .join('')}`;
+ return `${startIndex}-${endIndex}/${dayOfMonth.map((dayOfMonth) => dayOfMonth.value).join('')}`;
} else if (dayOfMonth[0].value === 'L') {
return 'L';
} else if (isIncreasingSequence(dayOfMonth) && dayOfMonth.length !== 1) {
@@ -118,9 +116,9 @@ export const monthCronState = atom((get) => {
get(monthState)
.map((month) => month.value)
.join('') ===
- getMonthOptions(get(localeState).shortMonthOptions)
- .map((month) => month.value)
- .join('')
+ getMonthOptions(get(localeState).shortMonthOptions)
+ .map((month) => month.value)
+ .join('')
) {
return '*';
} else if (isIncreasingSequence(months) && months.length !== 1) {
@@ -162,22 +160,34 @@ export const cronExpState = atom(
const res = validateCronExp(next);
set(cronValidationErrorMessageState, res.hasError ? res.message : '');
if (!res.hasError) {
+ // Snapshot the cron currently implied by the field/period atoms *before*
+ // regenerating them. If the incoming value matches, this is a round-trip
+ // from a manual field/period edit and we must not re-derive the period
+ // (otherwise selecting a broad period on default fields would snap back).
+ const currentDerived = get(cronExpState);
const cronParts = next.split(' ');
generateMinute(cronParts[0], get(localeState), set as Setter);
generateHour(cronParts[1], get(localeState), set as Setter);
generateDayOfMonth(cronParts[2], get(localeState), set as Setter);
generateMonth(cronParts[3], get(localeState), set as Setter);
generateWeek(cronParts[4], get(localeState), set as Setter);
- const period = get(periodState);
- const periodOptions = getPeriodOptions(get(localeState).periodOptions);
- if (cronParts[3] !== '*' && getPeriodIndex(period) < 4) {
- set(periodState, periodOptions[4]);
- } else if (cronParts[2] !== '*' && getPeriodIndex(period) < 3) {
- set(periodState, periodOptions[3]);
- } else if (cronParts[4] !== '*' && getPeriodIndex(period) < 2) {
- set(periodState, periodOptions[2]);
- } else if (cronParts[1] !== '*' && getPeriodIndex(period) < 1) {
- set(periodState, periodOptions[1]);
+ if (next !== currentDerived) {
+ // External (prop/text) change: derive the period from the highest
+ // non-`*` segment and set it unconditionally so going from a narrow cron
+ // (e.g. `30 9 * * 1`) to a broad one (`* * * * *`) resets it instead of
+ // staying stuck on the old, now-stale period.
+ const periodOptions = getPeriodOptions(get(localeState).periodOptions);
+ let periodIndex = 0;
+ if (cronParts[3] !== '*') {
+ periodIndex = 4;
+ } else if (cronParts[2] !== '*') {
+ periodIndex = 3;
+ } else if (cronParts[4] !== '*') {
+ periodIndex = 2;
+ } else if (cronParts[1] !== '*') {
+ periodIndex = 1;
+ }
+ set(periodState, periodOptions[periodIndex]);
}
}
},
@@ -197,6 +207,10 @@ const generateMinute = (part: string, locale: Locale, set: Setter) => {
} else if (subparts[0] === '*') {
set(minuteRangeStartSchedulerState, defaultMinuteOptionsWithOrdinal()[0]);
set(minuteRangeEndSchedulerState, defaultMinuteOptionsWithOrdinal()[59]);
+ } else {
+ // Plain `n/m` (e.g. 1/4): start at n, end at the max.
+ set(minuteRangeStartSchedulerState, defaultMinuteOptionsWithOrdinal()[Number(subparts[0])]);
+ set(minuteRangeEndSchedulerState, defaultMinuteOptionsWithOrdinal()[59]);
}
set(minuteState, [DEFAULT_MINUTE_OPTS[Number(subparts[1])]]);
} else if (part.indexOf('-') > 0) {
@@ -232,6 +246,10 @@ const generateHour = (part: string, locale: Locale, set: Setter) => {
} else if (subparts[0] === '*') {
set(hourRangeStartSchedulerState, getTimesOfTheDay()[0]);
set(hourRangeEndSchedulerState, getTimesOfTheDay()[23]);
+ } else {
+ // Plain `n/m` (e.g. 1/4): start at n, end at the max.
+ set(hourRangeStartSchedulerState, getTimesOfTheDay()[Number(subparts[0])]);
+ set(hourRangeEndSchedulerState, getTimesOfTheDay()[23]);
}
set(hourState, [DEFAULT_HOUR_OPTS_EVERY[Number(subparts[1])]]);
} else if (part.indexOf('-') > 0) {
@@ -273,6 +291,13 @@ const generateDayOfMonth = (part: string, locale: Locale, set: Setter) => {
} else if (subparts[0] === '*') {
set(dayOfMonthRangeStartSchedulerState, DEFAULT_DAY_OF_MONTH_OPTS_WITH_ORD[0]);
set(dayOfMonthRangeEndSchedulerState, DEFAULT_DAY_OF_MONTH_OPTS_WITH_ORD[30]);
+ } else {
+ // Plain `n/m` (e.g. 1/4): start at n, end at the max.
+ set(
+ dayOfMonthRangeStartSchedulerState,
+ DEFAULT_DAY_OF_MONTH_OPTS_WITH_ORD[Number(subparts[0]) - 1],
+ );
+ set(dayOfMonthRangeEndSchedulerState, DEFAULT_DAY_OF_MONTH_OPTS_WITH_ORD[30]);
}
set(dayOfMonthState, [DEFAULT_DAY_OF_MONTH_OPTS[Number(subparts[1]) - 1]]);
} else if (part === 'L') {
diff --git a/src/stories/scheduler.stories.tsx b/src/stories/scheduler.stories.tsx
deleted file mode 100644
index d3720d1..0000000
--- a/src/stories/scheduler.stories.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { expect, userEvent, waitFor, within } from '@storybook/test';
-import React from 'react';
-import Scheduler from '../index';
-
-export default {
- title: 'Material UI Cron',
- component: SchedulerDemo,
-};
-
-export function SchedulerDemo() {
- const [cronExp, setCronExp] = React.useState('0 0 * * *');
- const [, setCronError] = React.useState('');
- const [isAdmin] = React.useState(true);
-
- return (
-
- );
-}
-
-export const Default = () => ;
-
-Default.play = async ({ canvasElement }: { canvasElement: HTMLElement }) => {
- const canvas = within(canvasElement);
-
- // Verify default cron expression is displayed
- const cronInput = await canvas.findByDisplayValue('0 0 * * *');
- expect(cronInput).toBeInTheDocument();
-
- // Verify key fields exist
- expect(await canvas.findByLabelText('Period')).toHaveValue('day');
- expect(await canvas.findByLabelText('Minute(s)')).toBeInTheDocument();
- expect(await canvas.findByLabelText('Hour(s)')).toBeInTheDocument();
-};
-
-function SchedulerNonAdminDemo() {
- const [cronExp, setCronExp] = React.useState('0 0 * * *');
- const [, setCronError] = React.useState('');
- return (
-
- );
-}
-
-export const NonAdmin = () => ;
-
-NonAdmin.play = async ({ canvasElement }: { canvasElement: HTMLElement }) => {
- const canvas = within(canvasElement);
-
- // Verify cron input is disabled for non-admin
- const cronInput = await canvas.findByDisplayValue('0 0 * * *');
- expect(cronInput).toBeDisabled();
-};
-
-function SchedulerPropChangeDemo() {
- const [cronExp, setCronExp] = React.useState('0 0 * * *');
- const [, setCronError] = React.useState('');
- return (
-
-
-
-
- );
-}
-
-export const PropChange = () => ;
-
-PropChange.play = async ({ canvasElement }: { canvasElement: HTMLElement }) => {
- const canvas = within(canvasElement);
- const user = userEvent.setup();
-
- // Test that external prop changes sync to the component
- const btn = await canvas.findByRole('button', { name: 'Apply */5 * * * *' });
- await user.click(btn);
- await waitFor(() => expect(canvas.getByDisplayValue('*/5 * * * *')).toBeInTheDocument());
-};
diff --git a/src/utils.test.ts b/src/utils.test.ts
new file mode 100644
index 0000000..aa4ae10
--- /dev/null
+++ b/src/utils.test.ts
@@ -0,0 +1,265 @@
+import { describe, expect, it } from 'vitest';
+import type { SelectOptions } from './types';
+import {
+ countOccurrences,
+ doesNumberStartWithZero,
+ getNumbersInCronPart,
+ getPeriodIndex,
+ getSortedOptions,
+ hasNoDuplicates,
+ hasValidCronParts,
+ hasValidNumbersInCronPart,
+ isAscending,
+ isIncreasingSequence,
+ isValidDayOfMonthPart,
+ isValidDayOfWeekPart,
+ isValidHourPart,
+ isValidMinutePart,
+ isValidMonthPart,
+ isValidStepPart,
+ range,
+ validateCronExp,
+} from './utils';
+
+const opt = (value: string): SelectOptions => ({ value, label: value });
+
+describe('hasValidCronParts', () => {
+ it('accepts exactly five space-separated parts', () => {
+ expect(hasValidCronParts('* * * * *')).toBe(true);
+ expect(hasValidCronParts('0 0 1 1 0')).toBe(true);
+ });
+
+ it('rejects the wrong number of parts', () => {
+ expect(hasValidCronParts('* * * *')).toBe(false);
+ expect(hasValidCronParts('* * * * * *')).toBe(false);
+ });
+});
+
+describe('getNumbersInCronPart', () => {
+ it('extracts every numeric token', () => {
+ expect(getNumbersInCronPart('1-10/4')).toEqual([1, 10, 4]);
+ expect(getNumbersInCronPart('1,2,3')).toEqual([1, 2, 3]);
+ expect(getNumbersInCronPart('*')).toEqual([]);
+ expect(getNumbersInCronPart('5')).toEqual([5]);
+ });
+});
+
+describe('doesNumberStartWithZero', () => {
+ it('flags multi-digit numbers with a leading zero', () => {
+ expect(doesNumberStartWithZero('01')).toBe(true);
+ expect(doesNumberStartWithZero('00')).toBe(true);
+ expect(doesNumberStartWithZero('0-05')).toBe(true);
+ });
+
+ it('does not flag a bare zero or normal numbers', () => {
+ expect(doesNumberStartWithZero('0')).toBe(false);
+ expect(doesNumberStartWithZero('10')).toBe(false);
+ expect(doesNumberStartWithZero('1-10')).toBe(false);
+ });
+});
+
+describe('hasNoDuplicates', () => {
+ it('detects duplicate numbers within a list', () => {
+ expect(hasNoDuplicates('1,2,3')).toBe(true);
+ expect(hasNoDuplicates('1,2,2')).toBe(false);
+ });
+
+ it('treats slash sub-parts independently', () => {
+ expect(hasNoDuplicates('1-10/4')).toBe(true);
+ });
+});
+
+describe('hasValidNumbersInCronPart', () => {
+ it('applies the predicate to every number', () => {
+ expect(hasValidNumbersInCronPart('1,2,3', (n) => n >= 0 && n <= 59)).toBe(true);
+ expect(hasValidNumbersInCronPart('1,60', (n) => n >= 0 && n <= 59)).toBe(false);
+ });
+});
+
+describe('isAscending', () => {
+ it('is true for non-decreasing sequences', () => {
+ expect(isAscending(['1', '2', '2', '3'])).toBe(true);
+ expect(isAscending(['5'])).toBe(true);
+ });
+
+ it('is false when a later value is smaller', () => {
+ expect(isAscending(['3', '1'])).toBe(false);
+ });
+});
+
+describe('isIncreasingSequence', () => {
+ it('is true for a contiguous run', () => {
+ expect(isIncreasingSequence([opt('1'), opt('2'), opt('3')])).toBe(true);
+ });
+
+ it('is false when there is a gap', () => {
+ expect(isIncreasingSequence([opt('1'), opt('3')])).toBe(false);
+ });
+});
+
+describe('getSortedOptions', () => {
+ it('sorts options numerically by value', () => {
+ expect(getSortedOptions([opt('10'), opt('2'), opt('1')]).map((o) => o.value)).toEqual([
+ '1',
+ '2',
+ '10',
+ ]);
+ });
+});
+
+describe('getPeriodIndex', () => {
+ it('maps period values to their index', () => {
+ expect(getPeriodIndex(opt('hour'))).toBe(0);
+ expect(getPeriodIndex(opt('day'))).toBe(1);
+ expect(getPeriodIndex(opt('week'))).toBe(2);
+ expect(getPeriodIndex(opt('month'))).toBe(3);
+ expect(getPeriodIndex(opt('year'))).toBe(4);
+ });
+});
+
+describe('countOccurrences', () => {
+ it('counts matching entries', () => {
+ expect(countOccurrences(['a', 'b', 'a'], 'a')).toBe(2);
+ expect(countOccurrences([], 'a')).toBe(0);
+ });
+});
+
+describe('range', () => {
+ it('builds an inclusive string range', () => {
+ expect(range(0, 4)).toEqual(['0', '1', '2', '3', '4']);
+ });
+
+ it('honours a custom step', () => {
+ expect(range(0, 10, 5)).toEqual(['0', '5', '10']);
+ });
+});
+
+describe('isValidStepPart (#19 step validation)', () => {
+ it('accepts `*/n`', () => {
+ expect(isValidStepPart('*/4').isValid).toBe(true);
+ expect(isValidStepPart('*/15').isValid).toBe(true);
+ });
+
+ it('accepts plain `n/m` — the #19 regression', () => {
+ expect(isValidStepPart('1/4').isValid).toBe(true);
+ expect(isValidStepPart('5/15').isValid).toBe(true);
+ });
+
+ it('keeps the existing step-value constraint (no `0` digit in the step)', () => {
+ // REGEX_EVERY's step group is [1-9]{1,2}, so steps like 10/20 are rejected.
+ // This is pre-existing behaviour, documented here so the boundary is intentional.
+ expect(isValidStepPart('5/10').isValid).toBe(false);
+ });
+
+ it('accepts an ascending `x-y/n` range step', () => {
+ expect(isValidStepPart('1-10/4').isValid).toBe(true);
+ });
+
+ it('rejects a descending range step', () => {
+ const res = isValidStepPart('10-1/4');
+ expect(res.isValid).toBe(false);
+ expect(res.message).toBe('Incorrect range');
+ });
+
+ it('rejects malformed step parts', () => {
+ expect(isValidStepPart('1/').isValid).toBe(false);
+ expect(isValidStepPart('*/0').isValid).toBe(false);
+ });
+});
+
+describe('isValidMinutePart', () => {
+ it.each(['*', '5', '0', '1,2,3', '1-30', '*/5', '0/15', '1-30/5'])('accepts %s', (part) => {
+ expect(isValidMinutePart(`${part} * * * *`).isValid).toBe(true);
+ });
+
+ it('rejects out-of-range minutes', () => {
+ expect(isValidMinutePart('60 * * * *').isValid).toBe(false);
+ });
+
+ it('rejects leading zero', () => {
+ expect(isValidMinutePart('05 * * * *').isValid).toBe(false);
+ });
+
+ it('rejects duplicates', () => {
+ expect(isValidMinutePart('1,1 * * * *').isValid).toBe(false);
+ });
+
+ it('rejects a descending range', () => {
+ expect(isValidMinutePart('30-10 * * * *').isValid).toBe(false);
+ });
+});
+
+describe('isValidHourPart', () => {
+ it.each(['*', '9', '1/4', '*/4', '1-10/4'])('accepts %s', (part) => {
+ expect(isValidHourPart(`0 ${part} * * *`).isValid).toBe(true);
+ });
+
+ it('rejects out-of-range hours', () => {
+ expect(isValidHourPart('0 24 * * *').isValid).toBe(false);
+ });
+});
+
+describe('isValidDayOfMonthPart', () => {
+ it.each(['*', '1', '15', 'L', '1-15', '1/5'])('accepts %s', (part) => {
+ expect(isValidDayOfMonthPart(`0 0 ${part} * *`).isValid).toBe(true);
+ });
+
+ it('rejects day 0 and out-of-range days', () => {
+ expect(isValidDayOfMonthPart('0 0 0 * *').isValid).toBe(false);
+ expect(isValidDayOfMonthPart('0 0 32 * *').isValid).toBe(false);
+ });
+});
+
+describe('isValidMonthPart', () => {
+ it.each(['*', '1', '12', '1,6,12', '1-6'])('accepts %s', (part) => {
+ expect(isValidMonthPart(`0 0 1 ${part} *`).isValid).toBe(true);
+ });
+
+ it('rejects out-of-range months', () => {
+ expect(isValidMonthPart('0 0 1 13 *').isValid).toBe(false);
+ });
+});
+
+describe('isValidDayOfWeekPart', () => {
+ it.each(['*', '0', '6', '1-5', '1,3,5'])('accepts %s', (part) => {
+ expect(isValidDayOfWeekPart(`0 0 * * ${part}`).isValid).toBe(true);
+ });
+
+ it('rejects out-of-range weekdays', () => {
+ expect(isValidDayOfWeekPart('0 0 * * 7').isValid).toBe(false);
+ });
+});
+
+describe('validateCronExp (end-to-end)', () => {
+ const valid = [
+ '* * * * *',
+ '0 0 * * *',
+ '0 1/4 * * *', // #19
+ '*/5 * * * *',
+ '0 */4 * * *',
+ '0 1-10/4 * * *',
+ '0 0 1/5 * *',
+ '0 0 1-15 * *',
+ '30 9 * * 1',
+ '0 0 L * *',
+ '0 0 1 1 0',
+ ];
+ it.each(valid)('accepts %s', (cron) => {
+ expect(validateCronExp(cron).hasError).toBe(false);
+ });
+
+ const invalid: [string, string][] = [
+ ['0 1/4 * *', 'Cron should have 5 parts'],
+ ['60 * * * *', 'Invalid minute cron part: Number should be between 0 and 59'],
+ ['0 24 * * *', 'Invalid hour cron part: Number should be between 0 and 23'],
+ ['0 10-1/4 * * *', 'Invalid hour cron part: Incorrect range'],
+ ['0 0 32 * *', 'Invalid day of month cron part: Number should be between 1 and 31'],
+ ['0 0 1 13 *', 'Invalid month cron part: Number should be between 0 and 6'],
+ ['0 0 * * 7', 'Invalid day of week cron part: Number should be between 0 and 6'],
+ ];
+ it.each(invalid)('rejects %s', (cron, message) => {
+ const res = validateCronExp(cron);
+ expect(res.hasError).toBe(true);
+ expect(res.message).toBe(message);
+ });
+});
diff --git a/src/utils.ts b/src/utils.ts
index 0bbd960..f411280 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -126,6 +126,24 @@ export const CRON_VALIDATION = (isValid: boolean, message: string): CronValidati
};
};
+// Validates a step part (`a/b`) by branching on what comes before the slash:
+// `*` -> `*/n` (REGEX_ALL, e.g. */4)
+// `x-y` -> `x-y/n` (REGEX_EVERY_HYPEN, e.g. 1-10/4) when ascending
+// `n` -> `n/m` (REGEX_EVERY, e.g. 1/4)
+// The minute/hour/day-of-month parts share identical step semantics.
+export const isValidStepPart = (part: string): CronValidation => {
+ const beforeSlash = part.split('/')[0];
+ if (beforeSlash === '*') {
+ return CRON_VALIDATION(REGEX_ALL.test(part), 'Incorrect syntax');
+ } else if (beforeSlash.indexOf('-') > 0) {
+ if (isAscending(beforeSlash.split('-'))) {
+ return CRON_VALIDATION(REGEX_EVERY_HYPEN.test(part), 'Incorrect syntax hypen');
+ }
+ return CRON_VALIDATION(false, 'Incorrect range');
+ }
+ return CRON_VALIDATION(REGEX_EVERY.test(part), 'Incorrect syntax');
+};
+
export const isValidMinutePart = (cronExp: string) => {
const part = cronExp.split(' ')[0];
if (doesNumberStartWithZero(part)) {
@@ -135,13 +153,7 @@ export const isValidMinutePart = (cronExp: string) => {
} else if (!hasNoDuplicates(part)) {
return CRON_VALIDATION(false, 'Duplicate numbers not allowed');
} else if (part.indexOf('/') > 0) {
- if (part.split('/')[0] !== '*' && isAscending(part.split('/')[0].split('-'))) {
- return CRON_VALIDATION(REGEX_EVERY_HYPEN.test(part), 'Incorrect syntax hypen');
- } else if (part.indexOf('-') > 0) {
- return CRON_VALIDATION(false, 'Incorrect range');
- } else {
- return CRON_VALIDATION(REGEX_ALL.test(part), 'Incorrect syntax');
- }
+ return isValidStepPart(part);
} else if (part.indexOf(',') > 0) {
return CRON_VALIDATION(REGEX_COMMA.test(part), 'Invalid syntax');
} else if (part.indexOf('-') > 0) {
@@ -165,13 +177,7 @@ export const isValidHourPart = (cronExp: string) => {
} else if (!hasNoDuplicates(part)) {
return CRON_VALIDATION(false, 'Duplicate numbers not allowed');
} else if (part.indexOf('/') > 0) {
- if (part.split('/')[0] !== '*' && isAscending(part.split('/')[0].split('-'))) {
- return CRON_VALIDATION(REGEX_EVERY_HYPEN.test(part), 'Incorrect syntax hypen');
- } else if (part.indexOf('-') > 0) {
- return CRON_VALIDATION(false, 'Incorrect range');
- } else {
- return CRON_VALIDATION(REGEX_ALL.test(part), 'Incorrect syntax');
- }
+ return isValidStepPart(part);
} else if (part.indexOf(',') > 0) {
return CRON_VALIDATION(REGEX_COMMA.test(part), 'Invalid syntax');
} else if (part.indexOf('-') > 0) {
@@ -195,13 +201,7 @@ export const isValidDayOfMonthPart = (cronExp: string) => {
} else if (!hasNoDuplicates(part)) {
return CRON_VALIDATION(false, 'Duplicate numbers not allowed');
} else if (part.indexOf('/') > 0) {
- if (part.split('/')[0] !== '*' && isAscending(part.split('/')[0].split('-'))) {
- return CRON_VALIDATION(REGEX_EVERY_HYPEN.test(part), 'Incorrect syntax hypen');
- } else if (part.indexOf('-') > 0) {
- return CRON_VALIDATION(false, 'Incorrect range');
- } else {
- return CRON_VALIDATION(REGEX_ALL.test(part), 'Incorrect syntax');
- }
+ return isValidStepPart(part);
} else if (part.indexOf(',') > 0) {
return CRON_VALIDATION(REGEX_COMMA.test(part), 'Invalid syntax');
} else if (part.indexOf('-') > 0) {
diff --git a/tsconfig.json b/tsconfig.json
index 64b5c3c..2a03327 100755
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -22,5 +22,5 @@
"noEmit": false
},
"include": ["./src/**/*"],
- "exclude": ["node_modules", "dist", "storybook-static"]
+ "exclude": ["node_modules", "dist", "demo"]
}
diff --git a/vercel.json b/vercel.json
new file mode 100644
index 0000000..78d81bb
--- /dev/null
+++ b/vercel.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "https://openapi.vercel.sh/vercel.json",
+ "framework": "vite",
+ "installCommand": "yarn install --frozen-lockfile && yarn --cwd demo install",
+ "buildCommand": "yarn --cwd demo build",
+ "outputDirectory": "demo/dist",
+ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
+}
diff --git a/vitest.config.mts b/vitest.config.mts
new file mode 100644
index 0000000..360a426
--- /dev/null
+++ b/vitest.config.mts
@@ -0,0 +1,49 @@
+///
+import react from '@vitejs/plugin-react';
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ test: {
+ projects: [
+ {
+ // Pure-logic unit tests (cron validation, selector derivations) run in Node.
+ test: {
+ name: 'unit',
+ environment: 'node',
+ include: ['src/**/*.test.ts'],
+ exclude: ['src/**/*.browser.test.*'],
+ },
+ },
+ {
+ // Component tests render the real in a real browser. jsdom is not
+ // viable here — MUI's Autocomplete triggers an infinite update loop under jsdom.
+ plugins: [react()],
+ test: {
+ name: 'browser',
+ include: ['src/**/*.browser.test.tsx'],
+ setupFiles: ['./vitest.setup.browser.ts'],
+ browser: {
+ enabled: true,
+ provider: 'playwright',
+ headless: true,
+ instances: [{ browser: 'chromium' }],
+ },
+ },
+ },
+ ],
+ coverage: {
+ provider: 'v8',
+ include: ['src/**/*.{ts,tsx}'],
+ exclude: [
+ 'src/**/*.test.*',
+ 'src/**/*.browser.test.*',
+ 'src/**/*.stories.*',
+ 'src/**/*.d.ts',
+ 'src/index.ts',
+ 'src/types.ts',
+ ],
+ reporter: ['text', 'text-summary', 'html', 'json-summary'],
+ reportsDirectory: './coverage',
+ },
+ },
+});
diff --git a/vitest.setup.browser.ts b/vitest.setup.browser.ts
new file mode 100644
index 0000000..bb02c60
--- /dev/null
+++ b/vitest.setup.browser.ts
@@ -0,0 +1 @@
+import '@testing-library/jest-dom/vitest';
diff --git a/yarn.lock b/yarn.lock
index f2e8d8d..3322404 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7,6 +7,14 @@
resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.4.tgz#2856c55443d3d461693f32d2b96fb6ea92e1ffa9"
integrity sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==
+"@ampproject/remapping@^2.3.0":
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4"
+ integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
"@babel/code-frame@^7.0.0":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb"
@@ -103,6 +111,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687"
integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
+"@babel/helper-string-parser@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz#7f0871d99824d23137d60f86fcf6130fd5a1b51f"
+ integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==
+
"@babel/helper-validator-identifier@^7.14.5":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz#d0f0e277c512e0c938277faa85a3968c9a44c0e8"
@@ -113,6 +126,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8"
integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==
+"@babel/helper-validator-identifier@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2"
+ integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==
+
"@babel/helper-validator-option@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f"
@@ -142,6 +160,13 @@
dependencies:
"@babel/types" "^7.28.4"
+"@babel/parser@^7.25.4":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334"
+ integrity sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==
+ dependencies:
+ "@babel/types" "^7.29.7"
+
"@babel/plugin-transform-react-jsx-self@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz#af678d8506acf52c577cac73ff7fe6615c85fc92"
@@ -198,6 +223,19 @@
"@babel/helper-string-parser" "^7.27.1"
"@babel/helper-validator-identifier" "^7.27.1"
+"@babel/types@^7.25.4", "@babel/types@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92"
+ integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==
+ dependencies:
+ "@babel/helper-string-parser" "^7.29.7"
+ "@babel/helper-validator-identifier" "^7.29.7"
+
+"@bcoe/v8-coverage@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz#bbe12dca5b4ef983a0d0af4b07b9bc90ea0ababa"
+ integrity sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==
+
"@biomejs/biome@^2.2.5":
version "2.2.5"
resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.2.5.tgz#291df1137404843537dd50eaa0174c12c0681d82"
@@ -643,6 +681,11 @@
wrap-ansi "^8.1.0"
wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
+"@istanbuljs/schema@^0.1.2":
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.6.tgz#8dc9afa2ac1506cb1a58f89940f1c124446c8df3"
+ integrity sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==
+
"@joshwooding/vite-plugin-react-docgen-typescript@0.6.1":
version "0.6.1"
resolved "https://registry.yarnpkg.com/@joshwooding/vite-plugin-react-docgen-typescript/-/vite-plugin-react-docgen-typescript-0.6.1.tgz#f630b93ed13d5d07483c0ead42db793053b364a9"
@@ -678,6 +721,14 @@
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba"
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
+"@jridgewell/trace-mapping@^0.3.23", "@jridgewell/trace-mapping@^0.3.31":
+ version "0.3.31"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0"
+ integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.1.0"
+ "@jridgewell/sourcemap-codec" "^1.4.14"
+
"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28":
version "0.3.30"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz#4a76c4daeee5df09f5d3940e087442fb36ce2b99"
@@ -818,6 +869,11 @@
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
+"@polka/url@^1.0.0-next.24":
+ version "1.0.0-next.29"
+ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.29.tgz#5a40109a1ab5f84d6fd8fc928b19f367cbe7e7b1"
+ integrity sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==
+
"@popperjs/core@^2.11.8":
version "2.11.8"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
@@ -1077,6 +1133,20 @@
lz-string "^1.5.0"
pretty-format "^27.0.2"
+"@testing-library/dom@^10", "@testing-library/dom@^10.4.0":
+ version "10.4.1"
+ resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.1.tgz#d444f8a889e9a46e9a3b4f3b88e0fcb3efb6cf95"
+ integrity sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==
+ dependencies:
+ "@babel/code-frame" "^7.10.4"
+ "@babel/runtime" "^7.12.5"
+ "@types/aria-query" "^5.0.1"
+ aria-query "5.3.0"
+ dom-accessibility-api "^0.5.9"
+ lz-string "^1.5.0"
+ picocolors "1.1.1"
+ pretty-format "^27.0.2"
+
"@testing-library/jest-dom@6.5.0":
version "6.5.0"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz#50484da3f80fb222a853479f618a9ce5c47bfe54"
@@ -1090,7 +1160,7 @@
lodash "^4.17.21"
redent "^3.0.0"
-"@testing-library/jest-dom@^6.6.3":
+"@testing-library/jest-dom@^6", "@testing-library/jest-dom@^6.6.3":
version "6.9.1"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz#7613a04e146dd2976d24ddf019730d57a89d56c2"
integrity sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==
@@ -1102,12 +1172,19 @@
picocolors "^1.1.1"
redent "^3.0.0"
+"@testing-library/react@^16":
+ version "16.3.2"
+ resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.3.2.tgz#672883b7acb8e775fc0492d9e9d25e06e89786d0"
+ integrity sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==
+ dependencies:
+ "@babel/runtime" "^7.12.5"
+
"@testing-library/user-event@14.5.2":
version "14.5.2"
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.2.tgz#db7257d727c891905947bd1c1a99da20e03c2ebd"
integrity sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==
-"@testing-library/user-event@^14.6.1":
+"@testing-library/user-event@^14", "@testing-library/user-event@^14.6.1":
version "14.6.1"
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.6.1.tgz#13e09a32d7a8b7060fe38304788ebf4197cd2149"
integrity sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==
@@ -1228,6 +1305,39 @@
"@types/babel__core" "^7.20.5"
react-refresh "^0.17.0"
+"@vitest/browser@^3":
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/@vitest/browser/-/browser-3.2.4.tgz#4238600dc8343b8a9c032266c743f7d38ae3da84"
+ integrity sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw==
+ dependencies:
+ "@testing-library/dom" "^10.4.0"
+ "@testing-library/user-event" "^14.6.1"
+ "@vitest/mocker" "3.2.4"
+ "@vitest/utils" "3.2.4"
+ magic-string "^0.30.17"
+ sirv "^3.0.1"
+ tinyrainbow "^2.0.0"
+ ws "^8.18.2"
+
+"@vitest/coverage-v8@^3":
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz#a2d8d040288c1956a1c7d0a0e2cdcfc7a3319f13"
+ integrity sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==
+ dependencies:
+ "@ampproject/remapping" "^2.3.0"
+ "@bcoe/v8-coverage" "^1.0.2"
+ ast-v8-to-istanbul "^0.3.3"
+ debug "^4.4.1"
+ istanbul-lib-coverage "^3.2.2"
+ istanbul-lib-report "^3.0.1"
+ istanbul-lib-source-maps "^5.0.6"
+ istanbul-reports "^3.1.7"
+ magic-string "^0.30.17"
+ magicast "^0.3.5"
+ std-env "^3.9.0"
+ test-exclude "^7.0.1"
+ tinyrainbow "^2.0.0"
+
"@vitest/expect@2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.0.5.tgz#f3745a6a2c18acbea4d39f5935e913f40d26fa86"
@@ -1272,13 +1382,31 @@
dependencies:
tinyrainbow "^1.2.0"
-"@vitest/pretty-format@3.2.4":
+"@vitest/pretty-format@3.2.4", "@vitest/pretty-format@^3.2.4":
version "3.2.4"
resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-3.2.4.tgz#3c102f79e82b204a26c7a5921bf47d534919d3b4"
integrity sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==
dependencies:
tinyrainbow "^2.0.0"
+"@vitest/runner@3.2.4":
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-3.2.4.tgz#5ce0274f24a971f6500f6fc166d53d8382430766"
+ integrity sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==
+ dependencies:
+ "@vitest/utils" "3.2.4"
+ pathe "^2.0.3"
+ strip-literal "^3.0.0"
+
+"@vitest/snapshot@3.2.4":
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-3.2.4.tgz#40a8bc0346ac0aee923c0eefc2dc005d90bc987c"
+ integrity sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==
+ dependencies:
+ "@vitest/pretty-format" "3.2.4"
+ magic-string "^0.30.17"
+ pathe "^2.0.3"
+
"@vitest/spy@2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.0.5.tgz#590fc07df84a78b8e9dd976ec2090920084a2b9f"
@@ -1505,6 +1633,15 @@ ast-types@^0.16.1:
dependencies:
tslib "^2.0.1"
+ast-v8-to-istanbul@^0.3.3:
+ version "0.3.12"
+ resolved "https://registry.yarnpkg.com/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz#8eb1b7c86ef8499859be761b17ffd91406c0c36f"
+ integrity sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==
+ dependencies:
+ "@jridgewell/trace-mapping" "^0.3.31"
+ estree-walker "^3.0.3"
+ js-tokens "^10.0.0"
+
babel-plugin-macros@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1"
@@ -1519,6 +1656,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+balanced-match@^4.0.2:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a"
+ integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==
+
better-opn@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/better-opn/-/better-opn-3.0.2.tgz#f96f35deaaf8f34144a4102651babcf00d1d8817"
@@ -1533,6 +1675,13 @@ brace-expansion@^2.0.1:
dependencies:
balanced-match "^1.0.0"
+brace-expansion@^5.0.5:
+ version "5.0.6"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.6.tgz#ec68fe0a641a29d8711579caf641d05bae1f2285"
+ integrity sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==
+ dependencies:
+ balanced-match "^4.0.2"
+
browserslist@^4.24.0:
version "4.25.4"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.4.tgz#ebdd0e1d1cf3911834bab3a6cd7b917d9babf5af"
@@ -1543,6 +1692,11 @@ browserslist@^4.24.0:
node-releases "^2.0.19"
update-browserslist-db "^1.1.3"
+cac@^6.7.14:
+ version "6.7.14"
+ resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959"
+ integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==
+
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@@ -1702,6 +1856,13 @@ debug@^4.1.0, debug@^4.3.1:
dependencies:
ms "2.1.2"
+debug@^4.1.1, debug@^4.4.1:
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
+ integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
+ dependencies:
+ ms "^2.1.3"
+
debug@^4.3.4, debug@^4.4.0:
version "4.4.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b"
@@ -1781,6 +1942,11 @@ error-ex@^1.3.1:
dependencies:
is-arrayish "^0.2.1"
+es-module-lexer@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a"
+ integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==
+
esbuild-register@^3.5.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/esbuild-register/-/esbuild-register-3.6.0.tgz#cf270cfa677baebbc0010ac024b823cbf723a36d"
@@ -1889,6 +2055,11 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+expect-type@^1.2.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.3.0.tgz#0d58ed361877a31bbc4dd6cf71bbfef7faf6bd68"
+ integrity sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==
+
exsolve@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/exsolve/-/exsolve-1.0.7.tgz#3b74e4c7ca5c5f9a19c3626ca857309fa99f9e9e"
@@ -1940,7 +2111,7 @@ fs-extra@~11.3.0:
jsonfile "^6.0.1"
universalify "^2.0.0"
-fsevents@~2.3.2:
+fsevents@2.3.2, fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
@@ -1977,6 +2148,18 @@ glob@^10.0.0:
package-json-from-dist "^1.0.0"
path-scurry "^1.11.1"
+glob@^10.4.1:
+ version "10.5.0"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c"
+ integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==
+ dependencies:
+ foreground-child "^3.1.0"
+ jackspeak "^3.1.2"
+ minimatch "^9.0.4"
+ minipass "^7.1.2"
+ package-json-from-dist "^1.0.0"
+ path-scurry "^1.11.1"
+
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.6"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
@@ -2018,6 +2201,11 @@ hoist-non-react-statics@^3.3.1:
dependencies:
react-is "^16.7.0"
+html-escaper@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
+ integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
+
import-fresh@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
@@ -2077,6 +2265,37 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
+istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.2:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756"
+ integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==
+
+istanbul-lib-report@^3.0.0, istanbul-lib-report@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d"
+ integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==
+ dependencies:
+ istanbul-lib-coverage "^3.0.0"
+ make-dir "^4.0.0"
+ supports-color "^7.1.0"
+
+istanbul-lib-source-maps@^5.0.6:
+ version "5.0.6"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz#acaef948df7747c8eb5fbf1265cb980f6353a441"
+ integrity sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==
+ dependencies:
+ "@jridgewell/trace-mapping" "^0.3.23"
+ debug "^4.1.1"
+ istanbul-lib-coverage "^3.0.0"
+
+istanbul-reports@^3.1.7:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93"
+ integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==
+ dependencies:
+ html-escaper "^2.0.0"
+ istanbul-lib-report "^3.0.0"
+
jackspeak@^3.1.2:
version "3.4.3"
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a"
@@ -2096,11 +2315,21 @@ jotai@^2.15.0:
resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.15.0.tgz#2a697924b7dcfff71847e83c7493c92e3b731e88"
integrity sha512-nbp/6jN2Ftxgw0VwoVnOg0m5qYM1rVcfvij+MZx99Z5IK13eGve9FJoCwGv+17JvVthTjhSmNtT5e1coJnr6aw==
+js-tokens@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-10.0.0.tgz#dffe7599b4a8bb7fe30aff8d0235234dffb79831"
+ integrity sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==
+
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+js-tokens@^9.0.1:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-9.0.1.tgz#2ec43964658435296f6761b34e10671c2d9527f4"
+ integrity sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==
+
jsesc@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
@@ -2204,6 +2433,22 @@ magic-string@^0.30.0, magic-string@^0.30.17:
dependencies:
"@jridgewell/sourcemap-codec" "^1.5.5"
+magicast@^0.3.5:
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.5.tgz#8301c3c7d66704a0771eb1bad74274f0ec036739"
+ integrity sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==
+ dependencies:
+ "@babel/parser" "^7.25.4"
+ "@babel/types" "^7.25.4"
+ source-map-js "^1.2.0"
+
+make-dir@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e"
+ integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==
+ dependencies:
+ semver "^7.5.3"
+
min-indent@^1.0.0, min-indent@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
@@ -2216,6 +2461,13 @@ minimatch@10.0.3:
dependencies:
"@isaacs/brace-expansion" "^5.0.0"
+minimatch@^10.2.2:
+ version "10.2.5"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.5.tgz#bd48687a0be38ed2961399105600f832095861d1"
+ integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==
+ dependencies:
+ brace-expansion "^5.0.5"
+
minimatch@^9.0.3, minimatch@^9.0.4:
version "9.0.5"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
@@ -2243,6 +2495,11 @@ mlly@^1.7.4:
pkg-types "^1.3.1"
ufo "^1.6.1"
+mrmime@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.1.tgz#bc3e87f7987853a54c9850eeb1f1078cd44adddc"
+ integrity sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==
+
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
@@ -2361,7 +2618,7 @@ pathval@^2.0.0:
resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.1.tgz#8855c5a2899af072d6ac05d11e46045ad0dc605d"
integrity sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==
-picocolors@^1.1.1:
+picocolors@1.1.1, picocolors@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
@@ -2371,6 +2628,11 @@ picomatch@^4.0.2, picomatch@^4.0.3:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042"
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
+picomatch@^4.0.4:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589"
+ integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==
+
pkg-types@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.3.1.tgz#bd7cc70881192777eef5326c19deb46e890917df"
@@ -2389,6 +2651,20 @@ pkg-types@^2.3.0:
exsolve "^1.0.7"
pathe "^2.0.3"
+playwright-core@1.60.0:
+ version "1.60.0"
+ resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.60.0.tgz#24e0d9cc4730713db5dffcace29b5e4696b1907a"
+ integrity sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==
+
+playwright@^1:
+ version "1.60.0"
+ resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.60.0.tgz#89710863a51f21112633ef8b6b182594d3bfd7b5"
+ integrity sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==
+ dependencies:
+ playwright-core "1.60.0"
+ optionalDependencies:
+ fsevents "2.3.2"
+
postcss@^8.5.6:
version "8.5.6"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c"
@@ -2595,6 +2871,11 @@ semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
+semver@^7.5.3:
+ version "7.8.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.1.tgz#bf4970b5e70fda0686363cc18bfe8805d5ed957e"
+ integrity sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==
+
semver@^7.6.2:
version "7.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
@@ -2619,12 +2900,26 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+siginfo@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30"
+ integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==
+
signal-exit@^4.0.1:
version "4.1.0"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
-source-map-js@^1.2.1:
+sirv@^3.0.1:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/sirv/-/sirv-3.0.2.tgz#f775fccf10e22a40832684848d636346f41cd970"
+ integrity sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==
+ dependencies:
+ "@polka/url" "^1.0.0-next.24"
+ mrmime "^2.0.0"
+ totalist "^3.0.0"
+
+source-map-js@^1.2.0, source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
@@ -2644,6 +2939,16 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
+stackback@0.0.2:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b"
+ integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==
+
+std-env@^3.9.0:
+ version "3.10.0"
+ resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.10.0.tgz#d810b27e3a073047b2b5e40034881f5ea6f9c83b"
+ integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==
+
storybook@^9.1.10:
version "9.1.10"
resolved "https://registry.yarnpkg.com/storybook/-/storybook-9.1.10.tgz#c06cfd8b77522cf17bd22d82d1e83af96362a78e"
@@ -2739,6 +3044,13 @@ strip-json-comments@~3.1.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+strip-literal@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-3.1.0.tgz#222b243dd2d49c0bcd0de8906adbd84177196032"
+ integrity sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==
+ dependencies:
+ js-tokens "^9.0.1"
+
stylis@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51"
@@ -2770,11 +3082,38 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+test-exclude@^7.0.1:
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-7.0.2.tgz#482392077630bc57d5630c13abe908bb910dfc65"
+ integrity sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==
+ dependencies:
+ "@istanbuljs/schema" "^0.1.2"
+ glob "^10.4.1"
+ minimatch "^10.2.2"
+
tiny-invariant@^1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
+tinybench@^2.9.0:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b"
+ integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==
+
+tinyexec@^0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2"
+ integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==
+
+tinyglobby@^0.2.14:
+ version "0.2.16"
+ resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.16.tgz#1c3b7eb953fce42b226bc5a1ee06428281aff3d6"
+ integrity sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==
+ dependencies:
+ fdir "^6.5.0"
+ picomatch "^4.0.4"
+
tinyglobby@^0.2.15:
version "0.2.15"
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2"
@@ -2783,6 +3122,11 @@ tinyglobby@^0.2.15:
fdir "^6.5.0"
picomatch "^4.0.3"
+tinypool@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.1.1.tgz#059f2d042bd37567fbc017d3d426bdd2a2612591"
+ integrity sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==
+
tinyrainbow@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5"
@@ -2803,6 +3147,11 @@ tinyspy@^4.0.3:
resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-4.0.4.tgz#d77a002fb53a88aa1429b419c1c92492e0c81f78"
integrity sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==
+totalist@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8"
+ integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==
+
ts-dedent@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
@@ -2875,6 +3224,17 @@ uri-js@^4.2.2, uri-js@^4.4.1:
dependencies:
punycode "^2.1.0"
+vite-node@3.2.4:
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-3.2.4.tgz#f3676d94c4af1e76898c162c92728bca65f7bb07"
+ integrity sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==
+ dependencies:
+ cac "^6.7.14"
+ debug "^4.4.1"
+ es-module-lexer "^1.7.0"
+ pathe "^2.0.3"
+ vite "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+
vite-plugin-dts@^4.5.4:
version "4.5.4"
resolved "https://registry.yarnpkg.com/vite-plugin-dts/-/vite-plugin-dts-4.5.4.tgz#51b60aaaa760d9cf5c2bb3676c69d81910d6b08c"
@@ -2890,7 +3250,7 @@ vite-plugin-dts@^4.5.4:
local-pkg "^1.0.0"
magic-string "^0.30.17"
-vite@^7.1.9:
+vite@7.1.9, "vite@^5.0.0 || ^6.0.0 || ^7.0.0-0", vite@^7.1.9:
version "7.1.9"
resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.9.tgz#ba844410e5d0c0f2a4eaf17a52af60ebea322cbf"
integrity sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==
@@ -2904,6 +3264,40 @@ vite@^7.1.9:
optionalDependencies:
fsevents "~2.3.3"
+vitest-browser-react@^1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/vitest-browser-react/-/vitest-browser-react-1.0.1.tgz#e36f0a9ca46bf622841f0285defe0bebbb37e16d"
+ integrity sha512-LqiGFCdknrbMoSDWXTCTrPsED3SvdIXIgYOOZyYUNj2dkJusW2eF6NENOlBlxwq+FBQqzNK1X59b+b03pXFpAQ==
+
+vitest@^3:
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/vitest/-/vitest-3.2.4.tgz#0637b903ad79d1539a25bc34c0ed54b5c67702ea"
+ integrity sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==
+ dependencies:
+ "@types/chai" "^5.2.2"
+ "@vitest/expect" "3.2.4"
+ "@vitest/mocker" "3.2.4"
+ "@vitest/pretty-format" "^3.2.4"
+ "@vitest/runner" "3.2.4"
+ "@vitest/snapshot" "3.2.4"
+ "@vitest/spy" "3.2.4"
+ "@vitest/utils" "3.2.4"
+ chai "^5.2.0"
+ debug "^4.4.1"
+ expect-type "^1.2.1"
+ magic-string "^0.30.17"
+ pathe "^2.0.3"
+ picomatch "^4.0.2"
+ std-env "^3.9.0"
+ tinybench "^2.9.0"
+ tinyexec "^0.3.2"
+ tinyglobby "^0.2.14"
+ tinypool "^1.1.1"
+ tinyrainbow "^2.0.0"
+ vite "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ vite-node "3.2.4"
+ why-is-node-running "^2.3.0"
+
vscode-uri@^3.0.8:
version "3.1.0"
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.1.0.tgz#dd09ec5a66a38b5c3fffc774015713496d14e09c"
@@ -2921,6 +3315,14 @@ which@^2.0.1:
dependencies:
isexe "^2.0.0"
+why-is-node-running@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04"
+ integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==
+ dependencies:
+ siginfo "^2.0.0"
+ stackback "0.0.2"
+
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
@@ -2944,6 +3346,11 @@ ws@^8.18.0:
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472"
integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==
+ws@^8.18.2:
+ version "8.21.0"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.21.0.tgz#012e413fc07429945121b0c153158c4343086951"
+ integrity sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==
+
yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"