diff --git a/benchmark/REPORT.md b/benchmark/REPORT.md index a2d273e..35acef2 100644 --- a/benchmark/REPORT.md +++ b/benchmark/REPORT.md @@ -1,51 +1,52 @@ # React Testing Framework Benchmark Report -> Generated: Wed, 01 Apr 2026 12:41:50 GMT +> Generated: Sun, 05 Apr 2026 19:06:36 GMT ## Environment -| Property | Value | -| ------------- | ------------- | -| Node.js | v22.5.1 | -| Platform | darwin 25.4.0 | -| CPU | Apple M3 Pro | -| CPU Cores | 12 | -| Total RAM | 18.0 GB | -| Runs/scenario | 7 (trim ±1) | +| Property | Value | +|---|---| +| Node.js | v22.5.1 | +| Platform | darwin 25.4.0 | +| CPU | Apple M3 Pro | +| CPU Cores | 12 | +| Total RAM | 18.0 GB | +| Runs/scenario | 7 (trim ±1) | ## Scenarios Each scenario runs the **same 9 React tests** across 5 test files: -| Test File | Tests | -| ---------------------- | ------------------------------------------ | -| 'counter.test.jsx' | 1 — stateful counter, event interaction | -| 'hooks.test.jsx' | 2 — custom hook harness + `renderHook` | -| 'lifecycle.test.jsx' | 2 — `rerender`, `unmount` + effect cleanup | -| 'context.test.jsx' | 1 — `createContext` + wrapper injection | -| 'concurrency.test.jsx' | 2 — React 19 `use()` + `useTransition` | +| Test File | Tests | +|---|---| +| 'counter.test.jsx' | 1 — stateful counter, event interaction | +| 'hooks.test.jsx' | 2 — custom hook harness + `renderHook` | +| 'lifecycle.test.jsx' | 2 — `rerender`, `unmount` + effect cleanup | +| 'context.test.jsx' | 1 — `createContext` + wrapper injection | +| 'concurrency.test.jsx' | 2 — React 19 `use()` + `useTransition` | ### Frameworks under test -| Combination | DOM layer | Assertion style | -| --------------------------------- | ------------------------------ | -------------------- | -| poku + @pokujs/react | happy-dom | `assert.strictEqual` | -| poku + @pokujs/react | jsdom | `assert.strictEqual` | -| jest 29 + @testing-library/react | jsdom (jest-environment-jsdom) | `expect().toBe()` | -| vitest 3 + @testing-library/react | jsdom | `expect().toBe()` | -| vitest 3 + @testing-library/react | happy-dom | `expect().toBe()` | +| Combination | DOM layer | Assertion style | +|---|---|---| +| poku + @pokujs/react | happy-dom | `assert.strictEqual` | +| poku + @pokujs/react | jsdom | `assert.strictEqual` | +| jest 29 + @testing-library/react | jsdom (jest-environment-jsdom) | `expect().toBe()` | +| vitest 3 + @testing-library/react | jsdom | `expect().toBe()` | +| vitest 3 + @testing-library/react | happy-dom | `expect().toBe()` | ## Results | Scenario | Mean | Min | Max | Stdev | Peak RSS | vs poku+happy-dom | -| ------------------ | ------ | ------ | ------ | ------ | -------- | ----------------- | -| poku + happy-dom | 0.560s | 0.515s | 0.600s | 0.033s | 154.3 MB | _(baseline)_ | -| poku + jsdom | 0.444s | 0.429s | 0.451s | 0.008s | 157.1 MB | -21% | -| jest + jsdom | 1.040s | 0.975s | 1.135s | 0.056s | 203.4 MB | +86% | -| vitest + jsdom | 1.193s | 1.129s | 1.269s | 0.057s | 152.3 MB | +113% | -| vitest + happy-dom | 1.041s | 0.990s | 1.126s | 0.047s | 117.1 MB | +86% | - -> **Wall-clock time** is measured with `performance.now()` around the child-process spawn. +|--------------------|--------|--------|--------|--------|----------|-------------------| +| poku + happy-dom | 0.115s | 0.112s | 0.109s | 0.004s | 127.4 MB | *(baseline)* | +| poku + jsdom | 0.294s | 0.280s | 0.299s | 0.007s | 172.1 MB | +157% | +| jest + jsdom | 0.843s | 0.768s | 0.879s | 0.041s | 198.8 MB | +636% | +| vitest + jsdom | 0.968s | 0.951s | 0.986s | 0.011s | 147.8 MB | +745% | +| vitest + happy-dom | 0.845s | 0.825s | 0.856s | 0.013s | 115.2 MB | +637% | + +> **Poku elapsed time** uses Poku's reported suite `Duration` (ANSI-stripped parse) to avoid teardown-skew artifacts. +> **Jest/Vitest elapsed time** is measured with `performance.now()` around the child-process spawn. > **Peak RSS** is captured via `/usr/bin/time -l` on macOS (bytes → MB). > The baseline for relative comparisons is **poku + happy-dom**. @@ -53,44 +54,44 @@ Each scenario runs the **same 9 React tests** across 5 test files: ### Overall ranking (mean wall-clock time) -1. **poku + jsdom** — 0.444s -2. **poku + happy-dom** — 0.560s -3. **jest + jsdom** — 1.040s -4. **vitest + happy-dom** — 1.041s -5. **vitest + jsdom** — 1.193s +1. **poku + happy-dom** — 0.115s +2. **poku + jsdom** — 0.294s +3. **jest + jsdom** — 0.843s +4. **vitest + happy-dom** — 0.845s +5. **vitest + jsdom** — 0.968s ### Speed comparison -- poku+happy-dom vs jest+jsdom: jest is **86% slower** -- poku+happy-dom vs vitest+jsdom: vitest is **113% slower** +- poku+happy-dom vs jest+jsdom: jest is **636% slower** +- poku+happy-dom vs vitest+jsdom: vitest is **745% slower** - jest+jsdom vs vitest+jsdom: vitest is **15% slower** than jest ### DOM adapter impact -- **poku**: happy-dom vs jsdom — jsdom is **-21% faster** +- **poku**: happy-dom vs jsdom — jsdom is **157% slower** - **vitest**: happy-dom vs jsdom — jsdom is **15% slower** ### Memory footprint -- **vitest + happy-dom**: 117.1 MB peak RSS -- **vitest + jsdom**: 152.3 MB peak RSS -- **poku + happy-dom**: 154.3 MB peak RSS -- **poku + jsdom**: 157.1 MB peak RSS -- **jest + jsdom**: 203.4 MB peak RSS +- **vitest + happy-dom**: 115.2 MB peak RSS +- **poku + happy-dom**: 127.4 MB peak RSS +- **vitest + jsdom**: 147.8 MB peak RSS +- **poku + jsdom**: 172.1 MB peak RSS +- **jest + jsdom**: 198.8 MB peak RSS ### Consistency (lower stdev = more predictable) -- **poku + jsdom**: σ = 0.008s -- **poku + happy-dom**: σ = 0.033s -- **vitest + happy-dom**: σ = 0.047s -- **jest + jsdom**: σ = 0.056s -- **vitest + jsdom**: σ = 0.057s +- **poku + happy-dom**: σ = 0.004s +- **poku + jsdom**: σ = 0.007s +- **vitest + jsdom**: σ = 0.011s +- **vitest + happy-dom**: σ = 0.013s +- **jest + jsdom**: σ = 0.041s ## Key findings -- **Fastest**: poku + jsdom — 0.444s mean -- **Slowest**: vitest + jsdom — 1.193s mean -- **Speed spread**: 169% difference between fastest and slowest +- **Fastest**: poku + happy-dom — 0.115s mean +- **Slowest**: vitest + jsdom — 0.968s mean +- **Speed spread**: 745% difference between fastest and slowest ### Interpretation @@ -100,7 +101,6 @@ processes with minimal bootstrap — means cold-start overhead is proportional t files, not to the framework's own initialization. **jest** carries the heaviest startup cost due to: - 1. Babel transformation of every TSX file on first run (no persistent cache in this benchmark) 2. 'jest-worker' process pool initialisation 3. JSDOM environment setup per test file diff --git a/benchmark/package-lock.json b/benchmark/package-lock.json index 076f824..38badea 100644 --- a/benchmark/package-lock.json +++ b/benchmark/package-lock.json @@ -26,11 +26,11 @@ } }, "..": { - "name": "@pokujs/react", - "version": "1.2.0", + "version": "1.2.1", "dev": true, "license": "MIT", "dependencies": { + "@pokujs/dom": "^0.1.0", "@testing-library/dom": "^10.4.1" }, "devDependencies": { diff --git a/benchmark/results.json b/benchmark/results.json index 2d18d6e..f05480b 100644 --- a/benchmark/results.json +++ b/benchmark/results.json @@ -1,5 +1,5 @@ { - "timestamp": "2026-04-01T12:41:50.774Z", + "timestamp": "2026-04-05T19:06:36.388Z", "system": { "nodeVersion": "v22.5.1", "platform": "darwin 25.4.0", @@ -14,68 +14,68 @@ "results": [ { "label": "poku + happy-dom", - "mean": 559.9537916000002, - "min": 515.060708, - "max": 599.9880840000001, - "stddev": 33.06234198220535, - "meanRss": 161808384, - "meanUserCpu": 672, - "meanSysCpu": 202, + "mean": 114.5411498, + "min": 111.865375, + "max": 108.566625, + "stddev": 4.164501573153617, + "meanRss": 133624627.2, + "meanUserCpu": 358, + "meanSysCpu": 80, "runs": 7, "validRuns": 7, "failures": 0 }, { "label": "poku + jsdom", - "mean": 443.6319584000001, - "min": 428.5047089999998, - "max": 450.59829100000024, - "stddev": 7.9857833050707905, - "meanRss": 164767334.4, - "meanUserCpu": 432, - "meanSysCpu": 92, + "mean": 294.0646082, + "min": 280.499125, + "max": 298.546375, + "stddev": 6.855649968732701, + "meanRss": 180456652.8, + "meanUserCpu": 532, + "meanSysCpu": 102, "runs": 7, "validRuns": 7, "failures": 0 }, { "label": "jest + jsdom", - "mean": 1040.1169332000002, - "min": 975.1540420000001, - "max": 1134.9154159999998, - "stddev": 56.06532298235639, - "meanRss": 213300019.2, - "meanUserCpu": 936, - "meanSysCpu": 228, + "mean": 842.5360584000006, + "min": 768.3345829999998, + "max": 879.3446250000015, + "stddev": 40.57889525259689, + "meanRss": 208506060.8, + "meanUserCpu": 784, + "meanSysCpu": 152, "runs": 7, "validRuns": 7, "failures": 0 }, { "label": "vitest + jsdom", - "mean": 1192.7835997999996, - "min": 1129.2755830000006, - "max": 1268.7202919999982, - "stddev": 56.83080807294834, - "meanRss": 159652249.6, - "meanUserCpu": 3798, - "meanSysCpu": 1380, + "mean": 967.6274329999997, + "min": 950.946915999999, + "max": 985.529625000001, + "stddev": 11.190019303221952, + "meanRss": 155002470.4, + "meanUserCpu": 3524, + "meanSysCpu": 954, "runs": 7, "validRuns": 7, "failures": 0 }, { "label": "vitest + happy-dom", - "mean": 1041.3667920000007, - "min": 990.0950420000008, - "max": 1126.3293750000012, - "stddev": 47.26983715919258, - "meanRss": 122814464, - "meanUserCpu": 3346, - "meanSysCpu": 1156, + "mean": 844.6526001999998, + "min": 824.9157500000001, + "max": 856.1071249999986, + "stddev": 13.21280400724632, + "meanRss": 120832000, + "meanUserCpu": 3056, + "meanSysCpu": 900, "runs": 7, "validRuns": 7, "failures": 0 } ] -} +} \ No newline at end of file diff --git a/benchmark/run.mjs b/benchmark/run.mjs index 032c86c..d34fb62 100644 --- a/benchmark/run.mjs +++ b/benchmark/run.mjs @@ -26,6 +26,7 @@ import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = resolve(__dirname, '..'); +const DOM_ROOT = resolve(__dirname, '..', '..', 'dom'); const BENCH = __dirname; const RUNS = parseInt(process.env.BENCH_RUNS ?? '7', 10); @@ -40,12 +41,20 @@ const POKU_BIN = join(BENCH, 'node_modules', 'poku', 'lib', 'bin', 'index.js'); * benchmark/node_modules instead of following a symlink to the workspace root. */ function syncLocalBuild() { - const dest = join(BENCH, 'node_modules', '@pokujs', 'react'); - rmSync(dest, { recursive: true, force: true }); - mkdirSync(join(dest, 'dist'), { recursive: true }); - copyFileSync(join(ROOT, 'package.json'), join(dest, 'package.json')); + const reactDest = join(BENCH, 'node_modules', '@pokujs', 'react'); + rmSync(reactDest, { recursive: true, force: true }); + mkdirSync(join(reactDest, 'dist'), { recursive: true }); + copyFileSync(join(ROOT, 'package.json'), join(reactDest, 'package.json')); for (const file of readdirSync(join(ROOT, 'dist'))) { - copyFileSync(join(ROOT, 'dist', file), join(dest, 'dist', file)); + copyFileSync(join(ROOT, 'dist', file), join(reactDest, 'dist', file)); + } + + const domDest = join(BENCH, 'node_modules', '@pokujs', 'dom'); + rmSync(domDest, { recursive: true, force: true }); + mkdirSync(join(domDest, 'dist'), { recursive: true }); + copyFileSync(join(DOM_ROOT, 'package.json'), join(domDest, 'package.json')); + for (const file of readdirSync(join(DOM_ROOT, 'dist'))) { + copyFileSync(join(DOM_ROOT, 'dist', file), join(domDest, 'dist', file)); } } @@ -111,8 +120,17 @@ function runOnce(args, cwd, extraEnv = {}) { } } + const plainStdout = (result.stdout ?? '').replace(/\u001b\[[0-9;]*m/g, ''); + const reportedDurationMatch = plainStdout.match( + /Duration\s+›\s+([\d.]+)ms/ + ); + const reportedDurationMs = reportedDurationMatch + ? Number(reportedDurationMatch[1]) + : null; + return { elapsed, + reportedDurationMs, maxRssBytes, userCpuMs, sysCpuMs, @@ -125,7 +143,13 @@ function runOnce(args, cwd, extraEnv = {}) { // ─── scenario runner ──────────────────────────────────────────────────────── -function benchmarkScenario(label, args, cwd, extraEnv = {}) { +function benchmarkScenario( + label, + args, + cwd, + extraEnv = {}, + options = {} +) { process.stdout.write(` ${pad(label, 36)}`); const measurements = []; @@ -159,7 +183,14 @@ function benchmarkScenario(label, args, cwd, extraEnv = {}) { measurements.sort((a, b) => a.elapsed - b.elapsed); const trimmed = measurements.slice(TRIM, measurements.length - TRIM); - const times = trimmed.map((m) => m.elapsed); + const useReportedDuration = Boolean(options.useReportedDuration); + const times = trimmed.map((m) => { + if (useReportedDuration && m.reportedDurationMs != null) { + return m.reportedDurationMs; + } + + return m.elapsed; + }); const rssList = trimmed.map((m) => m.maxRssBytes).filter((v) => v != null); const userCpus = trimmed.map((m) => m.userCpuMs).filter((v) => v != null); const sysCpus = trimmed.map((m) => m.sysCpuMs).filter((v) => v != null); @@ -209,12 +240,14 @@ const scenarios = [ args: ['node', POKU_BIN, 'tests/poku'], cwd: BENCH, env: { POKU_REACT_TEST_DOM: 'happy-dom' }, + options: { useReportedDuration: true }, }, { label: 'poku + jsdom', args: ['node', POKU_BIN, 'tests/poku'], cwd: BENCH, env: { POKU_REACT_TEST_DOM: 'jsdom' }, + options: { useReportedDuration: true }, }, { label: 'jest + jsdom', @@ -266,7 +299,7 @@ if (!existsSync(join(BENCH, 'node_modules', 'poku'))) { // ─── sync local build ──────────────────────────────────────────────────────── -if (existsSync(join(ROOT, 'dist'))) { +if (existsSync(join(ROOT, 'dist')) && existsSync(join(DOM_ROOT, 'dist'))) { process.stdout.write(' Syncing local @pokujs/react build... '); syncLocalBuild(); process.stdout.write('done\n\n'); @@ -297,7 +330,7 @@ console.log('\n Legend: · = pass ✗ = fail\n'); const results = []; for (const s of scenarios) { - const r = benchmarkScenario(s.label, s.args, s.cwd, s.env); + const r = benchmarkScenario(s.label, s.args, s.cwd, s.env, s.options); if (r) results.push(r); } @@ -484,7 +517,8 @@ Each scenario runs the **same 9 React tests** across 5 test files: ${resultsTable} -> **Wall-clock time** is measured with \`performance.now()\` around the child-process spawn. +> **Poku elapsed time** uses Poku's reported suite \`Duration\` (ANSI-stripped parse) to avoid teardown-skew artifacts. +> **Jest/Vitest elapsed time** is measured with \`performance.now()\` around the child-process spawn. > **Peak RSS** is captured via \`/usr/bin/time -l\` on macOS (bytes → MB). > The baseline for relative comparisons is **poku + happy-dom**. diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..2dc131c --- /dev/null +++ b/deno.json @@ -0,0 +1,6 @@ +{ + "nodeModulesDir": "auto", + "imports": { + "@pokujs/dom": "npm:@pokujs/dom@^1.1.2" + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..d00aeac --- /dev/null +++ b/deno.lock @@ -0,0 +1,1193 @@ +{ + "version": "5", + "specifiers": { + "npm:@happy-dom/global-registrator@^20.8.9": "20.8.9", + "npm:@ianvs/prettier-plugin-sort-imports@^4.7.0": "4.7.1_prettier@3.8.1", + "npm:@pokujs/dom@^1.1.0": "1.1.0_@happy-dom+global-registrator@20.8.9_happy-dom@20.8.9_jsdom@26.1.0_poku@4.2.0", + "npm:@pokujs/dom@^1.1.1": "1.1.1_happy-dom@20.8.9_jsdom@26.1.0_poku@4.2.0", + "npm:@testing-library/dom@^10.4.1": "10.4.1", + "npm:@types/jsdom@^28.0.1": "28.0.1", + "npm:@types/node@^25.5.0": "25.5.2", + "npm:@types/react-dom@^19.2.3": "19.2.3_@types+react@19.2.14", + "npm:@types/react@^19.2.14": "19.2.14", + "npm:cross-env@^10.1.0": "10.1.0", + "npm:happy-dom@^20.8.9": "20.8.9", + "npm:jsdom@^26.1.0": "26.1.0", + "npm:poku@*": "4.2.0", + "npm:poku@4.2.0": "4.2.0", + "npm:prettier@^3.6.2": "3.8.1", + "npm:react-dom@^19.2.4": "19.2.4_react@19.2.4", + "npm:react@^19.2.4": "19.2.4", + "npm:rimraf@^6.0.1": "6.1.3", + "npm:tsup@^8.5.0": "8.5.1_typescript@6.0.2_esbuild@0.27.7_tsx@4.21.0", + "npm:tsx@^4.21.0": "4.21.0", + "npm:typescript@^6.0.2": "6.0.2" + }, + "npm": { + "@asamuzakjp/css-color@3.2.0_@csstools+css-parser-algorithms@3.0.5__@csstools+css-tokenizer@3.0.4_@csstools+css-tokenizer@3.0.4": { + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dependencies": [ + "@csstools/css-calc", + "@csstools/css-color-parser", + "@csstools/css-parser-algorithms", + "@csstools/css-tokenizer", + "lru-cache@10.4.3" + ] + }, + "@babel/code-frame@7.29.0": { + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dependencies": [ + "@babel/helper-validator-identifier", + "js-tokens", + "picocolors" + ] + }, + "@babel/generator@7.29.1": { + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dependencies": [ + "@babel/parser", + "@babel/types", + "@jridgewell/gen-mapping", + "@jridgewell/trace-mapping", + "jsesc" + ] + }, + "@babel/helper-globals@7.28.0": { + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==" + }, + "@babel/helper-string-parser@7.27.1": { + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" + }, + "@babel/helper-validator-identifier@7.28.5": { + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==" + }, + "@babel/parser@7.29.2": { + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dependencies": [ + "@babel/types" + ], + "bin": true + }, + "@babel/runtime@7.29.2": { + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==" + }, + "@babel/template@7.28.6": { + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dependencies": [ + "@babel/code-frame", + "@babel/parser", + "@babel/types" + ] + }, + "@babel/traverse@7.29.0": { + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dependencies": [ + "@babel/code-frame", + "@babel/generator", + "@babel/helper-globals", + "@babel/parser", + "@babel/template", + "@babel/types", + "debug" + ] + }, + "@babel/types@7.29.0": { + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dependencies": [ + "@babel/helper-string-parser", + "@babel/helper-validator-identifier" + ] + }, + "@csstools/color-helpers@5.1.0": { + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==" + }, + "@csstools/css-calc@2.1.4_@csstools+css-parser-algorithms@3.0.5__@csstools+css-tokenizer@3.0.4_@csstools+css-tokenizer@3.0.4": { + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dependencies": [ + "@csstools/css-parser-algorithms", + "@csstools/css-tokenizer" + ] + }, + "@csstools/css-color-parser@3.1.0_@csstools+css-parser-algorithms@3.0.5__@csstools+css-tokenizer@3.0.4_@csstools+css-tokenizer@3.0.4": { + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dependencies": [ + "@csstools/color-helpers", + "@csstools/css-calc", + "@csstools/css-parser-algorithms", + "@csstools/css-tokenizer" + ] + }, + "@csstools/css-parser-algorithms@3.0.5_@csstools+css-tokenizer@3.0.4": { + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dependencies": [ + "@csstools/css-tokenizer" + ] + }, + "@csstools/css-tokenizer@3.0.4": { + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==" + }, + "@epic-web/invariant@1.0.0": { + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==" + }, + "@esbuild/aix-ppc64@0.27.7": { + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "os": ["aix"], + "cpu": ["ppc64"] + }, + "@esbuild/android-arm64@0.27.7": { + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@esbuild/android-arm@0.27.7": { + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "os": ["android"], + "cpu": ["arm"] + }, + "@esbuild/android-x64@0.27.7": { + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "os": ["android"], + "cpu": ["x64"] + }, + "@esbuild/darwin-arm64@0.27.7": { + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@esbuild/darwin-x64@0.27.7": { + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@esbuild/freebsd-arm64@0.27.7": { + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@esbuild/freebsd-x64@0.27.7": { + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@esbuild/linux-arm64@0.27.7": { + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@esbuild/linux-arm@0.27.7": { + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@esbuild/linux-ia32@0.27.7": { + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "os": ["linux"], + "cpu": ["ia32"] + }, + "@esbuild/linux-loong64@0.27.7": { + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@esbuild/linux-mips64el@0.27.7": { + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "os": ["linux"], + "cpu": ["mips64el"] + }, + "@esbuild/linux-ppc64@0.27.7": { + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@esbuild/linux-riscv64@0.27.7": { + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@esbuild/linux-s390x@0.27.7": { + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@esbuild/linux-x64@0.27.7": { + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@esbuild/netbsd-arm64@0.27.7": { + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "os": ["netbsd"], + "cpu": ["arm64"] + }, + "@esbuild/netbsd-x64@0.27.7": { + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "os": ["netbsd"], + "cpu": ["x64"] + }, + "@esbuild/openbsd-arm64@0.27.7": { + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "os": ["openbsd"], + "cpu": ["arm64"] + }, + "@esbuild/openbsd-x64@0.27.7": { + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@esbuild/openharmony-arm64@0.27.7": { + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@esbuild/sunos-x64@0.27.7": { + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "os": ["sunos"], + "cpu": ["x64"] + }, + "@esbuild/win32-arm64@0.27.7": { + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@esbuild/win32-ia32@0.27.7": { + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@esbuild/win32-x64@0.27.7": { + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@happy-dom/global-registrator@20.8.9": { + "integrity": "sha512-DtZeRRHY9A/bisTJziUBBPrdnPui7+R185G/hzi6/Boymhqh7/wi53AY+IvQHS1+7OPaqfO/1XNpngNwthLz+A==", + "dependencies": [ + "@types/node@25.5.2", + "happy-dom" + ] + }, + "@ianvs/prettier-plugin-sort-imports@4.7.1_prettier@3.8.1": { + "integrity": "sha512-jmTNYGlg95tlsoG3JLCcuC4BrFELJtLirLAkQW/71lXSyOhVt/Xj7xWbbGcuVbNq1gwWgSyMrPjJc9Z30hynVw==", + "dependencies": [ + "@babel/generator", + "@babel/parser", + "@babel/traverse", + "@babel/types", + "prettier", + "semver" + ] + }, + "@jridgewell/gen-mapping@0.3.13": { + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dependencies": [ + "@jridgewell/sourcemap-codec", + "@jridgewell/trace-mapping" + ] + }, + "@jridgewell/resolve-uri@3.1.2": { + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" + }, + "@jridgewell/sourcemap-codec@1.5.5": { + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" + }, + "@jridgewell/trace-mapping@0.3.31": { + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dependencies": [ + "@jridgewell/resolve-uri", + "@jridgewell/sourcemap-codec" + ] + }, + "@pokujs/dom@1.1.0_@happy-dom+global-registrator@20.8.9_happy-dom@20.8.9_jsdom@26.1.0_poku@4.2.0": { + "integrity": "sha512-0Cp3KLirW4mOXE11l2a0q0b6qsgorh3iqZxd1RiL69amc7uOQm+QJ96ETwr9GRkIfxa2wCPX/0JrKhMNY1T10Q==", + "dependencies": [ + "@happy-dom/global-registrator", + "@testing-library/dom", + "happy-dom", + "jsdom", + "poku" + ], + "optionalPeers": [ + "@happy-dom/global-registrator", + "happy-dom", + "jsdom" + ] + }, + "@pokujs/dom@1.1.1_happy-dom@20.8.9_jsdom@26.1.0_poku@4.2.0": { + "integrity": "sha512-qK9JRz9tDgcO4l+QibqwGaWpDeQDKHP7y0Y9b039ieK86kY51wf0WyOi7iDfeKJQ2Quk9vIKBqDaagzJvaaX/A==", + "dependencies": [ + "@testing-library/dom", + "happy-dom", + "jsdom", + "poku" + ], + "optionalPeers": [ + "happy-dom", + "jsdom" + ] + }, + "@rollup/rollup-android-arm-eabi@4.60.1": { + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "os": ["android"], + "cpu": ["arm"] + }, + "@rollup/rollup-android-arm64@4.60.1": { + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@rollup/rollup-darwin-arm64@4.60.1": { + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@rollup/rollup-darwin-x64@4.60.1": { + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@rollup/rollup-freebsd-arm64@4.60.1": { + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@rollup/rollup-freebsd-x64@4.60.1": { + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@rollup/rollup-linux-arm-gnueabihf@4.60.1": { + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@rollup/rollup-linux-arm-musleabihf@4.60.1": { + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@rollup/rollup-linux-arm64-gnu@4.60.1": { + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@rollup/rollup-linux-arm64-musl@4.60.1": { + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@rollup/rollup-linux-loong64-gnu@4.60.1": { + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@rollup/rollup-linux-loong64-musl@4.60.1": { + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@rollup/rollup-linux-ppc64-gnu@4.60.1": { + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@rollup/rollup-linux-ppc64-musl@4.60.1": { + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@rollup/rollup-linux-riscv64-gnu@4.60.1": { + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@rollup/rollup-linux-riscv64-musl@4.60.1": { + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@rollup/rollup-linux-s390x-gnu@4.60.1": { + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@rollup/rollup-linux-x64-gnu@4.60.1": { + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@rollup/rollup-linux-x64-musl@4.60.1": { + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@rollup/rollup-openbsd-x64@4.60.1": { + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@rollup/rollup-openharmony-arm64@4.60.1": { + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@rollup/rollup-win32-arm64-msvc@4.60.1": { + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@rollup/rollup-win32-ia32-msvc@4.60.1": { + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@rollup/rollup-win32-x64-gnu@4.60.1": { + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@rollup/rollup-win32-x64-msvc@4.60.1": { + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@testing-library/dom@10.4.1": { + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dependencies": [ + "@babel/code-frame", + "@babel/runtime", + "@types/aria-query", + "aria-query", + "dom-accessibility-api", + "lz-string", + "picocolors", + "pretty-format" + ] + }, + "@types/aria-query@5.0.4": { + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==" + }, + "@types/estree@1.0.8": { + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + }, + "@types/jsdom@28.0.1": { + "integrity": "sha512-GJq2QE4TAZ5ajSoCasn5DOFm8u1mI3tIFvM5tIq3W5U/RTB6gsHwc6Yhpl91X9VSDOUVblgXmG+2+sSvFQrdlw==", + "dependencies": [ + "@types/node@22.15.15", + "@types/tough-cookie", + "parse5", + "undici-types@7.24.7" + ] + }, + "@types/node@22.15.15": { + "integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==", + "dependencies": [ + "undici-types@6.21.0" + ] + }, + "@types/node@25.5.2": { + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "dependencies": [ + "undici-types@7.18.2" + ] + }, + "@types/react-dom@19.2.3_@types+react@19.2.14": { + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dependencies": [ + "@types/react" + ] + }, + "@types/react@19.2.14": { + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dependencies": [ + "csstype" + ] + }, + "@types/tough-cookie@4.0.5": { + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==" + }, + "@types/whatwg-mimetype@3.0.2": { + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==" + }, + "@types/ws@8.18.1": { + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dependencies": [ + "@types/node@22.15.15" + ] + }, + "acorn@8.16.0": { + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "bin": true + }, + "agent-base@7.1.4": { + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==" + }, + "ansi-regex@5.0.1": { + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles@5.2.0": { + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + }, + "any-promise@1.3.0": { + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + }, + "aria-query@5.3.0": { + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dependencies": [ + "dequal" + ] + }, + "balanced-match@4.0.4": { + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" + }, + "brace-expansion@5.0.5": { + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dependencies": [ + "balanced-match" + ] + }, + "bundle-require@5.1.0_esbuild@0.27.7": { + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dependencies": [ + "esbuild", + "load-tsconfig" + ] + }, + "cac@6.7.14": { + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==" + }, + "chokidar@4.0.3": { + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dependencies": [ + "readdirp" + ] + }, + "commander@4.1.1": { + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" + }, + "confbox@0.1.8": { + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==" + }, + "consola@3.4.2": { + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==" + }, + "cross-env@10.1.0": { + "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", + "dependencies": [ + "@epic-web/invariant", + "cross-spawn" + ], + "bin": true + }, + "cross-spawn@7.0.6": { + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": [ + "path-key", + "shebang-command", + "which" + ] + }, + "cssstyle@4.6.0": { + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dependencies": [ + "@asamuzakjp/css-color", + "rrweb-cssom" + ] + }, + "csstype@3.2.3": { + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" + }, + "data-urls@5.0.0": { + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dependencies": [ + "whatwg-mimetype@4.0.0", + "whatwg-url" + ] + }, + "debug@4.4.3": { + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": [ + "ms" + ] + }, + "decimal.js@10.6.0": { + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==" + }, + "dequal@2.0.3": { + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" + }, + "dom-accessibility-api@0.5.16": { + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==" + }, + "entities@6.0.1": { + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==" + }, + "entities@7.0.1": { + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==" + }, + "esbuild@0.27.7": { + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "optionalDependencies": [ + "@esbuild/aix-ppc64", + "@esbuild/android-arm", + "@esbuild/android-arm64", + "@esbuild/android-x64", + "@esbuild/darwin-arm64", + "@esbuild/darwin-x64", + "@esbuild/freebsd-arm64", + "@esbuild/freebsd-x64", + "@esbuild/linux-arm", + "@esbuild/linux-arm64", + "@esbuild/linux-ia32", + "@esbuild/linux-loong64", + "@esbuild/linux-mips64el", + "@esbuild/linux-ppc64", + "@esbuild/linux-riscv64", + "@esbuild/linux-s390x", + "@esbuild/linux-x64", + "@esbuild/netbsd-arm64", + "@esbuild/netbsd-x64", + "@esbuild/openbsd-arm64", + "@esbuild/openbsd-x64", + "@esbuild/openharmony-arm64", + "@esbuild/sunos-x64", + "@esbuild/win32-arm64", + "@esbuild/win32-ia32", + "@esbuild/win32-x64" + ], + "scripts": true, + "bin": true + }, + "fdir@6.5.0_picomatch@4.0.4": { + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dependencies": [ + "picomatch" + ], + "optionalPeers": [ + "picomatch" + ] + }, + "fix-dts-default-cjs-exports@1.0.1": { + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dependencies": [ + "magic-string", + "mlly", + "rollup" + ] + }, + "fsevents@2.3.3": { + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "os": ["darwin"], + "scripts": true + }, + "get-tsconfig@4.13.7": { + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "dependencies": [ + "resolve-pkg-maps" + ] + }, + "glob@13.0.6": { + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dependencies": [ + "minimatch", + "minipass", + "path-scurry" + ] + }, + "happy-dom@20.8.9": { + "integrity": "sha512-Tz23LR9T9jOGVZm2x1EPdXqwA37G/owYMxRwU0E4miurAtFsPMQ1d2Jc2okUaSjZqAFz2oEn3FLXC5a0a+siyA==", + "dependencies": [ + "@types/node@25.5.2", + "@types/whatwg-mimetype", + "@types/ws", + "entities@7.0.1", + "whatwg-mimetype@3.0.0", + "ws" + ] + }, + "html-encoding-sniffer@4.0.0": { + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dependencies": [ + "whatwg-encoding" + ] + }, + "http-proxy-agent@7.0.2": { + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": [ + "agent-base", + "debug" + ] + }, + "https-proxy-agent@7.0.6": { + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": [ + "agent-base", + "debug" + ] + }, + "iconv-lite@0.6.3": { + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": [ + "safer-buffer" + ] + }, + "is-potential-custom-element-name@1.0.1": { + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, + "isexe@2.0.0": { + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "joycon@3.1.1": { + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==" + }, + "js-tokens@4.0.0": { + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "jsdom@26.1.0": { + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dependencies": [ + "cssstyle", + "data-urls", + "decimal.js", + "html-encoding-sniffer", + "http-proxy-agent", + "https-proxy-agent", + "is-potential-custom-element-name", + "nwsapi", + "parse5", + "rrweb-cssom", + "saxes", + "symbol-tree", + "tough-cookie", + "w3c-xmlserializer", + "webidl-conversions", + "whatwg-encoding", + "whatwg-mimetype@4.0.0", + "whatwg-url", + "ws", + "xml-name-validator" + ] + }, + "jsesc@3.1.0": { + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "bin": true + }, + "lilconfig@3.1.3": { + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==" + }, + "lines-and-columns@1.2.4": { + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "load-tsconfig@0.2.5": { + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==" + }, + "lru-cache@10.4.3": { + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, + "lru-cache@11.2.7": { + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==" + }, + "lz-string@1.5.0": { + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "bin": true + }, + "magic-string@0.30.21": { + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dependencies": [ + "@jridgewell/sourcemap-codec" + ] + }, + "minimatch@10.2.5": { + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dependencies": [ + "brace-expansion" + ] + }, + "minipass@7.1.3": { + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==" + }, + "mlly@1.8.2": { + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dependencies": [ + "acorn", + "pathe", + "pkg-types", + "ufo" + ] + }, + "ms@2.1.3": { + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "mz@2.7.0": { + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": [ + "any-promise", + "object-assign", + "thenify-all" + ] + }, + "nwsapi@2.2.23": { + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==" + }, + "object-assign@4.1.1": { + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "package-json-from-dist@1.0.1": { + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, + "parse5@7.3.0": { + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dependencies": [ + "entities@6.0.1" + ] + }, + "path-key@3.1.1": { + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "path-scurry@2.0.2": { + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dependencies": [ + "lru-cache@11.2.7", + "minipass" + ] + }, + "pathe@2.0.3": { + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==" + }, + "picocolors@1.1.1": { + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "picomatch@4.0.4": { + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" + }, + "pirates@4.0.7": { + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==" + }, + "pkg-types@1.3.1": { + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dependencies": [ + "confbox", + "mlly", + "pathe" + ] + }, + "poku@4.2.0": { + "integrity": "sha512-GygMGFGgEJ9kfs6Z+QPg/ODs9OF3oGHN8+hYIxtBox3pwYISO+Vu660vH1e+YzjpGoaoy2o5y6YwE1tX5yZx3Q==", + "bin": true + }, + "postcss-load-config@6.0.1_tsx@4.21.0": { + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dependencies": [ + "lilconfig", + "tsx" + ], + "optionalPeers": [ + "tsx" + ] + }, + "prettier@3.8.1": { + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "bin": true + }, + "pretty-format@27.5.1": { + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": [ + "ansi-regex", + "ansi-styles", + "react-is" + ] + }, + "punycode@2.3.1": { + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" + }, + "react-dom@19.2.4_react@19.2.4": { + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "dependencies": [ + "react", + "scheduler" + ] + }, + "react-is@17.0.2": { + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "react@19.2.4": { + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==" + }, + "readdirp@4.1.2": { + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==" + }, + "resolve-from@5.0.0": { + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" + }, + "resolve-pkg-maps@1.0.0": { + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==" + }, + "rimraf@6.1.3": { + "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", + "dependencies": [ + "glob", + "package-json-from-dist" + ], + "bin": true + }, + "rollup@4.60.1": { + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "dependencies": [ + "@types/estree" + ], + "optionalDependencies": [ + "@rollup/rollup-android-arm-eabi", + "@rollup/rollup-android-arm64", + "@rollup/rollup-darwin-arm64", + "@rollup/rollup-darwin-x64", + "@rollup/rollup-freebsd-arm64", + "@rollup/rollup-freebsd-x64", + "@rollup/rollup-linux-arm-gnueabihf", + "@rollup/rollup-linux-arm-musleabihf", + "@rollup/rollup-linux-arm64-gnu", + "@rollup/rollup-linux-arm64-musl", + "@rollup/rollup-linux-loong64-gnu", + "@rollup/rollup-linux-loong64-musl", + "@rollup/rollup-linux-ppc64-gnu", + "@rollup/rollup-linux-ppc64-musl", + "@rollup/rollup-linux-riscv64-gnu", + "@rollup/rollup-linux-riscv64-musl", + "@rollup/rollup-linux-s390x-gnu", + "@rollup/rollup-linux-x64-gnu", + "@rollup/rollup-linux-x64-musl", + "@rollup/rollup-openbsd-x64", + "@rollup/rollup-openharmony-arm64", + "@rollup/rollup-win32-arm64-msvc", + "@rollup/rollup-win32-ia32-msvc", + "@rollup/rollup-win32-x64-gnu", + "@rollup/rollup-win32-x64-msvc", + "fsevents" + ], + "bin": true + }, + "rrweb-cssom@0.8.0": { + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==" + }, + "safer-buffer@2.1.2": { + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "saxes@6.0.0": { + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": [ + "xmlchars" + ] + }, + "scheduler@0.27.0": { + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==" + }, + "semver@7.7.4": { + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "bin": true + }, + "shebang-command@2.0.0": { + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": [ + "shebang-regex" + ] + }, + "shebang-regex@3.0.0": { + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "source-map@0.7.6": { + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==" + }, + "sucrase@3.35.1": { + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dependencies": [ + "@jridgewell/gen-mapping", + "commander", + "lines-and-columns", + "mz", + "pirates", + "tinyglobby", + "ts-interface-checker" + ], + "bin": true + }, + "symbol-tree@3.2.4": { + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "thenify-all@1.6.0": { + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dependencies": [ + "thenify" + ] + }, + "thenify@3.3.1": { + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dependencies": [ + "any-promise" + ] + }, + "tinyexec@0.3.2": { + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==" + }, + "tinyglobby@0.2.15_picomatch@4.0.4": { + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dependencies": [ + "fdir", + "picomatch" + ] + }, + "tldts-core@6.1.86": { + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==" + }, + "tldts@6.1.86": { + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dependencies": [ + "tldts-core" + ], + "bin": true + }, + "tough-cookie@5.1.2": { + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dependencies": [ + "tldts" + ] + }, + "tr46@5.1.1": { + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dependencies": [ + "punycode" + ] + }, + "tree-kill@1.2.2": { + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "bin": true + }, + "ts-interface-checker@0.1.13": { + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" + }, + "tsup@8.5.1_typescript@6.0.2_esbuild@0.27.7_tsx@4.21.0": { + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dependencies": [ + "bundle-require", + "cac", + "chokidar", + "consola", + "debug", + "esbuild", + "fix-dts-default-cjs-exports", + "joycon", + "picocolors", + "postcss-load-config", + "resolve-from", + "rollup", + "source-map", + "sucrase", + "tinyexec", + "tinyglobby", + "tree-kill", + "typescript" + ], + "optionalPeers": [ + "typescript" + ], + "bin": true + }, + "tsx@4.21.0": { + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dependencies": [ + "esbuild", + "get-tsconfig" + ], + "optionalDependencies": [ + "fsevents" + ], + "bin": true + }, + "typescript@6.0.2": { + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "bin": true + }, + "ufo@1.6.3": { + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==" + }, + "undici-types@6.21.0": { + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" + }, + "undici-types@7.18.2": { + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==" + }, + "undici-types@7.24.7": { + "integrity": "sha512-XA+gOBkzYD3C74sZowtCLTpgtaCdqZhqCvR6y9LXvrKTt/IVU6bz49T4D+BPi475scshCCkb0IklJRw6T1ZlgQ==" + }, + "w3c-xmlserializer@5.0.0": { + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dependencies": [ + "xml-name-validator" + ] + }, + "webidl-conversions@7.0.0": { + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-encoding@3.1.1": { + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dependencies": [ + "iconv-lite" + ], + "deprecated": true + }, + "whatwg-mimetype@3.0.0": { + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==" + }, + "whatwg-mimetype@4.0.0": { + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==" + }, + "whatwg-url@14.2.0": { + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dependencies": [ + "tr46", + "webidl-conversions" + ] + }, + "which@2.0.2": { + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": [ + "isexe" + ], + "bin": true + }, + "ws@8.20.0": { + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==" + }, + "xml-name-validator@5.0.0": { + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==" + }, + "xmlchars@2.2.0": { + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + } + }, + "workspace": { + "dependencies": [ + "npm:@pokujs/dom@^1.1.0" + ], + "packageJson": { + "dependencies": [ + "npm:@happy-dom/global-registrator@^20.8.9", + "npm:@ianvs/prettier-plugin-sort-imports@^4.7.0", + "npm:@pokujs/dom@^1.1.1", + "npm:@testing-library/dom@^10.4.1", + "npm:@types/jsdom@^28.0.1", + "npm:@types/node@^25.5.0", + "npm:@types/react-dom@^19.2.3", + "npm:@types/react@^19.2.14", + "npm:cross-env@^10.1.0", + "npm:happy-dom@^20.8.9", + "npm:jsdom@^26.1.0", + "npm:poku@4.2.0", + "npm:prettier@^3.6.2", + "npm:react-dom@^19.2.4", + "npm:react@^19.2.4", + "npm:rimraf@^6.0.1", + "npm:tsup@^8.5.0", + "npm:tsx@^4.21.0", + "npm:typescript@^6.0.2" + ] + } + } +} diff --git a/package-lock.json b/package-lock.json index aea137f..381ea60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "devDependencies": { "@happy-dom/global-registrator": "^20.8.9", "@ianvs/prettier-plugin-sort-imports": "^4.7.0", + "@pokujs/dom": "^1.1.2", "@types/jsdom": "^28.0.1", "@types/node": "^25.5.0", "@types/react": "^19.2.14", @@ -70,13 +71,6 @@ "lru-cache": "^10.4.3" } }, - "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, "node_modules/@babel/code-frame": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -333,9 +327,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", - "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "cpu": [ "ppc64" ], @@ -350,9 +344,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", - "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "cpu": [ "arm" ], @@ -367,9 +361,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", - "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "cpu": [ "arm64" ], @@ -384,9 +378,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", - "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", "cpu": [ "x64" ], @@ -401,9 +395,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", - "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", "cpu": [ "arm64" ], @@ -418,9 +412,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", - "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", "cpu": [ "x64" ], @@ -435,9 +429,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", - "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", "cpu": [ "arm64" ], @@ -452,9 +446,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", - "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", "cpu": [ "x64" ], @@ -469,9 +463,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", - "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", "cpu": [ "arm" ], @@ -486,9 +480,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", - "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", "cpu": [ "arm64" ], @@ -503,9 +497,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", - "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", "cpu": [ "ia32" ], @@ -520,9 +514,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", - "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", "cpu": [ "loong64" ], @@ -537,9 +531,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", - "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", "cpu": [ "mips64el" ], @@ -554,9 +548,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", - "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", "cpu": [ "ppc64" ], @@ -571,9 +565,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", - "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", "cpu": [ "riscv64" ], @@ -588,9 +582,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", - "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", "cpu": [ "s390x" ], @@ -605,9 +599,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", - "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", "cpu": [ "x64" ], @@ -622,9 +616,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", - "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", "cpu": [ "arm64" ], @@ -639,9 +633,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", - "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", "cpu": [ "x64" ], @@ -656,9 +650,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", - "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", "cpu": [ "arm64" ], @@ -673,9 +667,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", - "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", "cpu": [ "x64" ], @@ -690,9 +684,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", - "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", "cpu": [ "arm64" ], @@ -707,9 +701,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", - "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", "cpu": [ "x64" ], @@ -724,9 +718,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", - "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", "cpu": [ "arm64" ], @@ -741,9 +735,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", - "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", "cpu": [ "ia32" ], @@ -758,9 +752,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", - "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", "cpu": [ "x64" ], @@ -862,6 +856,35 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@pokujs/dom": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@pokujs/dom/-/dom-1.1.2.tgz", + "integrity": "sha512-Bs9blOGDABsgNdKBoMt7EoB0attpI83VF+LCfc2qU1BVFJQ6bRWFqniNIFWZAJOfT+qlyRcxIITmpQHYlRmLew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@testing-library/dom": "^10.4.1" + }, + "engines": { + "bun": ">=1.x.x", + "deno": ">=2.x.x", + "node": ">=20.x.x", + "typescript": ">=6.x.x" + }, + "peerDependencies": { + "happy-dom": ">=20", + "jsdom": ">=22", + "poku": ">=4.1.0" + }, + "peerDependenciesMeta": { + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.60.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", @@ -1257,23 +1280,23 @@ "undici-types": "^7.21.0" } }, - "node_modules/@types/jsdom/node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", - "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.18.0" } }, + "node_modules/@types/node/node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", @@ -1602,9 +1625,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", - "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1615,32 +1638,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.4", - "@esbuild/android-arm": "0.27.4", - "@esbuild/android-arm64": "0.27.4", - "@esbuild/android-x64": "0.27.4", - "@esbuild/darwin-arm64": "0.27.4", - "@esbuild/darwin-x64": "0.27.4", - "@esbuild/freebsd-arm64": "0.27.4", - "@esbuild/freebsd-x64": "0.27.4", - "@esbuild/linux-arm": "0.27.4", - "@esbuild/linux-arm64": "0.27.4", - "@esbuild/linux-ia32": "0.27.4", - "@esbuild/linux-loong64": "0.27.4", - "@esbuild/linux-mips64el": "0.27.4", - "@esbuild/linux-ppc64": "0.27.4", - "@esbuild/linux-riscv64": "0.27.4", - "@esbuild/linux-s390x": "0.27.4", - "@esbuild/linux-x64": "0.27.4", - "@esbuild/netbsd-arm64": "0.27.4", - "@esbuild/netbsd-x64": "0.27.4", - "@esbuild/openbsd-arm64": "0.27.4", - "@esbuild/openbsd-x64": "0.27.4", - "@esbuild/openharmony-arm64": "0.27.4", - "@esbuild/sunos-x64": "0.27.4", - "@esbuild/win32-arm64": "0.27.4", - "@esbuild/win32-ia32": "0.27.4", - "@esbuild/win32-x64": "0.27.4" + "@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" } }, "node_modules/fdir": { @@ -1689,9 +1712,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1915,14 +1938,11 @@ } }, "node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } + "license": "ISC" }, "node_modules/lz-string": { "version": "1.5.0", @@ -2078,6 +2098,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -2672,9 +2702,9 @@ "license": "MIT" }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.7.tgz", + "integrity": "sha512-XA+gOBkzYD3C74sZowtCLTpgtaCdqZhqCvR6y9LXvrKTt/IVU6bz49T4D+BPi475scshCCkb0IklJRw6T1ZlgQ==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 87c4893..e8927e8 100644 --- a/package.json +++ b/package.json @@ -31,11 +31,19 @@ "typescript": ">=6.x.x" }, "scripts": { - "test": "npm run test:happy && npm run test:jsdom", + "test": "node --import=tsx run.test.ts", "test:happy": "cross-env POKU_REACT_TEST_DOM=happy-dom node --import=tsx ./node_modules/poku/lib/bin/index.js tests --showLogs", + "test:happy:none": "cross-env POKU_REACT_TEST_DOM=happy-dom node --import=tsx ./node_modules/poku/lib/bin/index.js tests --showLogs --isolation=none", + "test:happy:process": "cross-env POKU_REACT_TEST_DOM=happy-dom node --import=tsx ./node_modules/poku/lib/bin/index.js tests --showLogs --isolation=process", "test:jsdom": "cross-env POKU_REACT_TEST_DOM=jsdom node --import=tsx ./node_modules/poku/lib/bin/index.js tests --showLogs", - "test:bun": "cross-env POKU_REACT_TEST_DOM=happy-dom bun ./node_modules/poku/lib/bin/index.js tests --showLogs && cross-env POKU_REACT_TEST_DOM=jsdom bun ./node_modules/poku/lib/bin/index.js tests --showLogs", - "test:deno": "cross-env POKU_REACT_TEST_DOM=happy-dom deno run -A npm:poku tests --showLogs", + "test:jsdom:none": "cross-env POKU_REACT_TEST_DOM=jsdom node --import=tsx ./node_modules/poku/lib/bin/index.js tests --showLogs --isolation=none", + "test:jsdom:process": "cross-env POKU_REACT_TEST_DOM=jsdom node --import=tsx ./node_modules/poku/lib/bin/index.js tests --showLogs --isolation=process", + "test:bun": "bun run.test.ts", + "test:bun:none": "cross-env POKU_REACT_TEST_DOM=happy-dom bun ./node_modules/poku/lib/bin/index.js tests --showLogs --isolation=none && cross-env POKU_REACT_TEST_DOM=jsdom bun ./node_modules/poku/lib/bin/index.js tests --showLogs --isolation=none", + "test:bun:process": "cross-env POKU_REACT_TEST_DOM=happy-dom bun ./node_modules/poku/lib/bin/index.js tests --showLogs --isolation=process && cross-env POKU_REACT_TEST_DOM=jsdom bun ./node_modules/poku/lib/bin/index.js tests --showLogs --isolation=process", + "test:deno": "deno run -A run.test.ts", + "test:deno:none": "cross-env POKU_REACT_TEST_DOM=happy-dom deno run -A npm:poku tests --showLogs --isolation=none", + "test:deno:process": "cross-env POKU_REACT_TEST_DOM=happy-dom deno run -A npm:poku tests --showLogs --isolation=process", "clean": "rimraf dist", "build": "tsup src/index.ts src/plugin.ts src/react-testing.ts src/dom-setup-happy.ts src/dom-setup-jsdom.ts --format esm --dts --target node20 --sourcemap --clean --tsconfig tsconfig.tsup.json", "typecheck": "tsc -p tsconfig.build.json --noEmit", @@ -90,10 +98,11 @@ } }, "devDependencies": { - "@ianvs/prettier-plugin-sort-imports": "^4.7.0", "@happy-dom/global-registrator": "^20.8.9", - "@types/node": "^25.5.0", + "@ianvs/prettier-plugin-sort-imports": "^4.7.0", + "@pokujs/dom": "^1.1.2", "@types/jsdom": "^28.0.1", + "@types/node": "^25.5.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "cross-env": "^10.1.0", diff --git a/run.test.ts b/run.test.ts new file mode 100644 index 0000000..1bb466c --- /dev/null +++ b/run.test.ts @@ -0,0 +1,22 @@ +import { assert, poku } from 'poku'; +import { reactTestingPlugin } from './src/plugin.ts'; + +// isolation: 'none' — test files run in the same process; suites must be sequential +const happyCode = await poku('tests', { + noExit: true, + isolation: 'none', + plugins: [reactTestingPlugin({ dom: 'happy-dom' })], +}); + +assert.strictEqual(happyCode, 0, 'happy-dom suite'); + +// jsdom is not compatible with Deno +if (typeof Deno === 'undefined') { + const jsdomCode = await poku('tests', { + noExit: true, + isolation: 'none', + plugins: [reactTestingPlugin({ dom: 'jsdom' })], + }); + + assert.strictEqual(jsdomCode, 0, 'jsdom suite'); +} diff --git a/src/dom-setup-happy.ts b/src/dom-setup-happy.ts index 22ac062..aee9240 100644 --- a/src/dom-setup-happy.ts +++ b/src/dom-setup-happy.ts @@ -1,34 +1,8 @@ -import { GlobalRegistrator } from '@happy-dom/global-registrator'; +import { setupHappyDomEnvironment } from '@pokujs/dom'; import { parseRuntimeOptions } from './runtime-options.ts'; -declare const Deno: unknown; - -type ReactActGlobal = typeof globalThis & { - IS_REACT_ACT_ENVIRONMENT?: boolean; -}; - -const reactGlobal = globalThis as ReactActGlobal; - -const configuredUrl = parseRuntimeOptions().domUrl; - -if (!globalThis.window || !globalThis.document) { - const isDenoRuntime = typeof Deno !== 'undefined'; - const nativeEvent = isDenoRuntime ? globalThis.Event : undefined; - const nativeDispatchEvent = isDenoRuntime - ? globalThis.dispatchEvent?.bind(globalThis) - : undefined; - - GlobalRegistrator.register({ - url: configuredUrl, - }); - - if (isDenoRuntime) { - if (nativeEvent) - (globalThis as unknown as Record).Event = nativeEvent; - if (nativeDispatchEvent) globalThis.dispatchEvent = nativeDispatchEvent; - } -} - -if (typeof reactGlobal.IS_REACT_ACT_ENVIRONMENT === 'undefined') { - reactGlobal.IS_REACT_ACT_ENVIRONMENT = true; -} +await setupHappyDomEnvironment({ + runtimeOptions: parseRuntimeOptions(), + packageTag: '@pokujs/react', + enableReactActEnvironment: true, +}); diff --git a/src/dom-setup-jsdom.ts b/src/dom-setup-jsdom.ts index 0bc62a1..4adc456 100644 --- a/src/dom-setup-jsdom.ts +++ b/src/dom-setup-jsdom.ts @@ -1,52 +1,8 @@ +import { setupJsdomEnvironment } from '@pokujs/dom'; import { parseRuntimeOptions } from './runtime-options.ts'; -const configuredUrl = parseRuntimeOptions().domUrl; - -type ReactActGlobal = typeof globalThis & { - IS_REACT_ACT_ENVIRONMENT?: boolean; -}; - -const reactGlobal = globalThis as ReactActGlobal; - -const defineGlobal = (key: keyof typeof globalThis, value: unknown) => { - Object.defineProperty(globalThis, key, { - configurable: true, - writable: true, - value, - }); -}; - -const createJSDOMEnvironment = async () => { - let mod: typeof import('jsdom'); - - try { - mod = await import('jsdom'); - } catch { - throw new Error( - '[@pokujs/react] DOM adapter "jsdom" requires the "jsdom" package. Install it with "npm install --save-dev jsdom".' - ); - } - - const dom = new mod.JSDOM('', { - url: configuredUrl, - }); - - const { window } = dom; - - defineGlobal('window', window as unknown as Window & typeof globalThis); - defineGlobal('document', window.document); - defineGlobal('navigator', window.navigator); - defineGlobal('HTMLElement', window.HTMLElement); - defineGlobal('Node', window.Node); - defineGlobal('Event', window.Event); - defineGlobal('CustomEvent', window.CustomEvent); - defineGlobal('MouseEvent', window.MouseEvent); -}; - -if (!globalThis.window || !globalThis.document) { - await createJSDOMEnvironment(); -} - -if (typeof reactGlobal.IS_REACT_ACT_ENVIRONMENT === 'undefined') { - reactGlobal.IS_REACT_ACT_ENVIRONMENT = true; -} +await setupJsdomEnvironment({ + runtimeOptions: parseRuntimeOptions(), + packageTag: '@pokujs/react', + enableReactActEnvironment: true, +}); diff --git a/src/plugin-command.ts b/src/plugin-command.ts index c516049..a1c6e1d 100644 --- a/src/plugin-command.ts +++ b/src/plugin-command.ts @@ -1,14 +1,19 @@ import type { ReactDomAdapter } from './plugin-types.ts'; -import { existsSync } from 'node:fs'; -import { dirname, extname, resolve } from 'node:path'; +import { + buildRunnerCommand as buildCoreRunnerCommand, + canHandleRuntime, + createDomSetupPathResolver, + type BuildRunnerCommandInput, +} from '@pokujs/dom'; +import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { existsSync } from 'node:fs'; const currentDir = dirname(fileURLToPath(import.meta.url)); const resolveSetupModulePath = (baseName: string) => { const jsPath = resolve(currentDir, `${baseName}.js`); if (existsSync(jsPath)) return jsPath; - return resolve(currentDir, `${baseName}.ts`); }; @@ -17,129 +22,14 @@ const jsdomSetupPath = resolveSetupModulePath('dom-setup-jsdom'); const reactExtensions = new Set(['.tsx', '.jsx']); -export type RuntimeSupport = { - supportsNodeLikeImport: boolean; - supportsDenoPreload: boolean; -}; - -export type BuildRunnerCommandInput = { - runtime: string; - command: string[]; - file: string; - domSetupPath: string; - runtimeOptionArgs: string[]; -}; - -export type BuildRunnerCommandOutput = { - shouldHandle: boolean; - command: string[]; -}; - -const isTsxImport = (arg: string) => - arg === '--import=tsx' || arg === '--loader=tsx'; - -export const isNodeRuntime = (runtime: string) => runtime === 'node'; -const isBunRuntime = (runtime: string) => runtime === 'bun'; -const isDenoRuntime = (runtime: string) => runtime === 'deno'; - -export const getRuntimeSupport = (runtime: string): RuntimeSupport => ({ - supportsNodeLikeImport: isNodeRuntime(runtime) || isBunRuntime(runtime), - supportsDenoPreload: isDenoRuntime(runtime), -}); - -export const canHandleRuntime = (runtime: string) => { - const support = getRuntimeSupport(runtime); - return support.supportsNodeLikeImport || support.supportsDenoPreload; -}; - -export const resolveDomSetupPath = (adapter: ReactDomAdapter | undefined) => { - if (!adapter || adapter === 'happy-dom') return happyDomSetupPath; - if (adapter === 'jsdom') return jsdomSetupPath; +export const resolveDomSetupPath = createDomSetupPathResolver( + '@pokujs/react', + happyDomSetupPath, + jsdomSetupPath +); - const customPath = resolve(process.cwd(), adapter.setupModule); +export const buildRunnerCommand = ( + input: Omit +) => buildCoreRunnerCommand({ ...input, extensions: reactExtensions }); - if (!existsSync(customPath)) { - throw new Error( - `[@pokujs/react] Custom DOM setup module not found: "${customPath}"\n` + - `Check the "dom.setupModule" option in your poku.config.js.` - ); - } - - return customPath; -}; - -export const buildRunnerCommand = ({ - runtime, - command, - file, - domSetupPath, - runtimeOptionArgs, -}: BuildRunnerCommandInput): BuildRunnerCommandOutput => { - const support = getRuntimeSupport(runtime); - - if (!support.supportsNodeLikeImport && !support.supportsDenoPreload) { - return { shouldHandle: false, command }; - } - - if (!reactExtensions.has(extname(file))) { - return { shouldHandle: false, command }; - } - - const fileIndex = command.lastIndexOf(file); - if (fileIndex === -1) return { shouldHandle: false, command }; - - const nodeImportFlag = `--import=${domSetupPath}`; - const denoPreloadFlag = `--preload=${domSetupPath}`; - const beforeFile: string[] = []; - const afterFile: string[] = []; - - let hasTsx = false; - let hasNodeLikeDomSetup = false; - let hasDenoDomSetup = false; - const existingArgs = new Set(); - - for (let index = 1; index < command.length; index += 1) { - const arg = command[index]; - if (typeof arg !== 'string') continue; - - existingArgs.add(arg); - - if (index < fileIndex) { - beforeFile.push(arg); - - if (isTsxImport(arg)) hasTsx = true; - else if (arg === nodeImportFlag) hasNodeLikeDomSetup = true; - else if (arg === denoPreloadFlag) hasDenoDomSetup = true; - continue; - } - - if (index > fileIndex) { - afterFile.push(arg); - } - } - - const extraImports: string[] = []; - if (isNodeRuntime(runtime) && !hasTsx) extraImports.push('--import=tsx'); - if (support.supportsNodeLikeImport && !hasNodeLikeDomSetup) - extraImports.push(nodeImportFlag); - if (support.supportsDenoPreload && !hasDenoDomSetup) - extraImports.push(denoPreloadFlag); - - const runtimeArgsToInject: string[] = []; - for (const runtimeOptionArg of runtimeOptionArgs) { - if (existingArgs.has(runtimeOptionArg)) continue; - runtimeArgsToInject.push(runtimeOptionArg); - } - - return { - shouldHandle: true, - command: [ - runtime, - ...beforeFile, - ...extraImports, - file, - ...runtimeArgsToInject, - ...afterFile, - ], - }; -}; +export { canHandleRuntime }; diff --git a/src/plugin-metrics.ts b/src/plugin-metrics.ts index 4fbf3e3..eff23cb 100644 --- a/src/plugin-metrics.ts +++ b/src/plugin-metrics.ts @@ -4,158 +4,40 @@ import type { ReactTestingPluginOptions, RenderMetric, } from './plugin-types.ts'; +import { + buildRuntimeOptionArgs as buildCoreRuntimeOptionArgs, + createMetricsSummary, + getComponentName, + isRenderMetricBatchMessage as isCoreRenderMetricBatchMessage, + isRenderMetricMessage as isCoreRenderMetricMessage, + normalizeMetricsOptions, + printMetricsSummary as printCoreMetricsSummary, + selectTopSlowestMetrics, +} from '@pokujs/dom'; import { runtimeOptionArgPrefixes } from './runtime-options.ts'; -type RenderMetricMessage = { - type: 'POKU_REACT_RENDER_METRIC'; - componentName?: string; - durationMs?: number; -}; - -type RenderMetricBatchMessage = { - type: 'POKU_REACT_RENDER_METRIC_BATCH'; - metrics: Array<{ - componentName?: string; - durationMs?: number; - }>; -}; - -const DEFAULT_TOP_N = 5; -const DEFAULT_MIN_DURATION_MS = 0; - -export const isRenderMetricMessage = ( - message: unknown -): message is RenderMetricMessage => { - if (!message || typeof message !== 'object') return false; - return ( - (message as Record).type === 'POKU_REACT_RENDER_METRIC' - ); -}; - -export const isRenderMetricBatchMessage = ( - message: unknown -): message is RenderMetricBatchMessage => { - if (!message || typeof message !== 'object') return false; +const REACT_RENDER_METRIC = 'POKU_REACT_RENDER_METRIC'; +const REACT_RENDER_METRIC_BATCH = 'POKU_REACT_RENDER_METRIC_BATCH'; - const record = message as Record; - return ( - record.type === 'POKU_REACT_RENDER_METRIC_BATCH' && - Array.isArray(record.metrics) - ); -}; - -export const getComponentName = (componentName: unknown) => - typeof componentName === 'string' && componentName.length > 0 - ? componentName - : 'AnonymousComponent'; +export const isRenderMetricMessage = (message: unknown) => + isCoreRenderMetricMessage(message, REACT_RENDER_METRIC); -const getPositiveIntegerOrDefault = (value: unknown, fallback: number) => { - const numeric = - typeof value === 'number' - ? value - : typeof value === 'string' && value.trim().length > 0 - ? Number(value.trim()) - : NaN; - - if (!Number.isFinite(numeric) || numeric <= 0) return fallback; - return Math.floor(numeric); -}; - -const getNonNegativeNumberOrDefault = (value: unknown, fallback: number) => { - const numeric = - typeof value === 'number' - ? value - : typeof value === 'string' && value.trim().length > 0 - ? Number(value.trim()) - : NaN; - - if (!Number.isFinite(numeric) || numeric < 0) return fallback; - return numeric; -}; +export const isRenderMetricBatchMessage = (message: unknown) => + isCoreRenderMetricBatchMessage(message, REACT_RENDER_METRIC_BATCH); export const buildRuntimeOptionArgs = ( options: ReactTestingPluginOptions, metricsOptions: NormalizedMetricsOptions -) => { - const args: string[] = []; +) => buildCoreRuntimeOptionArgs(options, metricsOptions, runtimeOptionArgPrefixes); - if (options.domUrl) { - args.push(`${runtimeOptionArgPrefixes.domUrl}${options.domUrl}`); - } +export const printMetricsSummary = (summary: ReactMetricsSummary) => + printCoreMetricsSummary(summary, '@pokujs/react'); - if (metricsOptions.enabled) { - args.push(`${runtimeOptionArgPrefixes.metrics}1`); - args.push( - `${runtimeOptionArgPrefixes.minMetricMs}${metricsOptions.minDurationMs}` - ); - } - - return args; -}; - -export const normalizeMetricsOptions = ( - metrics: ReactTestingPluginOptions['metrics'] -): NormalizedMetricsOptions => { - if (metrics === true) { - return { - enabled: true, - topN: DEFAULT_TOP_N, - minDurationMs: DEFAULT_MIN_DURATION_MS, - }; - } - - if (!metrics) { - return { - enabled: false, - topN: DEFAULT_TOP_N, - minDurationMs: DEFAULT_MIN_DURATION_MS, - }; - } - - const normalized: NormalizedMetricsOptions = { - enabled: metrics.enabled ?? true, - topN: getPositiveIntegerOrDefault(metrics.topN, DEFAULT_TOP_N), - minDurationMs: getNonNegativeNumberOrDefault( - metrics.minDurationMs, - DEFAULT_MIN_DURATION_MS - ), - }; - - if (metrics.reporter) normalized.reporter = metrics.reporter; - - return normalized; +export { + createMetricsSummary, + getComponentName, + normalizeMetricsOptions, + selectTopSlowestMetrics, }; -export const selectTopSlowestMetrics = ( - metrics: RenderMetric[], - options: NormalizedMetricsOptions -) => - [...metrics] - .sort((a, b) => b.durationMs - a.durationMs) - .slice(0, options.topN); - -export const createMetricsSummary = ( - metrics: RenderMetric[], - options: NormalizedMetricsOptions -): ReactMetricsSummary | null => { - if (!options.enabled || metrics.length === 0) return null; - - const topSlowest = selectTopSlowestMetrics(metrics, options); - if (topSlowest.length === 0) return null; - - return { - totalCaptured: metrics.length, - totalReported: topSlowest.length, - topSlowest, - }; -}; - -export const printMetricsSummary = (summary: ReactMetricsSummary) => { - const lines = summary.topSlowest.map( - (metric) => - ` - ${metric.componentName} in ${metric.file}: ${metric.durationMs.toFixed(2)}ms` - ); - - console.log('\n[@pokujs/react] Slowest component renders'); - for (const line of lines) console.log(line); -}; +export type { RenderMetric }; diff --git a/src/plugin-setup.ts b/src/plugin-setup.ts index ae6b625..5142530 100644 --- a/src/plugin-setup.ts +++ b/src/plugin-setup.ts @@ -1,62 +1 @@ -import { pathToFileURL } from 'node:url'; -import { canHandleRuntime, isNodeRuntime } from './plugin-command.ts'; - -type TsxEsmApiModule = { - register?: () => () => void; -}; - -const TSX_LOADER_MODULE = 'tsx/esm/api'; - -const appendMissingRuntimeArgs = (runtimeOptionArgs: string[]) => { - for (const arg of runtimeOptionArgs) { - if (process.argv.includes(arg)) continue; - process.argv.push(arg); - } -}; - -const loadDomSetupModule = async (domSetupPath: string) => { - await import(pathToFileURL(domSetupPath).href); -}; - -const registerNodeTsxLoader = async () => { - const moduleName = TSX_LOADER_MODULE; - - try { - const mod = (await import(moduleName)) as TsxEsmApiModule; - if (typeof mod.register !== 'function') { - throw new Error('Missing register() export from tsx loader API'); - } - - return mod.register(); - } catch (error) { - throw new Error( - '[@pokujs/react] isolation "none" in Node.js requires a working "tsx" installation to load .tsx/.jsx test files.', - { cause: error } - ); - } -}; - -export type InProcessSetupOptions = { - isolation: string | undefined; - runtime: string; - runtimeOptionArgs: string[]; - domSetupPath: string; -}; - -export const setupInProcessEnvironment = async ( - options: InProcessSetupOptions -): Promise<(() => void) | undefined> => { - if (options.isolation !== 'none') return undefined; - if (!canHandleRuntime(options.runtime)) return undefined; - - let cleanupNodeTsxLoader: (() => void) | undefined; - - if (isNodeRuntime(options.runtime)) { - cleanupNodeTsxLoader = await registerNodeTsxLoader(); - } - - appendMissingRuntimeArgs(options.runtimeOptionArgs); - await loadDomSetupModule(options.domSetupPath); - - return cleanupNodeTsxLoader; -}; +export { setupInProcessEnvironment } from '@pokujs/dom'; diff --git a/src/plugin-types.ts b/src/plugin-types.ts index 2cd798c..1330644 100644 --- a/src/plugin-types.ts +++ b/src/plugin-types.ts @@ -1,60 +1,18 @@ -export type ReactDomAdapter = 'happy-dom' | 'jsdom' | { setupModule: string }; +import type { + DomAdapter, + MetricsOptions, + MetricsSummary, + NormalizedMetricsOptions, + RenderMetric, + TestingPluginOptions, +} from '@pokujs/dom'; -export type RenderMetric = { - file: string; - componentName: string; - durationMs: number; -}; - -export type ReactMetricsSummary = { - totalCaptured: number; - totalReported: number; - topSlowest: RenderMetric[]; -}; - -export type ReactMetricsOptions = { - /** - * Enable or disable render metrics collection. - */ - enabled?: boolean; - /** - * Maximum number of rows to display/report. - * @default 5 - */ - topN?: number; - /** - * Minimum duration to include in the final report. - * @default 0 - */ - minDurationMs?: number; - /** - * Custom reporter. Falls back to console output when omitted. - */ - reporter?: (summary: ReactMetricsSummary) => void; -}; - -export type ReactTestingPluginOptions = { - /** - * DOM implementation used by test file processes. - * - * - `happy-dom`: fast default suitable for most component tests. - * - `jsdom`: broader compatibility for browser-like APIs. - * - `{ setupModule }`: custom module that prepares globals. - */ - dom?: ReactDomAdapter; - /** - * URL assigned to the DOM environment. - */ - domUrl?: string; - /** - * Render metrics configuration. Disabled by default for production-safe behavior. - */ - metrics?: boolean | ReactMetricsOptions; -}; +export type ReactDomAdapter = DomAdapter; +export type ReactMetricsSummary = MetricsSummary; +export type ReactMetricsOptions = MetricsOptions; +export type ReactTestingPluginOptions = TestingPluginOptions; -export type NormalizedMetricsOptions = { - enabled: boolean; - topN: number; - minDurationMs: number; - reporter?: (summary: ReactMetricsSummary) => void; +export type { + NormalizedMetricsOptions, + RenderMetric, }; diff --git a/src/plugin.ts b/src/plugin.ts index 3046719..a3ac48a 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -3,9 +3,11 @@ import type { ReactMetricsOptions, ReactMetricsSummary, ReactTestingPluginOptions, - RenderMetric, } from './plugin-types.ts'; -import { definePlugin } from 'poku/plugins'; +import { + createFrameworkTestingPluginFactory, +} from '@pokujs/dom'; +import type { FrameworkDescriptor } from '@pokujs/dom'; import { buildRunnerCommand, canHandleRuntime, @@ -18,10 +20,21 @@ import { isRenderMetricBatchMessage, isRenderMetricMessage, normalizeMetricsOptions, - printMetricsSummary, selectTopSlowestMetrics, } from './plugin-metrics.ts'; -import { setupInProcessEnvironment } from './plugin-setup.ts'; + +const descriptor: FrameworkDescriptor = { + pluginName: 'react-testing', + packageTag: '@pokujs/react', + runtimeArgBase: 'poku-react', + metricMessageType: 'POKU_REACT_RENDER_METRIC', + metricBatchMessageType: 'POKU_REACT_RENDER_METRIC_BATCH', +}; + +const { createTestingPlugin } = createFrameworkTestingPluginFactory( + descriptor, + import.meta.url +); export type { ReactDomAdapter, @@ -30,103 +43,9 @@ export type { ReactTestingPluginOptions, }; -/** - * Create a Poku plugin that prepares DOM globals and TSX execution for React tests. - */ export const createReactTestingPlugin = ( options: ReactTestingPluginOptions = {} -) => { - let metrics: RenderMetric[] = []; - let cleanupNodeTsxLoader: (() => void) | undefined; - const domSetupPath = resolveDomSetupPath(options.dom); - const metricsOptions = normalizeMetricsOptions(options.metrics); - const runtimeOptionArgs = buildRuntimeOptionArgs(options, metricsOptions); - - return definePlugin({ - name: 'react-testing', - ipc: metricsOptions.enabled, - - async setup(context) { - cleanupNodeTsxLoader = await setupInProcessEnvironment({ - isolation: context.configs.isolation, - runtime: context.runtime, - runtimeOptionArgs, - domSetupPath, - }); - }, - - runner(command, file) { - const runtime = command[0]; - if (!runtime) return command; - const result = buildRunnerCommand({ - runtime, - command, - file, - domSetupPath, - runtimeOptionArgs, - }); - - if (!result.shouldHandle) return command; - return result.command; - }, - - onTestProcess(child, file) { - if (!metricsOptions.enabled) return; - - // Optimization: Prevent unbounded memory growth on massive suites. - // Prune array back down periodically to keep only top candidates. - const maybePruneMetrics = () => { - if (metrics.length > metricsOptions.topN * 10) { - metrics = selectTopSlowestMetrics(metrics, metricsOptions); - } - }; - - child.on('message', (message) => { - if (isRenderMetricBatchMessage(message)) { - for (const metric of message.metrics) { - const durationMs = Number(metric.durationMs) || 0; - - metrics.push({ - file, - componentName: getComponentName(metric.componentName), - durationMs, - }); - } - - maybePruneMetrics(); - return; - } - - if (!isRenderMetricMessage(message)) return; - - const durationMs = Number(message.durationMs) || 0; - - metrics.push({ - file, - componentName: getComponentName(message.componentName), - durationMs, - }); - - maybePruneMetrics(); - }); - }, - - teardown() { - cleanupNodeTsxLoader?.(); - cleanupNodeTsxLoader = undefined; - - const summary = createMetricsSummary(metrics, metricsOptions); - if (!summary) return; - - if (metricsOptions.reporter) { - metricsOptions.reporter(summary); - return; - } - - printMetricsSummary(summary); - }, - }); -}; +) => createTestingPlugin(options); export const reactTestingPlugin = createReactTestingPlugin; diff --git a/src/react-testing.ts b/src/react-testing.ts index d201c28..e88796a 100644 --- a/src/react-testing.ts +++ b/src/react-testing.ts @@ -9,11 +9,15 @@ import { getQueriesForElement, queries } from '@testing-library/dom'; import * as domTestingLibrary from '@testing-library/dom'; import React from 'react'; import { createRoot } from 'react-dom/client'; +import { + createRenderMetricsEmitter, + createScreen, + getNow, +} from '@pokujs/dom'; import { parseRuntimeOptions } from './runtime-options.ts'; const { act } = React; -/** React `act` re-export for explicit async orchestration in tests. */ export { act }; type WrapperComponent = ComponentType>; @@ -42,11 +46,6 @@ const unmountMounted = (mounted: InternalMounted) => { } }; -const getNow: () => number = - typeof performance !== 'undefined' && typeof performance.now === 'function' - ? performance.now.bind(performance) - : Date.now.bind(Date); - const getComponentName = (ui: ReactElement) => { const uiType = ui.type; if (!uiType) return 'AnonymousComponent'; @@ -57,136 +56,11 @@ const getComponentName = (ui: ReactElement) => { }; const runtimeOptions = parseRuntimeOptions(); -const metricsEnabled = runtimeOptions.metricsEnabled; -const minMetricMs = runtimeOptions.minMetricMs; - -type QueuedRenderMetric = { - componentName: string; - durationMs: number; -}; - -type MetricsRuntimeState = { - metricBuffer: QueuedRenderMetric[]; - metricFlushTimer: ReturnType | undefined; - metricsChannelClosed: boolean; - listenersRegistered: boolean; -}; - -const metricsStateKey = Symbol.for('@pokujs/react.metrics-runtime-state'); - -type MetricsStateGlobal = typeof globalThis & { - [metricsStateKey]?: MetricsRuntimeState; -}; - -const getMetricsRuntimeState = (): MetricsRuntimeState => { - const stateGlobal = globalThis as MetricsStateGlobal; - - if (!stateGlobal[metricsStateKey]) { - stateGlobal[metricsStateKey] = { - metricBuffer: [], - metricFlushTimer: undefined, - metricsChannelClosed: false, - listenersRegistered: false, - }; - } - - return stateGlobal[metricsStateKey]; -}; - -const metricsState = getMetricsRuntimeState(); - -const flushMetricBuffer = () => { - if (!metricsEnabled || typeof process.send !== 'function') return; - - if (process.connected === false) { - metricsState.metricBuffer.length = 0; - metricsState.metricsChannelClosed = true; - return; - } - - if ( - metricsState.metricsChannelClosed || - metricsState.metricBuffer.length === 0 - ) - return; - - const payload = metricsState.metricBuffer.splice( - 0, - metricsState.metricBuffer.length - ); - - try { - process.send({ - type: 'POKU_REACT_RENDER_METRIC_BATCH', - metrics: payload, - }); - } catch { - metricsState.metricsChannelClosed = true; - metricsState.metricBuffer.length = 0; - } -}; - -const clearMetricFlushTimer = () => { - if (!metricsState.metricFlushTimer) return; - clearTimeout(metricsState.metricFlushTimer); - metricsState.metricFlushTimer = undefined; -}; - -const scheduleMetricFlush = () => { - if (metricsState.metricFlushTimer) return; - - metricsState.metricFlushTimer = setTimeout(() => { - metricsState.metricFlushTimer = undefined; - flushMetricBuffer(); - }, runtimeOptions.metricFlushMs); - - metricsState.metricFlushTimer.unref?.(); -}; - -if (metricsEnabled && !metricsState.listenersRegistered) { - metricsState.listenersRegistered = true; - - process.on('beforeExit', () => { - clearMetricFlushTimer(); - flushMetricBuffer(); - }); - - process.on('disconnect', () => { - clearMetricFlushTimer(); - metricsState.metricBuffer.length = 0; - metricsState.metricsChannelClosed = true; - }); -} - -const emitRenderMetric = (componentName: string, durationMs: number) => { - if (!metricsEnabled || typeof process.send !== 'function') return; - - if (process.connected === false || metricsState.metricsChannelClosed) { - metricsState.metricBuffer.length = 0; - metricsState.metricsChannelClosed = true; - clearMetricFlushTimer(); - return; - } - - const safeDuration = - Number.isFinite(durationMs) && durationMs >= 0 ? durationMs : 0; - - // Optimization: Drop metrics below the threshold to prevent IPC flooding - if (safeDuration < minMetricMs) return; - - metricsState.metricBuffer.push({ - componentName, - durationMs: safeDuration, - }); - - if (metricsState.metricBuffer.length >= runtimeOptions.metricBatchSize) { - clearMetricFlushTimer(); - flushMetricBuffer(); - return; - } - - scheduleMetricFlush(); -}; +const metrics = createRenderMetricsEmitter({ + runtimeOptions, + metricsStateKey: Symbol.for('@pokujs/react.metrics-runtime-state'), + metricsBatchMessageType: 'POKU_REACT_RENDER_METRIC_BATCH', +}); const wrapUi = (ui: ReactElement, Wrapper?: WrapperComponent) => Wrapper ? React.createElement(Wrapper, null, ui) : ui; @@ -205,9 +79,6 @@ export type RenderResult = BoundFunctions & { unmount: () => void; }; -/** - * Render a React element in an isolated container and return bound DOM queries. - */ export const render = ( ui: ReactElement, options: RenderOptions = {} @@ -233,7 +104,7 @@ export const render = ( }); } - emitRenderMetric(getComponentName(ui), getNow() - startedAt); + metrics.emitRenderMetric(getComponentName(ui), getNow() - startedAt); const unmount = () => { if (!mountedRoots.has(mounted)) return; @@ -272,9 +143,6 @@ export type RenderHookResult = { unmount: () => void; }; -/** - * Render a hook directly and expose the latest hook value via `result.current`. - */ export const renderHook = < Result, Props extends Record = Record, @@ -303,35 +171,16 @@ export const renderHook = < }; }; -/** - * Unmount all rendered roots and remove owned containers from the document. - */ export const cleanup = () => { for (const mounted of [...mountedRoots]) { unmountMounted(mounted); } - flushMetricBuffer(); + metrics.flushMetricBuffer(); }; -/** - * Global Testing Library `screen` bound to `document.body`. - * - * Uses a Proxy so newly-added queries from future @testing-library/dom versions - * are automatically forwarded without needing manual rebinding. - */ -const baseScreenQueries = getQueriesForElement(document.body); - -export const screen = new Proxy(baseScreenQueries, { - get(target, prop, receiver) { - const value = Reflect.get(target, prop, receiver); - return typeof value === 'function' ? value.bind(target) : value; - }, -}) as Screen; - -/** - * Testing Library `fireEvent` wrapped in React `act` for synchronous state flushing. - */ +export const screen = createScreen() as Screen; + const baseFireEventInstance = domTestingLibrary.fireEvent; const wrappedFireEvent = ((...args: Parameters) => { @@ -373,7 +222,4 @@ for (const key of Object.keys(baseFireEventInstance) as Array< export const fireEvent = wrappedFireEvent; -// Re-export all remaining @testing-library/dom utilities. -// Note: local named exports above (act, cleanup, fireEvent, render, renderHook, -// screen) shadow any same-named re-exports from this star export in ESM. export * from '@testing-library/dom'; diff --git a/src/runtime-options.ts b/src/runtime-options.ts index eb38d7b..8a13e2f 100644 --- a/src/runtime-options.ts +++ b/src/runtime-options.ts @@ -1,79 +1,11 @@ -const DEFAULT_DOM_URL = 'http://localhost:3000/'; -const DEFAULT_METRIC_BATCH_SIZE = 50; -const DEFAULT_METRIC_FLUSH_MS = 50; +import { + createRuntimeOptionArgPrefixes, + parseRuntimeOptions as parseCoreRuntimeOptions, +} from '@pokujs/dom'; -const METRICS_FLAG_PREFIX = '--poku-react-metrics='; -const MIN_METRIC_MS_PREFIX = '--poku-react-min-metric-ms='; -const DOM_URL_PREFIX = '--poku-react-dom-url='; -const METRIC_BATCH_SIZE_PREFIX = '--poku-react-metric-batch-size='; -const METRIC_FLUSH_MS_PREFIX = '--poku-react-metric-flush-ms='; +export const runtimeOptionArgPrefixes = createRuntimeOptionArgPrefixes( + 'poku-react' +); -export type RuntimeOptions = { - domUrl: string; - metricsEnabled: boolean; - minMetricMs: number; - metricBatchSize: number; - metricFlushMs: number; -}; - -const parseFlagValue = (prefix: string, argv: string[]) => { - for (const arg of argv) { - if (!arg.startsWith(prefix)) continue; - return arg.slice(prefix.length); - } - - return undefined; -}; - -const parseBooleanFlag = (value: string | undefined) => { - if (!value) return false; - return value === '1' || value.toLowerCase() === 'true'; -}; - -const parseFiniteNumber = (value: string | undefined, fallback: number) => { - if (!value || value.trim() === '') return fallback; - - const parsed = Number(value.trim()); - if (!Number.isFinite(parsed)) return fallback; - - return parsed; -}; - -const parsePositiveInteger = (value: string | undefined, fallback: number) => { - const parsed = parseFiniteNumber(value, fallback); - if (parsed <= 0) return fallback; - return Math.floor(parsed); -}; - -export const parseRuntimeOptions = ( - argv: string[] = process.argv -): RuntimeOptions => { - const metricsEnabled = parseBooleanFlag( - parseFlagValue(METRICS_FLAG_PREFIX, argv) - ); - - return { - domUrl: parseFlagValue(DOM_URL_PREFIX, argv) || DEFAULT_DOM_URL, - metricsEnabled, - minMetricMs: Math.max( - 0, - parseFiniteNumber(parseFlagValue(MIN_METRIC_MS_PREFIX, argv), 0) - ), - metricBatchSize: parsePositiveInteger( - parseFlagValue(METRIC_BATCH_SIZE_PREFIX, argv), - DEFAULT_METRIC_BATCH_SIZE - ), - metricFlushMs: parsePositiveInteger( - parseFlagValue(METRIC_FLUSH_MS_PREFIX, argv), - DEFAULT_METRIC_FLUSH_MS - ), - }; -}; - -export const runtimeOptionArgPrefixes = { - metrics: METRICS_FLAG_PREFIX, - minMetricMs: MIN_METRIC_MS_PREFIX, - domUrl: DOM_URL_PREFIX, - metricBatchSize: METRIC_BATCH_SIZE_PREFIX, - metricFlushMs: METRIC_FLUSH_MS_PREFIX, -}; +export const parseRuntimeOptions = (argv: string[] = process.argv) => + parseCoreRuntimeOptions(runtimeOptionArgPrefixes, argv);