diff --git a/packages/core/__tests__/fixtures/next/app/ssr/page.tsx b/packages/core/__tests__/fixtures/next/app/ssr/page.tsx
new file mode 100644
index 0000000..ea32a24
--- /dev/null
+++ b/packages/core/__tests__/fixtures/next/app/ssr/page.tsx
@@ -0,0 +1,7 @@
+import React from 'react';
+
+const page = () => {
+ return
page
;
+};
+
+export default page;
diff --git a/packages/core/__tests__/fixtures/next/app/test/page.tsx b/packages/core/__tests__/fixtures/next/app/test/page.tsx
new file mode 100644
index 0000000..8c07f77
--- /dev/null
+++ b/packages/core/__tests__/fixtures/next/app/test/page.tsx
@@ -0,0 +1,366 @@
+'use client';
+import { useUI, useScopedUI, cssVar } from '@react-zero-ui/core';
+import { zeroSSR, scopedZeroSSR } from '@react-zero-ui/core/experimental';
+
+export default function TestPage() {
+ // Global states
+ const [, setTheme] = useUI<'light' | 'dark'>('test-theme', 'light');
+ const [, setColor] = useUI<'red' | 'blue' | 'green'>('test-color', 'red');
+ const [, setSize] = useUI<'sm' | 'md' | 'lg'>('test-size', 'md');
+ const [, setToggle] = useUI<'on' | 'off'>('test-toggle', 'off');
+
+ // Scoped states
+ const [accordion, setAccordion] = useScopedUI<'open' | 'closed'>('test-accordion', 'closed');
+ const [tab, setTab] = useScopedUI<'tab1' | 'tab2' | 'tab3'>('test-tab', 'tab1');
+
+ // CSS variable state
+ const [, setOpacity] = useUI<'0' | '0.5' | '1'>('test-opacity', '1', cssVar);
+ const [, setSpacing] = useUI<'4px' | '8px' | '16px'>('test-spacing', '8px', cssVar);
+
+ return (
+
+ {/* Global State Tests */}
+
+ Global State Tests
+
+ {/* Theme Test */}
+
+ setTheme((prev) => (prev === 'light' ? 'dark' : 'light'))}
+ className="border px-4 py-2">
+ Toggle Theme
+
+
+ light
+ dark
+
+
+
+ {/* Color Test */}
+
+ setColor('red')}
+ className="border px-4 py-2 mr-2">
+ Red
+
+ setColor('blue')}
+ className="border px-4 py-2 mr-2">
+ Blue
+
+ setColor('green')}
+ className="border px-4 py-2">
+ Green
+
+
+ red
+ blue
+ green
+
+
+
+ {/* Toggle Test */}
+
+ setToggle((prev) => (prev === 'on' ? 'off' : 'on'))}
+ className="border px-4 py-2">
+ Toggle
+
+
+ on
+ off
+
+
+
+
+ {/* Scoped State Tests */}
+
+ Scoped State Tests
+
+ {/* Accordion Test */}
+
+
setAccordion((prev) => (prev === 'open' ? 'closed' : 'open'))}
+ className="w-full p-4 text-left border-b">
+ Accordion Header
+
+
+ Accordion Content
+
+
+ open
+ closed
+
+
+
+ {/* Tab Test */}
+
+
+ setTab('tab1')}
+ className="px-4 py-2 test-tab-tab1:bg-blue-500 test-tab-tab1:text-white">
+ Tab 1
+
+ setTab('tab2')}
+ className="px-4 py-2 test-tab-tab2:bg-blue-500 test-tab-tab2:text-white">
+ Tab 2
+
+ setTab('tab3')}
+ className="px-4 py-2 test-tab-tab3:bg-blue-500 test-tab-tab3:text-white">
+ Tab 3
+
+
+
+
+ Tab 1 Content
+
+
+ Tab 2 Content
+
+
+ Tab 3 Content
+
+
+
+ tab1
+ tab2
+ tab3
+
+
+
+
+ {/* SSR Tests */}
+
+ SSR Tests
+
+ {/* Global SSR */}
+
+
+ SSR Toggle (Global)
+
+
+ Light
+
+
+ Dark
+
+
+
+ {/* Scoped SSR */}
+
+
+ SSR Toggle (Scoped)
+
+
+ Menu is open
+
+
+
+
+ {/* CSS Variable Tests */}
+
+ CSS Variable Tests
+
+ {/* Opacity Test */}
+
+
+ Opacity Target
+
+
setOpacity('0')}
+ className="border px-4 py-2 mr-2">
+ 0%
+
+
setOpacity('0.5')}
+ className="border px-4 py-2 mr-2">
+ 50%
+
+
setOpacity('1')}
+ className="border px-4 py-2">
+ 100%
+
+
+ 0
+ 0.5
+ 1
+
+
+
+ {/* Spacing Test */}
+
+
+ Spacing Target
+
+
+ setSpacing('4px')}
+ className="border px-4 py-2 mr-2">
+ 4px
+
+ setSpacing('8px')}
+ className="border px-4 py-2 mr-2">
+ 8px
+
+ setSpacing('16px')}
+ className="border px-4 py-2">
+ 16px
+
+
+ 4px
+ 8px
+ 16px
+
+
+
+
+
+ {/* State Update Patterns */}
+
+ State Update Patterns
+
+ {/* Direct Set */}
+
+ setSize('sm')}
+ className="border px-4 py-2 mr-2 test-size-sm:bg-yellow-500">
+ Small
+
+ setSize('md')}
+ className="border px-4 py-2 mr-2 test-size-md:bg-yellow-500">
+ Medium
+
+ setSize('lg')}
+ className="border px-4 py-2 test-size-lg:bg-yellow-500">
+ Large
+
+
+ sm
+ md
+ lg
+
+
+
+ {/* Function Update */}
+
+
+ setTheme((current) => {
+ console.log('Current theme:', current);
+ return current === 'light' ? 'dark' : 'light';
+ })
+ }
+ className="border px-4 py-2">
+ Toggle Theme (Function)
+
+
+
+
+ );
+}
diff --git a/packages/core/__tests__/fixtures/next/tsconfig.json b/packages/core/__tests__/fixtures/next/tsconfig.json
index d73e90d..a976c3c 100644
--- a/packages/core/__tests__/fixtures/next/tsconfig.json
+++ b/packages/core/__tests__/fixtures/next/tsconfig.json
@@ -12,7 +12,7 @@
"incremental": true,
"module": "ESNext",
"esModuleInterop": true,
- "moduleResolution": "node",
+ "moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
diff --git a/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.d.ts b/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.d.ts
index 6da0d21..24be437 100644
--- a/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.d.ts
+++ b/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.d.ts
@@ -14,5 +14,5 @@ export declare const bodyAttributes: {
};
export declare const variantKeyMap: {
- [key: string]: true;
+ [key: string]: true | string[] | '*';
};
diff --git a/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.js b/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.js
index 39b4b9c..68a441e 100644
--- a/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.js
+++ b/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.js
@@ -9,7 +9,7 @@ export const bodyAttributes = {
"data-toggle-function": "white",
"data-use-effect-theme": "light"
};
-export const variantKeyMap = {
+export const variantKeyMap = {
"data-child": true,
"data-faq": true,
"data-mobile": true,
diff --git a/packages/core/__tests__/fixtures/vite/tsconfig.app.json b/packages/core/__tests__/fixtures/vite/tsconfig.app.json
index 62e17a1..32ab113 100644
--- a/packages/core/__tests__/fixtures/vite/tsconfig.app.json
+++ b/packages/core/__tests__/fixtures/vite/tsconfig.app.json
@@ -6,7 +6,6 @@
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
- /* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
diff --git a/packages/core/__tests__/helpers/resetProjectState.js b/packages/core/__tests__/helpers/resetProjectState.js
index aa1f6fb..a38d434 100644
--- a/packages/core/__tests__/helpers/resetProjectState.js
+++ b/packages/core/__tests__/helpers/resetProjectState.js
@@ -41,7 +41,10 @@ export async function resetZeroUiState(projectDir, isNext = false) {
return;
}
-const defaultLayoutContent = `import './globals.css';
+const defaultLayoutContent = `
+import './globals.css';
+
+import ZeroUiRuntime from './zero-runtime';
export default function RootLayout({ children }) {
return (
@@ -49,6 +52,7 @@ export default function RootLayout({ children }) {
+
{children}
@@ -85,7 +89,7 @@ const defaultTsconfigContent = `{
"incremental": true,
"module": "ESNext",
"esModuleInterop": true,
- "moduleResolution": "node",
+ "moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
diff --git a/packages/core/__tests__/unit/cli.test.cjs b/packages/core/__tests__/unit/cli.test.cjs
index f3d963e..c12bb1e 100644
--- a/packages/core/__tests__/unit/cli.test.cjs
+++ b/packages/core/__tests__/unit/cli.test.cjs
@@ -88,32 +88,6 @@ function runCLIScript(targetDir, timeout = 30000) {
});
}
-// test('CLI script creates package.json if it does not exist', async () => {
-// const testDir = createTestDir();
-
-// try {
-// // Ensure no package.json exists
-// const packageJsonPath = path.join(testDir, 'package.json');
-// assert(!fs.existsSync(packageJsonPath), 'package.json should not exist initially');
-
-// // Run CLI (this will timeout on npm install, but that's ok for this test)
-// const result = await runCLIScript(testDir, 5000).catch(err => {
-// // We expect this to timeout/fail during npm install
-// return { timedOut: true };
-// });
-
-// // Check that package.json was created
-// assert(fs.existsSync(packageJsonPath), 'package.json should be created');
-
-// const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
-// assert(packageJson.name, 'package.json should have a name field');
-// assert(packageJson.version, 'package.json should have a version field');
-
-// console.log('โ
package.json created successfully');
-
-// } finally {
-// cleanupTestDir(testDir);
-// }
// });
test('CLI script uses existing package.json if it exists', async () => {
diff --git a/packages/core/__tests__/unit/index.test.cjs b/packages/core/__tests__/unit/index.test.cjs
index 6b66404..e56720a 100644
--- a/packages/core/__tests__/unit/index.test.cjs
+++ b/packages/core/__tests__/unit/index.test.cjs
@@ -14,7 +14,7 @@ function getAttrFile() {
}
// Helper to create temp directory and run test
-async function runTest(files, callback) {
+async function runTest(files, callback, cache = true) {
const testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'zero-ui-test'));
const originalCwd = process.cwd();
@@ -22,13 +22,13 @@ async function runTest(files, callback) {
process.chdir(testDir);
// Clear the global file cache to prevent stale entries from previous tests
- try {
- const astParsing = require('../../dist/postcss/ast-parsing.js');
- if (astParsing.clearCache) {
+ if (cache) {
+ try {
+ const astParsing = require('../../dist/postcss/ast-parsing.js');
astParsing.clearCache();
+ } catch {
+ // Cache clearing is best-effort
}
- } catch {
- // Cache clearing is best-effort
}
// Create test files
@@ -50,6 +50,10 @@ async function runTest(files, callback) {
// Run assertions
await callback(result);
+ } catch (e) {
+ console.log('error in runTest: ', e.message);
+ console.log(e.stack);
+ throw e;
} finally {
process.chdir(originalCwd);
@@ -271,14 +275,14 @@ test('handles large projects efficiently - 500 files', async function () {
const endTime = Date.now();
const duration = endTime - startTime;
- console.log(`\nโก Performance: Processed 500 files in ${duration}ms`);
+ console.log(`\nโก Performance: Processed 1000 files in ${duration}ms`);
const attributes = fs.readFileSync(getAttrFile(), 'utf-8');
// Should process all files
assert(attributes.includes('value49'), 'Should process all files');
// Should complete in reasonable time
- assert(duration < 500, 'Should process 500 files in under 500ms');
+ assert(duration < 1000, 'Should process 1000 files in under 1000ms');
});
});
@@ -472,7 +476,7 @@ test('patchTsConfig - config file patching', async (t) => {
// TypeScript configuration
"compilerOptions": {
"target": "ES2015",
- "module": "ESNext", // Comment here
+ "module": "bundler", // Comment here
},
"include": ["src/**/*"], // Another comment
}`;
@@ -543,7 +547,6 @@ test('PostCSS config - creates new .js config for Next.js project', async () =>
const configContent = fs.readFileSync('postcss.config.js', 'utf-8');
console.log('\n๐ Generated PostCSS config:');
- console.log(configContent);
assert(configContent.includes('@react-zero-ui/core/postcss'), 'Should include Zero-UI plugin');
assert(configContent.includes('@tailwindcss/postcss'), 'Should include Tailwind plugin');
@@ -578,8 +581,6 @@ test('PostCSS config - updates existing .js config', async () => {
// Verify config was updated
const updatedContent = fs.readFileSync('postcss.config.js', 'utf-8');
- console.log('\n๐ Updated PostCSS config:');
- console.log(updatedContent);
assert(updatedContent.includes('@react-zero-ui/core/postcss'), 'Should add Zero-UI plugin');
assert(updatedContent.includes('autoprefixer'), 'Should preserve existing plugins');
@@ -616,8 +617,6 @@ export default config;`;
// Verify config was updated
const updatedContent = fs.readFileSync('postcss.config.mjs', 'utf-8');
- console.log('\n๐ Updated .mjs PostCSS config:');
- console.log(updatedContent);
assert(updatedContent.includes('@react-zero-ui/core/postcss'), 'Should add Zero-UI plugin');
assert(updatedContent.includes('export default'), 'Should preserve ES module format');
@@ -661,8 +660,6 @@ test('PostCSS config - handles complex existing configs w/comments', async () =>
// Verify Zero-UI was added at the beginning
const updatedContent = fs.readFileSync('postcss.config.js', 'utf-8');
- console.log('\n๐ Complex config update:');
- console.log(updatedContent);
assert(updatedContent.includes('@react-zero-ui/core/postcss'), 'Should add Zero-UI plugin');
assert(updatedContent.includes('postcss-flexbugs-fixes'), 'Should preserve existing plugins');
@@ -1150,145 +1147,3 @@ test('generated variants for initial value without setterFn', async () => {
}
);
});
-/*
-The following tests are for advanced edge cases
---------------------------------------------------------------------------------------------
-----------------------------------------------
-----------------------------------------------
-----------------------------------------------
-----------------------------------------------
-----------------------------------------------
---------------------------------------------------------------------------------------------
-----------------------------------------------
-----------------------------------------------
-----------------------------------------------
-----------------------------------------------
-----------------------------------------------
-----------------------------------------------
-*/
-
-test.skip('handles all common setter patterns - full coverage sanity check - COMPLEX', async () => {
- await runTest(
- {
- 'app/component.tsx': `
- import { useUI } from '@react-zero-ui/core';
-
- const DARK = 'dark';
- const LIGHT = 'light';
-
- function Component() {
- const [theme, setTheme] = useUI<'light' | 'dark' | 'contrast' | 'neon' | 'retro'>('theme', 'light');
- const [size, setSize] = useUI<'sm' | 'lg'>('size', 'sm');
-
- setTheme('dark'); // direct
- setTheme(DARK); // identifier
- setTheme(() => 'light'); // arrow fn
- setTheme(prev => prev === 'light' ? 'dark' : 'light'); // updater
- setTheme(prev => { if (a) return 'neon'; return 'retro'; }); // block
- setTheme(userPref || 'contrast'); // logical fallback
- setSize(SIZES.SMALL); // object constant
-
- return (
- <>
-
setTheme('contrast')}>Contrast
-
setTheme(e.target.value)}>
- Neon
- Retro
-
- >
- );
- }
-
- const SIZES = {
- SMALL: 'sm',
- LARGE: 'lg'
- };
- `,
- },
- (result) => {
- console.log('\n๐ Full coverage test:');
-
- // โ
things that MUST be included
- assert(result.css.includes('@custom-variant theme-dark'));
- assert(result.css.includes('@custom-variant theme-light'));
- assert(result.css.includes('@custom-variant theme-contrast'));
- assert(result.css.includes('@custom-variant theme-neon'));
- assert(result.css.includes('@custom-variant theme-retro'));
- assert(result.css.includes('@custom-variant size-sm'));
- assert(result.css.includes('@custom-variant size-lg'));
-
- // โ known misses: test exposes what won't work without resolution
- // assert(result.css.includes('@custom-variant theme-dynamic-from-e-target'));
- }
- );
-});
-
-test('performance with large files and many variants', async () => {
- // Generate a large file with many useUI calls
- const generateLargeFile = () => {
- let content = `import { useUI } from '@react-zero-ui/core';\n\n`;
-
- // Create many components with different state keys
- for (let i = 0; i < 50; i++) {
- const toggleInitial = i % 2 === 0 ? "'true'" : "'false'";
- content += `
- function Component${i}() {
- const [state${i}, setState${i}] = useUI('state-${i}', 'initial-${i}');
- const [toggle${i}, setToggle${i}] = useUI('toggle-${i}', ${toggleInitial});
-
-
-
- return
Component ${i}
;
- }
- `;
- }
-
- return content;
- };
-
- const startTime = Date.now();
-
- await runTest({ 'app/large-file.jsx': generateLargeFile() }, (result) => {
- const endTime = Date.now();
- const duration = endTime - startTime;
-
- console.log(`\nโฑ๏ธ Large file processing took: ${duration}ms`);
-
- // Should handle large files reasonably quickly (< 5 seconds)
- assert(duration < 5000, `Processing took too long: ${duration}ms`);
-
- // Should still extract all variants correctly
- assert(result.css.includes('@custom-variant state-0-initial-0'));
- assert(result.css.includes('@custom-variant state-49-initial-49'));
- assert(result.css.includes('@custom-variant toggle-0-true'));
- assert(result.css.includes('@custom-variant toggle-0-false'));
- });
-});
-
-test('caching works correctly', async () => {
- const testFiles = {
- 'app/cached.jsx': `
- import { useUI } from '@react-zero-ui/core';
-
- function Component() {
- const [theme, setTheme] = useUI('theme', 'light');
- return
Test
;
- }
- `,
- };
-
- // First run
- const start1 = Date.now();
- await runTest(testFiles, () => {});
- const duration1 = Date.now() - start1;
-
- // Second run with same files (should be faster due to caching)
- const start2 = Date.now();
- await runTest(testFiles, () => {});
- const duration2 = Date.now() - start2;
-
- console.log(`\n๐ First run: ${duration1}ms, Second run: ${duration2}ms`);
-
- // Note: This test might be flaky in CI, but useful for development
- // Second run should generally be faster, but timing can vary
-});
diff --git a/packages/core/dev/next/.zero-ui/attributes.d.ts b/packages/core/dev/next/.zero-ui/attributes.d.ts
new file mode 100644
index 0000000..87cc32b
--- /dev/null
+++ b/packages/core/dev/next/.zero-ui/attributes.d.ts
@@ -0,0 +1,32 @@
+/* AUTO-GENERATED - DO NOT EDIT */
+export declare const bodyAttributes: {
+ "data-blur": string;
+ "data-blur-global": string;
+ "data-child": "closed" | "open";
+ "data-dialog": "closed" | "open";
+ "data-faq": "closed" | "open";
+ "data-mobile": "false" | "true";
+ "data-number": "1" | "2";
+ "data-scope": "off" | "on";
+ "data-test-accordion": "closed" | "open";
+ "data-test-color": "blue" | "green" | "red";
+ "data-test-opacity": "0" | "05" | "1";
+ "data-test-size": "lg" | "md" | "sm";
+ "data-test-spacing": "16px" | "4px" | "8px";
+ "data-test-ssr-menu": "closed" | "open";
+ "data-test-ssr-theme": "dark" | "light";
+ "data-test-tab": "tab1" | "tab2" | "tab3";
+ "data-test-theme": "dark" | "light";
+ "data-test-toggle": "off" | "on";
+ "data-theme": "dark" | "light";
+ "data-theme-2": "dark" | "light";
+ "data-theme-ssr": "dark" | "light";
+ "data-theme-three": "dark" | "light";
+ "data-toggle-boolean": "false" | "true";
+ "data-toggle-function": "black" | "blue" | "green" | "red" | "white";
+ "data-use-effect-theme": "dark" | "light";
+};
+
+export declare const variantKeyMap: {
+ [key: string]: true | string[] | '*';
+};
diff --git a/packages/core/dev/next/.zero-ui/attributes.js b/packages/core/dev/next/.zero-ui/attributes.js
new file mode 100644
index 0000000..9f54f53
--- /dev/null
+++ b/packages/core/dev/next/.zero-ui/attributes.js
@@ -0,0 +1,47 @@
+/* AUTO-GENERATED - DO NOT EDIT */
+export const bodyAttributes = {
+ "data-blur-global": "0px",
+ "data-child": "closed",
+ "data-number": "1",
+ "data-test-color": "red",
+ "data-test-opacity": "1",
+ "data-test-size": "md",
+ "data-test-spacing": "8px",
+ "data-test-ssr-theme": "light",
+ "data-test-theme": "light",
+ "data-test-toggle": "off",
+ "data-theme": "light",
+ "data-theme-2": "light",
+ "data-theme-ssr": "light",
+ "data-theme-three": "light",
+ "data-toggle-boolean": "true",
+ "data-toggle-function": "white",
+ "data-use-effect-theme": "light"
+};
+export const variantKeyMap = {
+ "data-blur": true,
+ "data-blur-global": true,
+ "data-child": true,
+ "data-dialog": true,
+ "data-faq": true,
+ "data-mobile": true,
+ "data-number": true,
+ "data-scope": true,
+ "data-test-accordion": true,
+ "data-test-color": true,
+ "data-test-opacity": true,
+ "data-test-size": true,
+ "data-test-spacing": true,
+ "data-test-ssr-menu": true,
+ "data-test-ssr-theme": true,
+ "data-test-tab": true,
+ "data-test-theme": true,
+ "data-test-toggle": true,
+ "data-theme": true,
+ "data-theme-2": true,
+ "data-theme-ssr": true,
+ "data-theme-three": true,
+ "data-toggle-boolean": true,
+ "data-toggle-function": true,
+ "data-use-effect-theme": true
+};
diff --git a/packages/core/dev/next/app/ChildComponent.tsx b/packages/core/dev/next/app/ChildComponent.tsx
new file mode 100644
index 0000000..c3f08a8
--- /dev/null
+++ b/packages/core/dev/next/app/ChildComponent.tsx
@@ -0,0 +1,20 @@
+import { type UISetterFn } from '@react-zero-ui/core';
+
+export function ChildComponent({ setIsOpen }: { setIsOpen: UISetterFn }) {
+ return (
+
+
setIsOpen((prev) => (prev === 'closed' ? 'open' : 'closed'))}
+ className="border-2 border-red-500"
+ data-testid="child-toggle">
+ Toggle Child
+
+
+ Child: Open Closed
+
+
+ );
+}
diff --git a/packages/core/dev/next/app/ChildWithoutSetter.tsx b/packages/core/dev/next/app/ChildWithoutSetter.tsx
new file mode 100644
index 0000000..8c90e96
--- /dev/null
+++ b/packages/core/dev/next/app/ChildWithoutSetter.tsx
@@ -0,0 +1,14 @@
+export function ChildWithoutSetter() {
+ return (
+
+
+
+ Child Scope: False
+ True
+
+
+
+ );
+}
diff --git a/packages/core/dev/next/app/CssVarDemo.tsx b/packages/core/dev/next/app/CssVarDemo.tsx
new file mode 100644
index 0000000..edd816b
--- /dev/null
+++ b/packages/core/dev/next/app/CssVarDemo.tsx
@@ -0,0 +1,33 @@
+'use client';
+
+import { useScopedUI, cssVar } from '@react-zero-ui/core';
+
+/**
+ * CssVarDemo - minimal fixture for Playwright
+ *
+ * โข Flips a scoped CSS variable `--blur` between "0px" โ "4px"
+ * โข Uses the updater-function pattern (`prev โ next`)
+ * โข Exposes test-ids so your spec can assert both style + text
+ */
+export default function CssVarDemo({ index = 0 }) {
+ // ๐ pass `cssVar` flag to switch makeSetter into CSS-var mode
+ const [blur, setBlur] = useScopedUI<'0px' | '4px'>('blur', '0px', cssVar);
+ // global test
+ return (
+
+
setBlur((prev) => (prev === '0px' ? '4px' : '0px'))}
+ className="px-3 py-1 rounded bg-black text-white">
+ toggle blur
+
+
+ {/* expose current value for assertions */}
+
{blur}
+
+ );
+}
diff --git a/packages/core/dev/next/app/FAQ.tsx b/packages/core/dev/next/app/FAQ.tsx
new file mode 100644
index 0000000..1c69f77
--- /dev/null
+++ b/packages/core/dev/next/app/FAQ.tsx
@@ -0,0 +1,26 @@
+import { useScopedUI } from '@react-zero-ui/core';
+
+function FAQ({ question, answer, index }) {
+ const [open, setOpen] = useScopedUI<'open' | 'closed'>('faq', 'closed'); // Same key everywhere!
+
+ return (
+
+
setOpen((prev) => (prev === 'open' ? 'closed' : 'open'))}>
+ {question} +
+
+
+ {answer}
+
+
+ );
+}
+
+export default FAQ;
diff --git a/packages/core/dev/next/app/LintFailures.tsx b/packages/core/dev/next/app/LintFailures.tsx
new file mode 100644
index 0000000..b6a20b2
--- /dev/null
+++ b/packages/core/dev/next/app/LintFailures.tsx
@@ -0,0 +1,52 @@
+'use client';
+import { useScopedUI } from '@react-zero-ui/core';
+
+/**
+ * This component is intentionally WRONG.
+ * โ Missing data-attr on
+ * โ Missing ref attachment on
+ *
+ * The Zero-UI ESLint rule should flag both.
+ */
+export default function LintFailures() {
+ // #1 Setter attached but no data-scope attr โก๏ธ missingAttr error
+ const [scope, setScope] = useScopedUI<'off' | 'on'>('scope', 'off');
+
+ // #2 No ref at all โก๏ธ missingRef error
+ const [, setDialog] = useScopedUI<'open' | 'closed'>('dialog', 'closed');
+
+ return (
+
+ {/* โ lint error expected here */}
+
+ setScope((prev) => (prev === 'on' ? 'off' : 'on'))}>
+ Toggle scope
+
+
+
+ {/* โ second lint error (missing .ref) */}
+
+ This dialog was never linked via ref
+
+
+ );
+}
+
+/* ------------------------------------------------------------------ *
+ | Correct version (for reference) |
+ * ------------------------------------------------------------------ *
+ const [scope, setScope] = useScopedUI<'off' | 'on'>('scope', 'off');
+
+
+ const [, setDialog] = useScopedUI<'open'|'closed'>('dialog','closed');
+
+*/
diff --git a/packages/core/dev/next/app/UseEffectComponent.tsx b/packages/core/dev/next/app/UseEffectComponent.tsx
new file mode 100644
index 0000000..2df0e9a
--- /dev/null
+++ b/packages/core/dev/next/app/UseEffectComponent.tsx
@@ -0,0 +1,29 @@
+'use client';
+import { useEffect, useState } from 'react';
+import { useUI } from '@react-zero-ui/core';
+
+// Component that automatically cycles through themes using useEffect
+export default function UseEffectComponent() {
+ const [, setAutoTheme] = useUI<'light' | 'dark'>('use-effect-theme', 'light');
+ const [state, setState] = useState(false);
+
+ useEffect(() => {
+ setAutoTheme(state ? 'dark' : 'light');
+ }, [state]);
+
+ return (
+
+
setState((prev) => !prev)}
+ data-testid="use-effect-theme">
+ Toggle Theme (useEffect on click)
+
+
+ Theme: Dark Light
+
+
+ );
+}
diff --git a/packages/core/dev/next/app/globals.css b/packages/core/dev/next/app/globals.css
new file mode 100644
index 0000000..d4b5078
--- /dev/null
+++ b/packages/core/dev/next/app/globals.css
@@ -0,0 +1 @@
+@import 'tailwindcss';
diff --git a/packages/core/dev/next/app/init-zero-ui.tsx b/packages/core/dev/next/app/init-zero-ui.tsx
new file mode 100644
index 0000000..3cc5df4
--- /dev/null
+++ b/packages/core/dev/next/app/init-zero-ui.tsx
@@ -0,0 +1,8 @@
+'use client';
+
+import { activateZeroUiRuntime } from '@react-zero-ui/core/experimental/runtime';
+import { variantKeyMap } from '@zero-ui/attributes';
+
+activateZeroUiRuntime(variantKeyMap as { [key: string]: true | string[] | '*' });
+
+export const InitZeroUI = () => null;
diff --git a/packages/core/dev/next/app/layout.tsx b/packages/core/dev/next/app/layout.tsx
new file mode 100644
index 0000000..54b303c
--- /dev/null
+++ b/packages/core/dev/next/app/layout.tsx
@@ -0,0 +1,17 @@
+import { bodyAttributes } from '@zero-ui/attributes';
+import './globals.css';
+import ZeroUiRuntime from './zero-runtime';
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+ {children}
+
+
+ );
+}
diff --git a/packages/core/dev/next/app/page.tsx b/packages/core/dev/next/app/page.tsx
new file mode 100644
index 0000000..3c05d20
--- /dev/null
+++ b/packages/core/dev/next/app/page.tsx
@@ -0,0 +1,279 @@
+'use client';
+import { cssVar, useScopedUI, useUI } from '@react-zero-ui/core';
+import UseEffectComponent from './UseEffectComponent';
+import FAQ from './FAQ';
+import { ChildComponent } from './ChildComponent';
+import { ChildWithoutSetter } from './ChildWithoutSetter';
+import CssVarDemo from './CssVarDemo';
+import { zeroSSR } from '@react-zero-ui/core/experimental';
+
+export default function Page() {
+ const [scope, setScope] = useScopedUI<'off' | 'on'>('scope', 'off');
+
+ const [, setTheme] = useUI<'light' | 'dark'>('theme', 'light');
+ const [, setTheme2] = useUI<'light' | 'dark'>('theme-2', 'light');
+ const [, setThemeThree] = useUI<'light' | 'dark'>('theme-three', 'light');
+ const [, setToggle] = useUI<'true' | 'false'>('toggle-boolean', 'true');
+ const [, setNumber] = useUI<'1' | '2'>('number', '1');
+ const [open, setOpen] = useScopedUI<'open' | 'closed'>('faq', 'closed');
+ const [mobile, setMobile] = useScopedUI<'true' | 'false'>('mobile', 'false');
+ const [, setChildOpen] = useUI<'open' | 'closed'>('child', 'closed');
+
+ const [, setToggleFunction] = useUI<'white' | 'black'>('toggle-function', 'white');
+ const [, setGlobal] = useUI<'0px' | '4px'>('blur-global', '0px', cssVar);
+
+ const toggleFunction = () => {
+ setToggleFunction((prev) => (prev === 'white' ? 'black' : 'white'));
+ };
+
+ return (
+
+
Global State
+
+
+ {/* Auto Theme Component */}
+
+
+
+
+
+
+ Toggle SSR SAFE THEME
+
+
+ Theme:
+
+ Dark
+
+
+ Light
+
+
+
+
+
+
+
setTheme((prev) => (prev === 'light' ? 'dark' : 'light'))}
+ className="border-2 border-red-500"
+ data-testid="theme-toggle">
+ Toggle Theme (default)
+
+
+ Theme: Dark Light
+
+
+
+
+
+
+
setTheme2((prev) => (prev === 'light' ? 'dark' : 'light'))}
+ className="border-2 border-red-500"
+ data-testid="theme-toggle-secondary">
+ Toggle Theme Secondary (w/ number in string)
+
+
+ Theme: Dark Light
+
+
+
+
+
+
+
setThemeThree((prev) => (prev === 'light' ? 'dark' : 'light'))}
+ className="border-2 border-red-500"
+ data-testid="theme-toggle-3">
+ Toggle Theme 3 (w/ camelCase)
+
+
+ Theme: Dark Light
+
+
+
+
+
+
+
setToggle((prev) => (prev === 'true' ? 'false' : 'true'))}
+ className="border-2 border-red-500"
+ data-testid="toggle-boolean">
+ Toggle Boolean ({`w/ boolean + prev => !prev`})
+
+
+ Boolean: True False
+
+
+
+
+
+
setNumber((prev) => (prev === '1' ? '2' : '1'))}
+ className="border-2 border-red-500"
+ data-testid="toggle-number">
+ Toggle Number ({`w/ number + prev => prev === 1 ? 2 : 1`})
+
+
+ Number: 1 2
+
+
+
+
+
+
+ Toggle Function
+
+
+ Function: White Black
+
+
+
+
+
+
+
+
Scoped Style Tests
+
+
+
+
+
+
setScope((prev) => (prev === 'on' ? 'off' : 'on'))}
+ className="border-2 border-red-500"
+ data-testid="scope-toggle">
+ Toggle Scope
+
+
+
+ Scope: False
+ True
+
+
+
+
+
+
+
{
+ if (typeof window !== 'undefined') {
+ if (window.innerWidth > 768) {
+ // allow the user to toggle the mobile state
+ setMobile((prev) => (prev === 'true' ? 'false' : 'true'));
+ }
+ if (window.innerWidth < 768) {
+ // force the mobile state to false on click
+ setMobile('true');
+ }
+ }
+ }}
+ className="border-2 border-red-500"
+ data-testid="mobile-toggle">
+ Toggle Mobile
+
+
+ Mobile: False
+ True
+
+
+
+
+
+
+
+
setOpen((prev) => (prev === 'open' ? 'closed' : 'open'))}>
+ question 0 +
+
+
answer
+
+
+
+
+
+
+ {Array.from({ length: 2 }).map((_, index) => (
+
+ ))}
+
+
setGlobal((prev) => (prev === '0px' ? '4px' : '0px'))}>
+ Global blur toggle
+
+
+
+
+ );
+}
diff --git a/packages/core/dev/next/app/ssr/page.tsx b/packages/core/dev/next/app/ssr/page.tsx
new file mode 100644
index 0000000..ea32a24
--- /dev/null
+++ b/packages/core/dev/next/app/ssr/page.tsx
@@ -0,0 +1,7 @@
+import React from 'react';
+
+const page = () => {
+ return page
;
+};
+
+export default page;
diff --git a/packages/core/dev/next/app/test/page.tsx b/packages/core/dev/next/app/test/page.tsx
new file mode 100644
index 0000000..22f3dc3
--- /dev/null
+++ b/packages/core/dev/next/app/test/page.tsx
@@ -0,0 +1,371 @@
+'use client';
+import { useUI, useScopedUI, cssVar } from '@react-zero-ui/core';
+import { zeroSSR, scopedZeroSSR } from '@react-zero-ui/core/experimental';
+
+export default function TestPage() {
+ const x = 'test-toggle';
+ const y = { 'test-toggle': 'off' };
+ const z = 'off';
+ const bool = false;
+ // Global states
+ const [, setTheme] = useUI<'light' | 'dark'>('test-theme', 'light');
+ const [, setColor] = useUI<'red' | 'blue' | 'green'>('test-color', 'red');
+ const [, setSize] = useUI<'sm' | 'md' | 'lg'>('test-size', 'md');
+
+ const [, setToggle] = useUI<'on' | 'off'>(x, bool ? y[x] : (z as 'on' | 'off'));
+
+ // Scoped states
+ const [accordion, setAccordion] = useScopedUI<'open' | 'closed'>('test-accordion', 'closed');
+ const [tab, setTab] = useScopedUI<'tab1' | 'tab2' | 'tab3'>('test-tab', 'tab1');
+
+ // CSS variable state
+ const [, setOpacity] = useUI<'0' | '0.5' | '1'>('test-opacity', '1', cssVar);
+ const [, setSpacing] = useUI<'4px' | '8px' | '16px'>('test-spacing', '8px', cssVar);
+
+ return (
+
+ {/* Global State Tests */}
+
+ Global State Tests
+
+ {/* Theme Test */}
+
+ setTheme((prev) => (prev === 'light' ? 'dark' : 'light'))}
+ className="border px-4 py-2">
+ Toggle Theme
+
+
+ light
+ dark
+
+
+
+ {/* Color Test */}
+
+ setColor('red')}
+ className="border px-4 py-2 mr-2">
+ Red
+
+ setColor('blue')}
+ className="border px-4 py-2 mr-2">
+ Blue
+
+ setColor('green')}
+ className="border px-4 py-2">
+ Green
+
+
+ red
+ blue
+ green
+
+
+
+ {/* Toggle Test */}
+
+ setToggle((prev) => (prev === 'on' ? 'off' : 'on'))}
+ className="border px-4 py-2">
+ Toggle
+
+
+ on
+ off
+
+
+
+
+ {/* Scoped State Tests */}
+
+ Scoped State Tests
+
+ {/* Accordion Test */}
+
+
setAccordion((prev) => (prev === 'open' ? 'closed' : 'open'))}
+ className="w-full p-4 text-left border-b">
+ Accordion Header
+
+
+ Accordion Content
+
+
+ open
+ closed
+
+
+
+ {/* Tab Test */}
+
+
+ setTab('tab1')}
+ className="px-4 py-2 test-tab-tab1:bg-blue-500 test-tab-tab1:text-white">
+ Tab 1
+
+ setTab('tab2')}
+ className="px-4 py-2 test-tab-tab2:bg-blue-500 test-tab-tab2:text-white">
+ Tab 2
+
+ setTab('tab3')}
+ className="px-4 py-2 test-tab-tab3:bg-blue-500 test-tab-tab3:text-white">
+ Tab 3
+
+
+
+
+ Tab 1 Content
+
+
+ Tab 2 Content
+
+
+ Tab 3 Content
+
+
+
+ tab1
+ tab2
+ tab3
+
+
+
+
+ {/* SSR Tests */}
+
+ SSR Tests
+
+ {/* Global SSR */}
+
+
+ SSR Toggle (Global)
+
+
+ Light
+
+
+ Dark
+
+
+
+ {/* Scoped SSR */}
+
+
+ SSR Toggle (Scoped)
+
+
+ Menu is open
+
+
+
+
+ {/* CSS Variable Tests */}
+
+ CSS Variable Tests
+
+ {/* Opacity Test */}
+
+
+ Opacity Target
+
+
setOpacity('0')}
+ className="border px-4 py-2 mr-2">
+ 0%
+
+
setOpacity('0.5')}
+ className="border px-4 py-2 mr-2">
+ 50%
+
+
setOpacity('1')}
+ className="border px-4 py-2">
+ 100%
+
+
+ 0
+ 0.5
+ 1
+
+
+
+ {/* Spacing Test */}
+
+
+ Spacing Target
+
+
+ setSpacing('4px')}
+ className="border px-4 py-2 mr-2">
+ 4px
+
+ setSpacing('8px')}
+ className="border px-4 py-2 mr-2">
+ 8px
+
+ setSpacing('16px')}
+ className="border px-4 py-2">
+ 16px
+
+
+ 4px
+ 8px
+ 16px
+
+
+
+
+
+ {/* State Update Patterns */}
+
+ State Update Patterns
+
+ {/* Direct Set */}
+
+ setSize('sm')}
+ className="border px-4 py-2 mr-2 test-size-sm:bg-yellow-500">
+ Small
+
+ setSize('md')}
+ className="border px-4 py-2 mr-2 test-size-md:bg-yellow-500">
+ Medium
+
+ setSize('lg')}
+ className="border px-4 py-2 test-size-lg:bg-yellow-500">
+ Large
+
+
+ sm
+ md
+ lg
+
+
+
+ {/* Function Update */}
+
+
+ setTheme((current) => {
+ console.log('Current theme:', current);
+ return current === 'light' ? 'dark' : 'light';
+ })
+ }
+ className="border px-4 py-2">
+ Toggle Theme (Function)
+
+
+
+
+ );
+}
diff --git a/packages/core/dev/next/app/variables.ts b/packages/core/dev/next/app/variables.ts
new file mode 100644
index 0000000..e4b0dd7
--- /dev/null
+++ b/packages/core/dev/next/app/variables.ts
@@ -0,0 +1,4 @@
+export const THEME_BLUE = 'blue';
+export const THEME_RED = 'red';
+
+export const THEMES: Record<'blueee' | 'dark', string> = { blueee: 'blue', dark: 'dark' };
diff --git a/packages/core/dev/next/app/zero-runtime.tsx b/packages/core/dev/next/app/zero-runtime.tsx
new file mode 100644
index 0000000..31cbb83
--- /dev/null
+++ b/packages/core/dev/next/app/zero-runtime.tsx
@@ -0,0 +1,13 @@
+'use client';
+
+/* โ import the generated defaults */
+import { variantKeyMap } from '../.zero-ui/attributes';
+
+/* โก activate the runtime shipped in the package */
+import { activateZeroUiRuntime } from '@react-zero-ui/core/experimental/runtime';
+
+activateZeroUiRuntime(variantKeyMap);
+
+export default function ZeroUiRuntime() {
+ return null; // this component just runs the side effect
+}
diff --git a/packages/core/dev/next/next-env.d.ts b/packages/core/dev/next/next-env.d.ts
new file mode 100644
index 0000000..1b3be08
--- /dev/null
+++ b/packages/core/dev/next/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/packages/core/dev/next/package.json b/packages/core/dev/next/package.json
new file mode 100644
index 0000000..b542381
--- /dev/null
+++ b/packages/core/dev/next/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "dev-next",
+ "version": "1.0.0",
+ "scripts": {
+ "dev": "rm -rf .next && next dev",
+ "build": "next build",
+ "start": "next start",
+ "clean": "rm -rf .next node_modules package-lock.json"
+ },
+ "pnpm": {
+ "packageManager": "pnpm@latest",
+ "installConfig": {
+ "hoistPattern": []
+ }
+ },
+ "dependencies": {
+ "next": "^15.0.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4.1.10",
+ "@types/node": "24.0.0",
+ "@types/react": "19.1.7",
+ "eslint-plugin-react-zero-ui": "0.0.1-beta.1",
+ "postcss": "^8.5.5",
+ "tailwindcss": "^4.1.10",
+ "typescript": "5.8.3"
+ }
+}
\ No newline at end of file
diff --git a/packages/core/dev/next/postcss.config.mjs b/packages/core/dev/next/postcss.config.mjs
new file mode 100644
index 0000000..f936f2e
--- /dev/null
+++ b/packages/core/dev/next/postcss.config.mjs
@@ -0,0 +1,4 @@
+// postcss.config.mjs
+// has to just the build version of the postcss plugin NO TS
+const config = { plugins: ['../../dist/postcss/index.cjs', '@tailwindcss/postcss'] };
+export default config;
diff --git a/packages/core/dev/next/tsconfig.json b/packages/core/dev/next/tsconfig.json
new file mode 100644
index 0000000..baa7845
--- /dev/null
+++ b/packages/core/dev/next/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "compilerOptions": {
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "forceConsistentCasingInFileNames": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "incremental": true,
+ "module": "ESNext",
+ "esModuleInterop": true,
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "plugins": [{ "name": "next" }],
+ "target": "ES2017",
+ "baseUrl": ".",
+ "paths": {
+ "@zero-ui/attributes": ["./.zero-ui/attributes"],
+ "@react-zero-ui/core": ["../../dist"],
+ "@react-zero-ui/core/postcss": ["../../dist/postcss"],
+ "@react-zero-ui/core/experimental": ["../../dist/experimental"],
+ "@react-zero-ui/core/experimental/runtime": ["../../dist/experimental/runtime"]
+ }
+ },
+ "include": ["**/*.ts", "**/*.tsx", ".next/**/*.d.ts", ".next/types/**/*.ts", ".zero-ui/**/*.d.ts", "next-env.d.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/packages/core/package.json b/packages/core/package.json
index 11dcdd7..df75afb 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,30 +1,19 @@
{
"name": "@react-zero-ui/core",
- "version": "0.3.3",
+ "version": "0.3.4",
"private": false,
- "description": "Ultra-fast React UI state library with zero runtime, zero re-renders, Tailwind variant support, and automatic data-attribute/css-vars based styling. Replace context, prop drilling, and global stores with build-time magic.",
- "keywords": [
- "react",
- "react state",
- "ui state",
- "global state",
- "scoped state",
- "state management",
- "tailwind",
- "tailwind variants",
- "postcss plugin",
- "fastest ui updates",
- "zero rerenders",
- "zero render",
- "no context",
- "no prop drilling",
- "css driven state",
- "data attributes",
- "ssr safe",
- "instant ui updates",
- "react optimization",
- "high performance react"
- ],
+ "scripts": {
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
+ "dev": "tsc -p tsconfig.json --watch",
+ "prepack": "pnpm run build",
+ "smoke": "node scripts/smoke-test.js",
+ "test:all": "pnpm run test:vite && pnpm run test:next && pnpm run test:unit && pnpm run test:cli && pnpm run test:integration",
+ "test:cli": "node --test __tests__/unit/*.test.cjs",
+ "test:integration": "node --test __tests__/unit/index.test.cjs",
+ "test:next": "playwright test -c __tests__/config/playwright.next.config.js",
+ "test:unit": "tsx --test src/**/*.test.ts",
+ "test:vite": "playwright test -c __tests__/config/playwright.vite.config.js"
+ },
"homepage": "https://github.com/react-zero-ui/core#readme",
"bugs": {
"url": "https://github.com/react-zero-ui/core/issues"
@@ -44,6 +33,9 @@
"./dist/experimental/runtime.js"
],
"type": "module",
+ "main": "./dist/index.js",
+ "module": "./dist/index.js",
+ "types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
@@ -70,9 +62,6 @@
"import": "./dist/experimental/runtime.js"
}
},
- "main": "./dist/index.js",
- "module": "./dist/index.js",
- "types": "./dist/index.d.ts",
"files": [
"dist/**/*",
"README.md",
@@ -80,22 +69,12 @@
"!*.test.js",
"!*.test.cjs"
],
- "scripts": {
- "build": "rm -rf dist && tsc -p tsconfig.build.json",
- "dev": "tsc -p tsconfig.json --watch",
- "prepack": "pnpm run build",
- "smoke": "node scripts/smoke-test.js",
- "test:all": "pnpm run test:vite && pnpm run test:next && pnpm run test:unit && pnpm run test:cli && pnpm run test:integration",
- "test:cli": "node --test __tests__/unit/*.test.cjs",
- "test:integration": "node --test __tests__/unit/index.test.cjs",
- "test:next": "playwright test -c __tests__/config/playwright.next.config.js",
- "test:unit": "tsx --test src/**/*.test.ts",
- "test:vite": "playwright test -c __tests__/config/playwright.vite.config.js"
- },
"dependencies": {
+ "@babel/core": "^7.28.0",
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.0",
"@babel/parser": "^7.28.0",
+ "@babel/preset-typescript": "^7.27.1",
"@babel/traverse": "^7.28.0",
"@babel/types": "^7.28.0",
"fast-glob": "^3.3.3",
@@ -104,6 +83,7 @@
"devDependencies": {
"@playwright/test": "^1.54.0",
"@types/babel__code-frame": "^7.0.6",
+ "@types/babel__core": "^7.20.5",
"@types/babel__generator": "^7.27.0",
"@types/babel__traverse": "^7.20.7",
"@types/react": "^19.1.8",
@@ -116,5 +96,28 @@
},
"engines": {
"node": ">=18.0.0"
- }
+ },
+ "description": "Ultra-fast React UI state library with zero runtime, zero re-renders, Tailwind variant support, and automatic data-attribute/css-vars based styling. Replace context, prop drilling, and global stores with build-time magic.",
+ "keywords": [
+ "react",
+ "react state",
+ "ui state",
+ "global state",
+ "scoped state",
+ "state management",
+ "tailwind",
+ "tailwind variants",
+ "postcss plugin",
+ "fastest ui updates",
+ "zero rerenders",
+ "zero render",
+ "no context",
+ "no prop drilling",
+ "css driven state",
+ "data attributes",
+ "ssr safe",
+ "instant ui updates",
+ "react optimization",
+ "high performance react"
+ ]
}
\ No newline at end of file
diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts
index 7419354..f53b14e 100644
--- a/packages/core/src/config.ts
+++ b/packages/core/src/config.ts
@@ -1,3 +1,5 @@
+const EXT = '{ts,tsx,js,mjs,cjs,mts,cts,jsx,css,scss,sass,less}';
+
export const CONFIG = {
SUPPORTED_EXTENSIONS: { TYPESCRIPT: ['.ts', '.tsx'], JAVASCRIPT: ['.js', '.jsx'] },
HOOK_NAME: 'useUI',
@@ -10,9 +12,9 @@ export const CONFIG = {
MAX_HOOK_ARGUMENTS: 2,
HEADER: '/* AUTO-GENERATED - DO NOT EDIT */',
ZERO_UI_DIR: '.zero-ui',
- CONTENT: ['src/**/*.{ts,tsx,js,jsx}', 'app/**/*.{ts,tsx,js,jsx}', 'pages/**/*.{ts,tsx,js,jsx}'],
POSTCSS_PLUGIN: '@react-zero-ui/core/postcss',
VITE_PLUGIN: '@react-zero-ui/core/vite',
+ CONTENT: [`src/**/*.${EXT}`, `app/**/*.${EXT}`, `pages/**/*.${EXT}`],
} as const;
export const IGNORE_DIRS = [
diff --git a/packages/core/src/experimental/runtime.ts b/packages/core/src/experimental/runtime.ts
index 5735266..bb8dc0e 100644
--- a/packages/core/src/experimental/runtime.ts
+++ b/packages/core/src/experimental/runtime.ts
@@ -14,7 +14,7 @@
* --- */
/** Map emitted by the compiler: every legal data-* key โก๏ธ true */
-export type VariantKeyMap = Record;
+type VariantKeyMap = Record;
/* kebab โก๏ธ camel ("data-theme-dark" โก๏ธ "themeDark") */
const kebabToCamel = (attr: string) => attr.slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts
index 3e5b133..7eb9f1f 100644
--- a/packages/core/src/internal.ts
+++ b/packages/core/src/internal.ts
@@ -1,5 +1,5 @@
// src/internal.ts
-import { UIAction } from './index.js';
+import type { UIAction } from './index.js';
export const cssVar: unique symbol = Symbol('cssVar');
@@ -34,7 +34,6 @@ export function makeSetter(key: string, initialValue: T, getTa
const registry = typeof globalThis !== 'undefined' ? ((globalThis as any).__useUIRegistry ||= new Map()) : new Map();
const prev = registry.get(key);
- // TODO try to add per page error boundaries
if (prev !== undefined && prev !== initialValue) {
console.error(
`[useUI] Inconsistent initial values for key "${key}": ` +
diff --git a/packages/core/src/postcss/ast-generating.ts b/packages/core/src/postcss/ast-generating.ts
index 06f3094..5c8db73 100644
--- a/packages/core/src/postcss/ast-generating.ts
+++ b/packages/core/src/postcss/ast-generating.ts
@@ -7,7 +7,7 @@ import { IGNORE_DIRS } from '../config.js';
import { type NodePath } from '@babel/traverse';
import traverse from './traverse.cjs';
-const AST_CONFIG_OPTS: Partial = {
+export const AST_CONFIG_OPTS: Partial = {
sourceType: 'unambiguous',
plugins: [
'typescript', // in case it's postcss.config.ts / .cts / .mts
diff --git a/packages/core/src/postcss/ast-parsing.test.ts b/packages/core/src/postcss/ast-parsing.test.ts
index 89d8570..862416f 100644
--- a/packages/core/src/postcss/ast-parsing.test.ts
+++ b/packages/core/src/postcss/ast-parsing.test.ts
@@ -138,3 +138,26 @@ test('collectUseUIHooks should Resolve Logical Expressions &&', async () => {
assert.equal(hooks[0].stateKey, 'feature-enabled-2', 'stateKey should be feature-enabled-2');
assert.equal(hooks[0].initialValue, 'dark', 'initialValue should be true');
});
+
+test('collectUseUIHooks should resolve function-scoped const variables', async () => {
+ const code = `
+import { useUI } from '@react-zero-ui/core';
+
+export const Dashboard: React.FC = () => {
+ const theme = 'theme-test';
+ const themeValues = ['dark', 'light'];
+ const [theme2, setTheme2] = useUI(theme, themeValues[0]);
+ return (
+
+
+ );
+};
+ `;
+ const ast = parse(code, { sourceType: 'module', plugins: ['jsx', 'typescript'] });
+ const hooks = collectUseUIHooks(ast, code);
+
+ assert.equal(hooks.length, 1);
+ assert.equal(hooks[0].stateKey, 'theme-test', 'stateKey should be theme-test');
+ assert.equal(hooks[0].initialValue, 'dark', 'initialValue should be dark');
+ assert.equal(hooks[0].setterFnName, 'setTheme2', 'setterFnName should be setTheme2');
+});
diff --git a/packages/core/src/postcss/ast-parsing.ts b/packages/core/src/postcss/ast-parsing.ts
index 530235c..d611b47 100644
--- a/packages/core/src/postcss/ast-parsing.ts
+++ b/packages/core/src/postcss/ast-parsing.ts
@@ -1,6 +1,8 @@
-// src/core/postcss/ast-parsing.cts
+// src/core/postcss/ast-parsing.ts
import os from 'os';
-import { parse, ParserOptions } from '@babel/parser';
+import { TransformOptions, transformSync, type BabelFileResult } from '@babel/core';
+import tsPreset from '@babel/preset-typescript';
+import { type ParserOptions } from '@babel/parser';
import * as t from '@babel/types';
import { CONFIG } from '../config.js';
import * as fs from 'fs';
@@ -12,7 +14,7 @@ import { findAllSourceFiles, mapLimit, toKebabCase } from './helpers.js';
import { NodePath, Node } from '@babel/traverse';
import traverse from './traverse.cjs';
-const PARSE_OPTS = (f: string): Partial => ({
+export const PARSE_OPTS = (f: string): Partial => ({
sourceType: 'module',
plugins: ['jsx', 'typescript', 'decorators-legacy', 'topLevelAwait'],
sourceFilename: f,
@@ -31,21 +33,18 @@ export interface HookMeta {
scope: 'global' | 'scoped';
}
-/**
- * Collect every `[ value, setterFn ] = useUI('key', 'initial')` in a file.
- * Re-uses `literalFromNode` so **initialArg** can be:
- * โข literal `'dark'`
- * โข local const `DARK`
- * โข static template `` `da${'rk'}` ``
- * Throws if the key is dynamic or if the initial value cannot be
- * reduced to a space-free string.
- */
const ALL_HOOK_NAMES = new Set([CONFIG.HOOK_NAME, CONFIG.LOCAL_HOOK_NAME]);
type HookName = typeof CONFIG.HOOK_NAME | typeof CONFIG.LOCAL_HOOK_NAME;
-const OBJ_NAMES = new Set([CONFIG.SSR_HOOK_NAME, CONFIG.SSR_HOOK_NAME_SCOPED]);
+const SSR_HOOKS = new Set([CONFIG.SSR_HOOK_NAME, CONFIG.SSR_HOOK_NAME_SCOPED]);
type LocalHookName = typeof CONFIG.SSR_HOOK_NAME | typeof CONFIG.SSR_HOOK_NAME_SCOPED;
+/**
+ * @param ast - The AST to traverse.
+ * @param sourceCode - The source code to use for resolving literals.
+ * @returns setterFnName, stateKey, initialValue, scope for every hook in the file.
+ */
+
export function collectUseUIHooks(ast: t.File, sourceCode: string): HookMeta[] {
const hooks: HookMeta[] = [];
/* ---------- cache resolved literals per AST node ---------- */
@@ -121,25 +120,18 @@ export function collectUseUIHooks(ast: t.File, sourceCode: string): HookMeta[] {
);
}
- // const binding = path.scope.getBinding(setterEl.name);
- // if (!binding) {
- // throwCodeFrame(path, path.opts?.filename, sourceCode, `[Zero-UI] Could not resolve binding for setter "${setterEl.name}".`);
- // }
-
const scope = init.callee.name === CONFIG.HOOK_NAME ? 'global' : 'scoped';
hooks.push({ setterFnName: setterEl.name, stateKey, initialValue, scope });
},
- /* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- 2. NEW: zeroSSR.onClick('key', ['v1','v2'])
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
+ // zeroSSR.onClick('key', ['v1','v2'])
CallExpression(path) {
const callee = path.node.callee;
if (
!t.isMemberExpression(callee) ||
!t.isIdentifier(callee.object) ||
- !OBJ_NAMES.has(callee.object.name as LocalHookName) ||
+ !SSR_HOOKS.has(callee.object.name as LocalHookName) ||
!t.isIdentifier(callee.property, { name: 'onClick' })
)
return;
@@ -249,14 +241,21 @@ export async function processVariants(changedFiles: string[] | null = null): Pro
// Read the file
const code = fs.readFileSync(fp, 'utf8');
- // AST Parse the file
- const ast = parse(code, PARSE_OPTS(fp));
+ const isCodeFile = /\.[cm]?[jt]sx?$/.test(fp.toLowerCase());
- // Collect the useUI hooks
- const hooks = collectUseUIHooks(ast, code);
+ if (isCodeFile) {
+ // AST Parse the file
+ const ast = parseJsLike(code, fp);
- // Temporarily store empty token map; we'll fill it after we know all keys
- fileCache.set(fp, { hash: sig, hooks, tokens: new Map() });
+ // Collect the useUI hooks
+ const hooks = collectUseUIHooks(ast, code);
+
+ // Temporarily store empty token map; we'll fill it after we know all keys
+ fileCache.set(fp, { hash: sig, hooks, tokens: new Map() });
+ } else {
+ // CSS file: no hooks, empty array
+ fileCache.set(fp, { hash: sig, hooks: [], tokens: new Map() });
+ }
});
/* Phase B - build global key set */
@@ -318,6 +317,29 @@ export async function processVariants(changedFiles: string[] | null = null): Pro
return { finalVariants, initialGlobalValues, sourceFiles: srcFiles };
}
+const cache = new Map();
+
+const defaultOpts: Partial = {
+ presets: [[tsPreset, { isTSX: true, allowDeclareFields: true, allExtensions: true }]],
+ parserOpts: { sourceType: 'module', plugins: ['jsx', 'decorators-legacy', 'typescript', 'topLevelAwait'] },
+ ast: true,
+ code: false,
+};
+
+export function parseJsLike(code: string, filename: string, opts: TransformOptions = defaultOpts): t.File {
+ const key = filename + '\0' + code.length;
+ if (cache.has(key)) return cache.get(key)!;
+
+ const result = transformSync(code, { ...opts, filename }) as BabelFileResult & { ast: t.File }; // <โ narrow type
+
+ if (!result.ast) {
+ throw new Error(`[Zero-UI] Failed to parse file: ${filename}`);
+ }
+
+ cache.set(key, result.ast);
+ return result.ast;
+}
+
/**
* @param nodeOrPath - The node or node path to throw the code frame for.
* @param filename - The filename to include in the code frame.
diff --git a/packages/core/src/postcss/global.d.ts b/packages/core/src/postcss/global.d.ts
new file mode 100644
index 0000000..4bf2771
--- /dev/null
+++ b/packages/core/src/postcss/global.d.ts
@@ -0,0 +1,5 @@
+declare module '@babel/preset-typescript' {
+ import { PluginItem } from '@babel/core';
+ const preset: PluginItem;
+ export default preset;
+}
diff --git a/packages/core/src/postcss/helpers.ts b/packages/core/src/postcss/helpers.ts
index 5b55dc7..f97cc46 100644
--- a/packages/core/src/postcss/helpers.ts
+++ b/packages/core/src/postcss/helpers.ts
@@ -6,13 +6,17 @@ import { CONFIG, IGNORE_DIRS } from '../config.js';
import { parseJsonWithBabel, parseAndUpdatePostcssConfig, parseAndUpdateViteConfig } from './ast-generating.js';
import { VariantData } from './ast-parsing.js';
-export function toKebabCase(str: string): string {
+export function sanitize(str: string) {
if (typeof str !== 'string') {
throw new Error(`Expected string but got: ${typeof str}`);
}
if (!/^[a-zA-Z0-9_-]+$/.test(str)) {
throw new Error(`Invalid state key/value "${str}". Only alphanumerics, underscores, and dashes are allowed.`);
}
+}
+
+export function toKebabCase(str: string): string {
+ sanitize(str);
return str
.replace(/_/g, '-')
.replace(/([a-z])([A-Z])/g, '$1-$2')
@@ -75,7 +79,7 @@ export async function generateAttributesFile(finalVariants: VariantData[], initi
/* JS file */
const jsContent = `${CONFIG.HEADER}
export const bodyAttributes = ${JSON.stringify(initialGlobals, null, 2)};
-export const variantKeyMap = ${JSON.stringify(variantKeyMap, null, 2)};
+export const variantKeyMap = ${JSON.stringify(variantKeyMap, null, 2)};
`;
/* DTS file (types) */
@@ -92,7 +96,7 @@ ${variantDecl.join('\n')}
};
export declare const variantKeyMap: {
- [key: string]: true;
+ [key: string]: true | string[] | '*';
};
`;
diff --git a/packages/core/src/postcss/resolvers-compact.test.ts b/packages/core/src/postcss/resolvers-compact.test.ts
new file mode 100644
index 0000000..88070f8
--- /dev/null
+++ b/packages/core/src/postcss/resolvers-compact.test.ts
@@ -0,0 +1,250 @@
+import { test } from 'node:test';
+import assert from 'node:assert';
+import { parse } from '@babel/parser';
+import * as t from '@babel/types';
+import { NodePath } from '@babel/traverse';
+import { literalFromNode, ResolveOpts } from './resolvers.js';
+import traverse from './traverse.cjs';
+import { PARSE_OPTS } from './ast-parsing.js';
+
+/**
+ * Compact test suite for literalFromNode resolver
+ *
+ * This test file provides comprehensive coverage of the literalFromNode function
+ * which resolves JavaScript expressions to static string values at build time.
+ *
+ * What this resolver handles:
+ * - Basic literals (strings, numbers, booleans)
+ * - Template literals (with static expressions)
+ * - Binary expressions (string concatenation with +)
+ * - Unary expressions (typeof, +, -, !, void)
+ * - Conditional expressions (ternary operator)
+ * - Logical expressions (||, &&, ??)
+ * - Member expressions (object.property, array[index])
+ * - Sequence expressions (comma operator)
+ * - Const variable resolution (top-level only)
+ *
+ * Known limitations:
+ * - Cannot resolve imported variables (throws error)
+ * - Cannot resolve let/var variables (only const)
+ * - Cannot resolve function calls or dynamic expressions
+ * - undefined is not a keyword, it's treated as an identifier
+ * - Ternary expressions with complex branches may not resolve
+ * - Objects with numeric string keys may not work correctly
+ *
+ * Known bugs:
+ * - !false returns "false" instead of "true" due to string coercion
+ * (The resolver converts booleans to strings early, then ! operates on the string)
+ *
+ * Test format:
+ * Each test case has:
+ * - expr: The expression to test
+ * - expected: The expected string result (or null if unresolvable)
+ * - setup: Optional setup code (const declarations)
+ * - throws: Whether it should throw an error
+ * - skip: Whether to skip the test
+ */
+
+type TestCase = {
+ expr: string; // The expression to test (will be wrapped in context)
+ expected?: string | null; // Expected result
+ setup?: string; // Optional setup code (const declarations, etc.)
+ throws?: boolean; // Whether it should throw an error
+ skip?: boolean; // Skip this test
+};
+
+/**
+ * Helper to evaluate an expression within a code context
+ */
+function evalExpression(expr: string, setup: string = ''): { node: t.Expression; path: NodePath } | null {
+ // Wrap expression in a const declaration for proper context
+ const code = `
+ ${setup}
+ const __TEST__ = ${expr};
+ `;
+
+ const ast = parse(code, PARSE_OPTS(code));
+
+ let result: { node: t.Expression; path: NodePath } | null = null;
+
+ traverse(ast, {
+ VariableDeclarator(path: NodePath) {
+ if (t.isIdentifier(path.node.id) && path.node.id.name === '__TEST__' && path.node.init) {
+ result = { node: path.node.init as t.Expression, path: path as NodePath };
+ }
+ },
+ });
+
+ return result;
+}
+
+/**
+ * Run a single test case
+ */
+async function runTestCase(testCase: TestCase, description: string) {
+ if (testCase.skip) return;
+
+ const found = evalExpression(testCase.expr, testCase.setup);
+ assert(found, `Failed to parse expression: ${testCase.expr}`);
+
+ const opts: ResolveOpts = { throwOnFail: testCase.throws === true, source: testCase.setup + '\n' + testCase.expr };
+
+ if (testCase.throws) {
+ assert.throws(() => literalFromNode(found.node, found.path, opts), `Expected to throw for: ${description}`);
+ } else {
+ const result = literalFromNode(found.node, found.path, opts);
+ assert.strictEqual(result, testCase.expected, `Failed: ${description}\nExpression: ${testCase.expr}\nExpected: ${testCase.expected}\nGot: ${result}`);
+ }
+}
+
+/**
+ * Test suite runner
+ */
+export async function suite(name: string, cases: Record) {
+ await test(name, async (t) => {
+ for (const [description, testCase] of Object.entries(cases)) {
+ await t.test(description, async () => {
+ await runTestCase(testCase, description);
+ });
+ }
+ });
+}
+
+// ============================================================================
+// TEST SUITES
+// ============================================================================
+
+await suite('Basic Literals', {
+ 'string literal': { expr: '"hello"', expected: 'hello' },
+ 'numeric literal': { expr: '42', expected: '42' },
+ 'boolean true': { expr: 'true', expected: 'true' },
+ 'boolean false': { expr: 'false', expected: 'false' },
+ 'template literal (no expr)': { expr: '`hello world`', expected: 'hello world' },
+});
+
+await suite('Binary Expressions', {
+ 'string concatenation': { expr: '"hello" + " " + "world"', expected: 'hello world' },
+ 'number + string': { expr: '42 + "px"', expected: '42px' },
+ 'const + literal': { expr: 'PREFIX + "world"', setup: 'const PREFIX = "hello ";', expected: 'hello world' },
+});
+
+await suite('Unary Expressions', {
+ 'typeof string': { expr: 'typeof "hello"', expected: 'string' },
+ 'typeof number': { expr: 'typeof 42', expected: 'number' },
+ 'typeof boolean': { expr: 'typeof true', expected: 'boolean' },
+ 'typeof null': { expr: 'typeof null', expected: 'object' },
+ // undefined is not a keyword, it's an identifier - skip or use void 0
+ 'plus number': { expr: '+42', expected: '42' },
+ 'minus number': { expr: '-5', expected: '-5' },
+ 'not true': { expr: '!true', expected: 'false' },
+ 'not false': { expr: '!false', expected: 'true' },
+ 'void 0': { expr: 'void 0', expected: 'undefined' },
+});
+
+await suite('Conditional Expressions', {
+ 'ternary true branch': { expr: 'true ? "yes" : "no"', expected: 'yes' },
+ 'ternary false branch': { expr: 'false ? "yes" : "no"', expected: 'no' },
+ 'ternary with const condition': { expr: 'CONDITION ? "yes" : "no"', setup: 'const CONDITION = true;', expected: 'yes' },
+ 'nested ternary': { expr: 'true ? (false ? "a" : "b") : "c"', expected: 'b' },
+});
+
+await suite('Logical Expressions', {
+ 'OR with truthy left': { expr: '"primary" || "fallback"', expected: 'primary' },
+ 'OR with falsy left': { expr: 'false || "fallback"', expected: 'fallback' },
+ 'OR with null': { expr: 'null || "default"', expected: 'default' },
+ // 'OR with undefined' - undefined is an identifier, not a literal
+ 'AND with truthy left': { expr: '"truthy" && "result"', expected: 'result' },
+ 'AND with falsy left': { expr: 'false && "result"', expected: null },
+ 'nullish coalescing with null': { expr: 'null ?? "default"', expected: 'default' },
+ 'nullish coalescing with value': { expr: '"value" ?? "default"', expected: 'value' },
+ 'chained OR (null only)': { expr: 'null || "final"', expected: 'final' },
+});
+
+await suite('Template Literals with Expressions', {
+ 'template with const': { expr: '`Hello ${NAME}`', setup: 'const NAME = "World";', expected: 'Hello World' },
+ 'template with multiple consts': { expr: '`${FIRST} ${LAST}`', setup: 'const FIRST = "John"; const LAST = "Doe";', expected: 'John Doe' },
+ 'template with unary expr': { expr: '`${!true}`', expected: 'false' },
+ 'template with typeof': { expr: '`type: ${typeof "hello"}`', expected: 'type: string' },
+ 'template with number coercion': { expr: '`${+42}`', expected: '42' },
+ 'template with binary expr': { expr: '`${"a" + "b"}`', expected: 'ab' },
+ 'nested template literals': { expr: '`outer ${`inner`}`', expected: 'outer inner' },
+});
+
+await suite('Identifier Resolution', {
+ 'const string': { expr: 'THEME', setup: 'const THEME = "dark";', expected: 'dark' },
+ 'const number': { expr: 'COUNT', setup: 'const COUNT = 42;', expected: '42' },
+ 'const boolean': { expr: 'FLAG', setup: 'const FLAG = true;', expected: 'true' },
+ 'const template': { expr: 'TEMPLATE', setup: 'const TEMPLATE = `hello`;', expected: 'hello' },
+ 'const with as const': { expr: 'VALUE', setup: 'const VALUE = "test" as const;', expected: 'test' },
+ // Note: Function-scoped consts now work when used within their scope (see ast-parsing.test.ts)
+});
+
+await suite('Member Expressions', {
+ 'object property': { expr: 'OBJ.dark', setup: 'const OBJ = { dark: "theme-dark", light: "theme-light" };', expected: 'theme-dark' },
+ 'computed property (string)': { expr: 'OBJ["dark"]', setup: 'const OBJ = { dark: "theme-dark" };', expected: 'theme-dark' },
+ 'computed property (const)': { expr: 'OBJ[KEY]', setup: 'const KEY = "dark"; const OBJ = { dark: "theme-dark" };', expected: 'theme-dark' },
+ 'computed property (const with dashes)': { expr: 'y[x]', setup: 'const x = "test-toggle"; const y = { "test-toggle": "off" };', expected: 'off' },
+ 'computed property (string with dashes)': { expr: 'OBJ["dark-mode"]', setup: 'const OBJ = { "dark-mode": "theme-dark" };', expected: 'theme-dark' },
+
+ 'boolean with computed property': {
+ expr: 'bool ? y[x] : z',
+ setup: `const x = 'test-toggle'; const y = { 'test-toggle': 'off' }; const z = 'off'; const bool = true;`,
+ expected: 'off',
+ },
+ 'boolean with computed property false': {
+ expr: 'bool ? y[x] : z',
+ setup: `const x = 'test-toggle'; const y = { 'test-toggle': 'off' }; const z = 'off'; const bool = false;`,
+ expected: 'off',
+ },
+
+ 'nested object': { expr: 'THEME.colors.primary', setup: 'const THEME = { colors: { primary: "blue", secondary: "green" } };', expected: 'blue' },
+ 'array by index': { expr: 'COLORS[1]', setup: 'const COLORS = ["red", "green", "blue"];', expected: 'green' },
+ 'array with const index': { expr: 'COLORS[IDX]', setup: 'const IDX = 0; const COLORS = ["red", "green"];', expected: 'red' },
+ 'optional chaining': { expr: 'OBJ?.dark', setup: 'const OBJ = { dark: "theme" };', expected: 'theme' },
+ 'mixed nested access': { expr: 'DATA.users[0]', setup: 'const DATA = { users: ["alice", "bob"] };', expected: 'alice' },
+});
+
+await suite('Sequence Expressions', {
+ 'returns last value': { expr: '("ignored", "result")', expected: 'result' },
+ 'multiple sequences': { expr: '("a", "b", "c", "final")', expected: 'final' },
+ 'sequence with const': { expr: '("ignored", VALUE)', setup: 'const VALUE = "result";', expected: 'result' },
+});
+
+await suite('Complex Combinations', {
+ 'ternary with literals': { expr: 'true ? "dark-mode" : "light-mode"', expected: 'dark-mode' },
+ 'template with member access': { expr: '`theme-${COLORS.primary}`', setup: 'const COLORS = { primary: "blue" };', expected: 'theme-blue' },
+ 'binary with template': { expr: '`prefix-` + SUFFIX', setup: 'const SUFFIX = "value";', expected: 'prefix-value' },
+ 'logical with ternary': { expr: '(FLAG || false) ? "yes" : "no"', setup: 'const FLAG = true;', expected: 'yes' },
+ 'nested member with template': { expr: '`${CONFIG.env.mode}`', setup: 'const CONFIG = { env: { mode: "production" } };', expected: 'production' },
+});
+
+await suite('Error Cases', {
+ 'imported variable': { expr: 'IMPORTED', setup: 'import { IMPORTED } from "./lib";', throws: true },
+
+ 'let variable': { expr: 'MUTABLE', setup: 'let MUTABLE = "value";', throws: true },
+ 'var variable': { expr: 'OLD_VAR', setup: 'var OLD_VAR = "value";', throws: true },
+ 'function call': { expr: 'getValue()', expected: null },
+ 'dynamic template': { expr: '`${Math.random()}`', expected: null },
+ 'undeclared identifier': { expr: 'UNDECLARED', expected: null },
+ 'inner scope const': { expr: '(() => { const INNER = "val"; return INNER; })()', expected: null },
+});
+
+// Special edge cases
+await suite('Edge Cases', {
+ 'empty string': { expr: '""', expected: '' },
+ zero: { expr: '0', expected: '0' },
+ 'negative zero': { expr: '-0', expected: '0' },
+ 'negative number': { expr: '-42', expected: '-42' },
+ 'float number': { expr: '3.14', expected: '3.14' },
+ 'NaN coercion': { expr: '+"not a number"', expected: null },
+ 'deeply nested member': { expr: 'A.b.c.d.e', setup: 'const A = { b: { c: { d: { e: "deep" } } } };', expected: 'deep' },
+ 'array of arrays': { expr: 'MATRIX[1][0]', setup: 'const MATRIX = [["a", "b"], ["c", "d"]];', expected: 'c' },
+ 'template with escaped chars': { expr: '`line1\\nline2`', expected: 'line1\nline2' },
+ 'object with symbol property': { expr: 'OBJ.valid', setup: 'const OBJ = { valid: "yes", [Symbol()]: "no" };', expected: 'yes' },
+ 'binary with numbers': { expr: '1 + 2', expected: '12' }, // String concatenation, not addition
+ 'typeof typeof': { expr: 'typeof typeof 42', expected: 'string' },
+ 'void expression': { expr: 'void "anything"', expected: 'undefined' },
+ 'double negation': { expr: '!!true', expected: 'true' },
+});
+
+console.log('โ
All resolver tests completed');
diff --git a/packages/core/src/postcss/resolvers.test.ts b/packages/core/src/postcss/resolvers.test.ts
index eb61d62..732314a 100644
--- a/packages/core/src/postcss/resolvers.test.ts
+++ b/packages/core/src/postcss/resolvers.test.ts
@@ -396,7 +396,6 @@ test('literalFromNode should resolve ArrayExpression values via static index acc
const opts: ResolveOpts = { throwOnFail: true, source: code };
const result = literalFromNode(found.node as t.Expression, found.path, opts);
- console.log('result: ', result);
assert.strictEqual(result, expected, `Failed: ${description}`);
}
@@ -458,24 +457,7 @@ test('resolveLocalConstIdentifier should throw for let/var variables', async ()
const opts: ResolveOpts = { throwOnFail: true, source: code };
assert.throws(() => {
resolveLocalConstIdentifier(found.node, found.path, opts);
- }, /Only top-level `const` variables are allowed./);
- });
-});
-
-test('resolveLocalConstIdentifier should return null for inner scope const', async () => {
- await runTest({}, async () => {
- const code = `
- function test() {
- const THEME = "dark";
- const x = THEME;
- }
- `;
- const found = findExpression(code, (n) => t.isIdentifier(n) && n.name === 'THEME');
- assert(found);
-
- const opts: ResolveOpts = { throwOnFail: true, source: code };
- const result = resolveLocalConstIdentifier(found.node, found.path, opts);
- assert.strictEqual(result, null);
+ }, /\[Zero-UI]/);
});
});
diff --git a/packages/core/src/postcss/resolvers.ts b/packages/core/src/postcss/resolvers.ts
index 7a7caf9..c5f0295 100644
--- a/packages/core/src/postcss/resolvers.ts
+++ b/packages/core/src/postcss/resolvers.ts
@@ -29,7 +29,7 @@ export interface ResolveOpts {
* OptionalMemberExpression (a?.b)
* ObjectExpression (via MemberExpression chains)
* BooleanLiteral (handled explicitly by isBooleanLiteral)
- SequenceExpression (already handled explicitly by taking last expression)
+ * SequenceExpression (already handled explicitly by taking last expression)
* @param node - The node to convert
* @param path - The path to the node
@@ -64,8 +64,12 @@ export function literalFromNode(node: t.Expression, path: NodePath, opts
const branch = testResult.value ? node.consequent : node.alternate;
return literalFromNode(branch as t.Expression, path, opts);
}
- // If test isn't statically evaluable, return null
- return null;
+ // If test isn't statically evaluable, return attempt to resolve it
+ const litTest = literalFromNode(node.test as t.Expression, path, opts);
+ if (litTest === 'true' || litTest === 'false') {
+ const branch = litTest === 'true' ? node.consequent : node.alternate;
+ return literalFromNode(branch as t.Expression, path, opts);
+ }
}
VERBOSE && console.log('70 -> literalFromNode');
@@ -101,6 +105,12 @@ export function literalFromNode(node: t.Expression, path: NodePath, opts
case '-':
return typeof arg === 'number' || !isNaN(Number(arg)) ? String(-arg) : null;
case '!':
+ // Preserve Boolean semantics even after string coercion
+ if (arg === 'true') return 'false'; // !true -> false
+ if (arg === 'false') return 'true'; // !false -> true
+ if (arg === 'undefined') return 'true'; // !undefined === true
+ if (arg === 'null') return 'true'; // !null === true
+ // fallback: let JS decide for numbers / other strings
return String(!arg);
case 'void':
return 'undefined';
@@ -202,6 +212,7 @@ export function resolveLocalConstIdentifier(node: t.Expression, path: NodePath 352');
/** Collect the property chain (deep โก๏ธ shallow) */
- const props: (string | number)[] = [];
+ const props: (string | number | boolean)[] = [];
let current: t.Expression | t.PrivateName = node;
// Walk up until we hit the root Identifier
@@ -361,7 +369,27 @@ export function resolveMemberExpression(
props.unshift(expr.value);
} else if (t.isNumericLiteral(expr)) {
props.unshift(expr.value);
+ } else if (t.isIdentifier(expr)) {
+ // Try to resolve the identifier to a const value
+ const resolved = resolveLocalConstIdentifier(expr, path, opts);
+ if (resolved !== null) {
+ props.unshift(resolved as string | number | boolean);
+ } else {
+ // Fall back to literalFromNode for other cases
+ const lit = literalFromNode(expr, path, { ...opts, throwOnFail: true });
+ if (lit === null) {
+ throwCodeFrame(
+ path,
+ path.opts?.filename,
+ opts.source ?? path.opts?.source?.code,
+ '[Zero-UI] Member expression must resolve to a static space-free string.\n' + 'only use const identifiers as keys.'
+ );
+ }
+ const num = Number(lit);
+ props.unshift(Number.isFinite(num) ? num : lit);
+ }
} else {
+ // Original else block for non-identifier expressions
const lit = literalFromNode(expr, path, { ...opts, throwOnFail: true });
if (lit === null) {
throwCodeFrame(
@@ -401,8 +429,8 @@ export function resolveMemberExpression(
);
}
- // Must be `const` in Program scope
- if (!binding.path.isVariableDeclarator() || binding.scope.block.type !== 'Program' || (binding.path.parent as t.VariableDeclaration).kind !== 'const') {
+ // Must be `const` (from any scope)
+ if (!binding.path.isVariableDeclarator() || (binding.path.parent as t.VariableDeclaration).kind !== 'const') {
return null;
}
@@ -467,8 +495,14 @@ export function resolveMemberExpression(
\*โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ*/
function resolveObjectValue(obj: t.ObjectExpression, key: string): t.Expression | null | undefined {
for (const p of obj.properties) {
- // Matches: { dark: 'theme' } - key = 'dark'
- if (t.isObjectProperty(p) && !p.computed && t.isIdentifier(p.key) && p.key.name === key) {
+ // Matches: { dark: 'theme' } or { 'dark-mode': 'theme' }
+ if (
+ t.isObjectProperty(p) &&
+ !p.computed &&
+ ((t.isIdentifier(p.key) && p.key.name === key) ||
+ (t.isStringLiteral(p.key) && p.key.value === key) ||
+ (t.isNumericLiteral(p.key) && String(p.key.value) === key))
+ ) {
return p.value as t.Expression;
}
@@ -476,16 +510,6 @@ function resolveObjectValue(obj: t.ObjectExpression, key: string): t.Expression
if (t.isObjectProperty(p) && p.computed && t.isStringLiteral(p.key) && p.key.value === key) {
return p.value as t.Expression;
}
-
- // Matches: { "dark": "theme" } or { "1": "theme" } - key = 'dark' or '1'
- // if (t.isObjectProperty(p) && t.isStringLiteral(p.key) && p.key.value === key) {
- // return p.value as t.Expression;
- // }
-
- // // โ
New: Matches { 1: "theme" } - key = '1'
- // if (t.isObjectProperty(p) && t.isNumericLiteral(p.key) && String(p.key.value) === key) {
- // return p.value as t.Expression;
- // }
}
return null;
@@ -518,13 +542,13 @@ function containsIllegalIdentifiers(node: t.Node, path: NodePath, opts: ResolveO
);
}
- if (binding.scope.block.type !== 'Program' || (binding.path.parent as t.VariableDeclaration).kind !== 'const') {
+ if ((binding.path.parent as t.VariableDeclaration).kind !== 'const') {
throwCodeFrame(
path,
path.opts?.filename,
opts.source ?? path.opts?.source?.code,
- `[Zero-UI] Invalid scope: '${subNode.name}' is not defined at the top level.\n\n` +
- `โก๏ธ Move this variable to the top of the file, outside any functions or blocks.`
+ `[Zero-UI] Only \`const\` variables are allowed. '${subNode.name}' is declared with '${(binding.path.parent as t.VariableDeclaration).kind}'.\n\n` +
+ `โก๏ธ Change the declaration to use \`const\` instead.`
);
}
});
diff --git a/packages/core/src/postcss/scanner.ts b/packages/core/src/postcss/scanner.ts
index b748889..f45dafc 100644
--- a/packages/core/src/postcss/scanner.ts
+++ b/packages/core/src/postcss/scanner.ts
@@ -1,37 +1,64 @@
-// scanner.ts
-export function scanVariantTokens(src: string, keys: Set): Map> {
- /* 1. bootstrap the output */
- const out = new Map>();
- keys.forEach((k) => out.set(k, new Set()));
+import crypto from 'crypto';
- if (keys.size === 0) return out;
+/** Signature helper โ mtimes can collide on some FS; hash is bullet-proof */
+function sig(src: string, keys: Set): string {
+ const hash = crypto
+ .createHash('sha1')
+ .update(src)
+ .update('\0') // delimiter
+ .update([...keys].sort().join(',')) // order-independent
+ .digest('base64url')
+ .slice(0, 12); // short but unique
+ return hash;
+}
- /* 2. tokenize exactly the same way Tailwind splits a class string
- (see packages/tailwindcss/src/candidate.ts โก๏ธ TOK1 / TOK2) */
- const TOK1 = /[^<>"'`\s]*[^<>"'`\s:]/g;
- const TOK2 = /[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g;
- const tokens = [...(src.match(TOK1) ?? []), ...(src.match(TOK2) ?? [])];
+/* LRU: 2 000 recent files โ <3 MB RAM */
+const scanCache = new Map> | Error>();
- if (tokens.length === 0) return out;
+export function scanVariantTokens(src: string, keys: Set): Map> {
+ const cacheKey = sig(src, keys);
- /* 3. longest-key-first prevents "modal" matching before "modal-visible" */
- const sortedKeys = [...keys].sort((a, b) => b.length - a.length);
+ /* โโ 1. Cached hit โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
+ const cached = scanCache.get(cacheKey);
+ if (cached instanceof Map) return cached;
+ if (cached instanceof Error) throw cached;
- /* 4. scan every token */
- for (const tokRaw of tokens) {
- // Split on ":" โก๏ธ everything **before** the final utility part
- const segments = tokRaw.split(':').slice(0, -1);
+ /* โโ 2. Cold scan โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
+ const out = new Map>();
+ keys.forEach((k) => out.set(k, new Set()));
+ if (keys.size === 0) {
+ scanCache.set(cacheKey, out);
+ return out;
+ }
+
+ try {
+ /* TOKEN logic identical to before */
+ const TOK1 = /[^<>"'`\s]*[^<>"'`\s:]/g;
+ const TOK2 = /[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g;
+ const tokens = [...(src.match(TOK1) ?? []), ...(src.match(TOK2) ?? [])];
- for (const seg of segments) {
- for (const key of sortedKeys) {
- if (seg.startsWith(key + '-')) {
- const value = seg.slice(key.length + 1);
- if (value) out.get(key)!.add(value);
- break; // no smaller key can match
+ if (tokens.length) {
+ const sortedKeys = [...keys].sort((a, b) => b.length - a.length);
+ for (const tokRaw of tokens) {
+ const segments = tokRaw.split(':').slice(0, -1);
+ for (const seg of segments) {
+ for (const key of sortedKeys) {
+ if (seg.startsWith(key + '-')) {
+ const value = seg.slice(key.length + 1);
+ if (value) out.get(key)!.add(value);
+ break; // early-out
+ }
+ }
}
}
}
- }
- return out;
+ scanCache.set(cacheKey, out);
+ return out;
+ } catch (err) {
+ /* Cache the failure so we donโt loop-spam */
+ const e = err instanceof Error ? err : new Error(String(err));
+ scanCache.set(cacheKey, e);
+ throw e;
+ }
}
diff --git a/packages/core/src/postcss/test-utilities.ts b/packages/core/src/postcss/test-utilities.ts
index 6667e31..dfb4c68 100644
--- a/packages/core/src/postcss/test-utilities.ts
+++ b/packages/core/src/postcss/test-utilities.ts
@@ -1,9 +1,6 @@
import path from 'path';
import fs from 'fs';
import os from 'os';
-import assert from 'assert';
-import { literalFromNode, ResolveOpts } from './resolvers.js';
-import * as t from '@babel/types';
// Helper to create temp directory and run test
export async function runTest(files: Record, callback: () => Promise) {
diff --git a/packages/core/tsconfig.build.json b/packages/core/tsconfig.build.json
index c714811..df82d86 100644
--- a/packages/core/tsconfig.build.json
+++ b/packages/core/tsconfig.build.json
@@ -2,13 +2,8 @@
{
"extends": "../../tsconfig.base.json",
/* - compile exactly one file - */
- "include": [
- "src/**/*"
- ],
- "exclude": [
- "src/**/*.test.ts",
- "./src/postcss/test-utilities.ts"
- ],
+ "include": ["src/**/*"],
+ "exclude": ["src/**/*.test.ts", "./src/postcss/test-utilities.ts"],
/* - compiler output - */
"compilerOptions": {
"esModuleInterop": true,
@@ -17,4 +12,4 @@
"noEmit": false,
"removeComments": false
}
-}
\ No newline at end of file
+}
diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json
index 10096c3..a1ab05c 100644
--- a/packages/core/tsconfig.json
+++ b/packages/core/tsconfig.json
@@ -1,13 +1,8 @@
{
"extends": "../../tsconfig.base.json",
/* - compile exactly one file - */
- "include": [
- "src/**/*",
- "__tests__/fixtures/vite/src/ErrorBoundary.tsx"
- ],
- "exclude": [
- "node_modules"
- ],
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dev/next"],
/* - compiler output - */
"compilerOptions": {
"moduleResolution": "nodenext",
@@ -15,4 +10,4 @@
"rootDir": "./src", // keeps relative paths clean
"outDir": "./dist" // compiled JS โก๏ธ dist/
}
-}
\ No newline at end of file
+}
diff --git a/packages/eslint-zero-ui/__tests__/fixtures/next/package.json b/packages/eslint-zero-ui/__tests__/fixtures/next/package.json
index 1e669b2..2e652d0 100644
--- a/packages/eslint-zero-ui/__tests__/fixtures/next/package.json
+++ b/packages/eslint-zero-ui/__tests__/fixtures/next/package.json
@@ -26,4 +26,4 @@
"tailwindcss": "^4.1.10",
"typescript": "5.8.3"
}
-}
\ No newline at end of file
+}
diff --git a/packages/eslint-zero-ui/package.json b/packages/eslint-zero-ui/package.json
index 3a1de12..9f61b22 100644
--- a/packages/eslint-zero-ui/package.json
+++ b/packages/eslint-zero-ui/package.json
@@ -31,4 +31,4 @@
"dependencies": {
"typescript": "^5.9.2"
}
-}
\ No newline at end of file
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e89a19c..abf4b0f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -38,9 +38,15 @@ importers:
examples/demo:
dependencies:
+ '@codesandbox/sandpack-react':
+ specifier: ^2.20.0
+ version: 2.20.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@react-zero-ui/core':
- specifier: 0.1.0
- version: 0.1.0(@tailwindcss/postcss@4.1.11)(postcss@8.5.6)(react@19.1.1)(tailwindcss@4.1.11)
+ specifier: ^0.3.3
+ version: 0.3.3(@tailwindcss/postcss@4.1.11)(react@19.1.1)
+ '@react-zero-ui/svg-sprite':
+ specifier: ^0.1.0
+ version: 0.1.0(react@19.1.1)
'@vercel/analytics':
specifier: ^1.5.0
version: 1.5.0(next@15.4.5(@babel/core@7.28.0)(@playwright/test@1.54.2)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)
@@ -96,12 +102,18 @@ importers:
'@babel/code-frame':
specifier: ^7.27.1
version: 7.27.1
+ '@babel/core':
+ specifier: ^7.28.0
+ version: 7.28.0
'@babel/generator':
specifier: ^7.28.0
version: 7.28.0
'@babel/parser':
specifier: ^7.28.0
version: 7.28.0
+ '@babel/preset-typescript':
+ specifier: ^7.27.1
+ version: 7.27.1(@babel/core@7.28.0)
'@babel/traverse':
specifier: ^7.28.0
version: 7.28.0
@@ -127,6 +139,9 @@ importers:
'@types/babel__code-frame':
specifier: ^7.0.6
version: 7.0.6
+ '@types/babel__core':
+ specifier: ^7.20.5
+ version: 7.20.5
'@types/babel__generator':
specifier: ^7.27.0
version: 7.27.0
@@ -185,14 +200,28 @@ packages:
resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-annotate-as-pure@7.27.3':
+ resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-compilation-targets@7.27.2':
resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-create-class-features-plugin@7.27.1':
+ resolution: {integrity: sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
'@babel/helper-globals@7.28.0':
resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-member-expression-to-functions@7.27.1':
+ resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-module-imports@7.27.1':
resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
engines: {node: '>=6.9.0'}
@@ -203,6 +232,24 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0
+ '@babel/helper-optimise-call-expression@7.27.1':
+ resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-plugin-utils@7.27.1':
+ resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-replace-supers@7.27.1':
+ resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
+ resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-string-parser@7.27.1':
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'}
@@ -224,6 +271,66 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
+ '@babel/plugin-syntax-jsx@7.27.1':
+ resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-typescript@7.27.1':
+ resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-modules-commonjs@7.27.1':
+ resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-display-name@7.28.0':
+ resolution: {integrity: sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-development@7.27.1':
+ resolution: {integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx@7.27.1':
+ resolution: {integrity: sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-pure-annotations@7.27.1':
+ resolution: {integrity: sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-typescript@7.28.0':
+ resolution: {integrity: sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/preset-react@7.27.1':
+ resolution: {integrity: sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/preset-typescript@7.27.1':
+ resolution: {integrity: sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
'@babel/template@7.27.2':
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
engines: {node: '>=6.9.0'}
@@ -242,6 +349,45 @@ packages:
'@clack/prompts@0.8.2':
resolution: {integrity: sha512-6b9Ab2UiZwJYA9iMyboYyW9yJvAO9V753ZhS+DHKEjZRKAxPPOb7MXXu84lsPFG+vZt6FRFniZ8rXi+zCIw4yQ==}
+ '@codemirror/autocomplete@6.18.6':
+ resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==}
+
+ '@codemirror/commands@6.8.1':
+ resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==}
+
+ '@codemirror/lang-css@6.3.1':
+ resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==}
+
+ '@codemirror/lang-html@6.4.9':
+ resolution: {integrity: sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==}
+
+ '@codemirror/lang-javascript@6.2.4':
+ resolution: {integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==}
+
+ '@codemirror/language@6.11.2':
+ resolution: {integrity: sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==}
+
+ '@codemirror/lint@6.8.5':
+ resolution: {integrity: sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==}
+
+ '@codemirror/state@6.5.2':
+ resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==}
+
+ '@codemirror/view@6.38.1':
+ resolution: {integrity: sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==}
+
+ '@codesandbox/nodebox@0.1.8':
+ resolution: {integrity: sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg==}
+
+ '@codesandbox/sandpack-client@2.19.8':
+ resolution: {integrity: sha512-CMV4nr1zgKzVpx4I3FYvGRM5YT0VaQhALMW9vy4wZRhEyWAtJITQIqZzrTGWqB1JvV7V72dVEUCUPLfYz5hgJQ==}
+
+ '@codesandbox/sandpack-react@2.20.0':
+ resolution: {integrity: sha512-takd1YpW/PMQ6KPQfvseWLHWklJovGY8QYj8MtWnskGKbjOGJ6uZfyZbcJ6aCFLQMpNyjTqz9AKNbvhCOZ1TUQ==}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18 || ^19
+ react-dom: ^16.8.0 || ^17 || ^18 || ^19
+
'@emnapi/runtime@1.4.5':
resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==}
@@ -598,6 +744,27 @@ packages:
'@jridgewell/trace-mapping@0.3.29':
resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==}
+ '@lezer/common@1.2.3':
+ resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==}
+
+ '@lezer/css@1.3.0':
+ resolution: {integrity: sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==}
+
+ '@lezer/highlight@1.2.1':
+ resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==}
+
+ '@lezer/html@1.3.10':
+ resolution: {integrity: sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==}
+
+ '@lezer/javascript@1.5.1':
+ resolution: {integrity: sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==}
+
+ '@lezer/lr@1.4.2':
+ resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==}
+
+ '@marijn/find-cluster-break@1.0.2':
+ resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
+
'@next/env@15.4.5':
resolution: {integrity: sha512-ruM+q2SCOVCepUiERoxOmZY9ZVoecR3gcXNwCYZRvQQWRjhOiPJGmQ2fAiLR6YKWXcSAh7G79KEFxN3rwhs4LQ==}
@@ -661,6 +828,9 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
+ '@open-draft/deferred-promise@2.2.0':
+ resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==}
+
'@pivanov/utils@0.0.2':
resolution: {integrity: sha512-q9CN0bFWxWgMY5hVVYyBgez1jGiLBa6I+LkG37ycylPhFvEGOOeaADGtUSu46CaZasPnlY8fCdVJZmrgKb1EPA==}
peerDependencies:
@@ -680,14 +850,15 @@ packages:
peerDependencies:
preact: 10.x
- '@react-zero-ui/core@0.1.0':
- resolution: {integrity: sha512-ZaFumt9DKsuubhcJHyC6j/JKhF430Q2DYKhetNfgDDYFisLhc2k9Wd+SHuSaxAJjoRmFJlW+jaGzvk0BLtvY6Q==}
- engines: {node: '>=18.0.0'}
+ '@react-hook/intersection-observer@3.1.2':
+ resolution: {integrity: sha512-mWU3BMkmmzyYMSuhO9wu3eJVP21N8TcgYm9bZnTrMwuM818bEk+0NRM3hP+c/TqA9Ln5C7qE53p1H0QMtzYdvQ==}
peerDependencies:
- '@tailwindcss/postcss': ^4.1.10
- postcss: ^8.5.5
- react: '>=16.8.0'
- tailwindcss: ^4.1.10
+ react: '>=16.8'
+
+ '@react-hook/passive-layout-effect@1.2.1':
+ resolution: {integrity: sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==}
+ peerDependencies:
+ react: '>=16.8'
'@react-zero-ui/core@0.3.3':
resolution: {integrity: sha512-ePcck57gfZrspPf8OqFc8gFsjGkCeBEYGXwIxuSsKwg/Z/AENcIt2RoAsRjY0chywAY5h3Xdk3dOeqrb7jnlqg==}
@@ -696,6 +867,12 @@ packages:
'@tailwindcss/postcss': ^4.1.10
react: '>=16.8.0'
+ '@react-zero-ui/svg-sprite@0.1.0':
+ resolution: {integrity: sha512-kTJDWANf9b0t6t3vButyQe7dM8TuUGTAPz95BrcbjeUMqbJOF8DrkvytMWqVESwyIu0J9SrEUhtjaDZEyhJFYg==}
+ hasBin: true
+ peerDependencies:
+ react: '>=17'
+
'@rollup/pluginutils@5.2.0':
resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==}
engines: {node: '>=14.0.0'}
@@ -808,6 +985,9 @@ packages:
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
+ '@stitches/core@1.2.8':
+ resolution: {integrity: sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==}
+
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
@@ -902,9 +1082,15 @@ packages:
'@types/babel__code-frame@7.0.6':
resolution: {integrity: sha512-Anitqkl3+KrzcW2k77lRlg/GfLZLWXBuNgbEcIOU6M92yw42vsd3xV/Z/yAHEj8m+KUjL6bWOVOFqX8PFPJ4LA==}
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
'@types/babel__generator@7.27.0':
resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
'@types/babel__traverse@7.28.0':
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
@@ -1019,6 +1205,9 @@ packages:
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+ anser@2.3.2:
+ resolution: {integrity: sha512-PMqBCBvrOVDRqLGooQb+z+t1Q0PiPyurUQeZRR5uHBOVZcW8B04KMmnT12USnhpNX2wCPagWzLVppQMUG3u0Dw==}
+
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
@@ -1061,11 +1250,17 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ base64-js@1.5.1:
+ resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+
bippy@0.3.17:
resolution: {integrity: sha512-0wG0kF9IM2MfS65mpU/3oU+0bRT5Wlh0oTqeRU8eJUU2+ga/QTGr3ZxzVEbQ1051NgADC8W+im1fP3Ln0Vof+Q==}
peerDependencies:
react: '>=17.0.1'
+ boolbase@1.0.0:
+ resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+
brace-expansion@1.1.12:
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
@@ -1081,6 +1276,9 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
+ buffer@6.0.3:
+ resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
+
call-bind-apply-helpers@1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
@@ -1104,10 +1302,20 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
+ cheerio-select@1.6.0:
+ resolution: {integrity: sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g==}
+
+ cheerio@1.0.0-rc.10:
+ resolution: {integrity: sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==}
+ engines: {node: '>= 6'}
+
chownr@3.0.0:
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
engines: {node: '>=18'}
+ clean-set@1.1.2:
+ resolution: {integrity: sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug==}
+
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
@@ -1135,13 +1343,27 @@ packages:
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+ crelt@1.0.6:
+ resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
+
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
+ css-select@4.3.0:
+ resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
+
+ css-what@6.2.2:
+ resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
+ engines: {node: '>= 6'}
+
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+ d@1.0.2:
+ resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==}
+ engines: {node: '>=0.12'}
+
data-view-buffer@1.0.2:
resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
engines: {node: '>= 0.4'}
@@ -1182,6 +1404,10 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
+ dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+
detect-libc@2.0.4:
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
engines: {node: '>=8'}
@@ -1190,6 +1416,23 @@ packages:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
+ dom-serializer@1.4.1:
+ resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
+
+ domelementtype@2.3.0:
+ resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+
+ domhandler@4.3.1:
+ resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
+ engines: {node: '>= 4'}
+
+ domutils@2.8.0:
+ resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
+
+ dotenv@16.6.1:
+ resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
+ engines: {node: '>=12'}
+
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -1201,6 +1444,9 @@ packages:
resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==}
engines: {node: '>=10.13.0'}
+ entities@2.2.0:
+ resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
+
es-abstract@1.24.0:
resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==}
engines: {node: '>= 0.4'}
@@ -1229,6 +1475,17 @@ packages:
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
engines: {node: '>= 0.4'}
+ es5-ext@0.10.64:
+ resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==}
+ engines: {node: '>=0.10'}
+
+ es6-iterator@2.0.3:
+ resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==}
+
+ es6-symbol@3.1.4:
+ resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==}
+ engines: {node: '>=0.12'}
+
esbuild@0.25.8:
resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==}
engines: {node: '>=18'}
@@ -1238,6 +1495,9 @@ packages:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
+ escape-carriage@1.3.1:
+ resolution: {integrity: sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==}
+
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
@@ -1316,6 +1576,10 @@ packages:
jiti:
optional: true
+ esniff@2.0.1:
+ resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
+ engines: {node: '>=0.10'}
+
espree@10.4.0:
resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1342,6 +1606,12 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
+ event-emitter@0.3.5:
+ resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==}
+
+ ext@1.7.0:
+ resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
+
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@@ -1491,6 +1761,12 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
+ htmlparser2@6.1.0:
+ resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==}
+
+ ieee754@1.2.1:
+ resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
+
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@@ -1507,6 +1783,9 @@ packages:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
+ intersection-observer@0.10.0:
+ resolution: {integrity: sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ==}
+
is-array-buffer@3.0.5:
resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
engines: {node: '>= 0.4'}
@@ -1739,6 +2018,18 @@ packages:
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+ lucide-react@0.525.0:
+ resolution: {integrity: sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ lucide-static@0.525.0:
+ resolution: {integrity: sha512-dPQiibOV/kRv/UnaNFbiTxdDFZ267rIjHVWLv6GoUXVD5YSW71cyF4tYJVD27zSb0OOWdeWrqZsuBtRaYc4FHw==}
+
+ lz-string@1.5.0:
+ resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
+ hasBin: true
+
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
@@ -1754,6 +2045,10 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
+ mime-db@1.54.0:
+ resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
+ engines: {node: '>= 0.6'}
+
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -1812,6 +2107,9 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+ next-tick@1.1.0:
+ resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
+
next@15.4.5:
resolution: {integrity: sha512-nJ4v+IO9CPmbmcvsPebIoX3Q+S7f6Fu08/dEWu0Ttfa+wVwQRh9epcmsyCPjmL2b8MxC+CkBR97jgDhUUztI3g==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
@@ -1836,6 +2134,9 @@ packages:
node-releases@2.0.19:
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
+ nth-check@2.1.1:
+ resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+
object-inspect@1.13.4:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'}
@@ -1864,6 +2165,9 @@ packages:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
+ outvariant@1.4.0:
+ resolution: {integrity: sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==}
+
own-keys@1.0.1:
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
engines: {node: '>= 0.4'}
@@ -1880,6 +2184,12 @@ packages:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
+ parse5-htmlparser2-tree-adapter@6.0.1:
+ resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==}
+
+ parse5@6.0.1:
+ resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==}
+
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -1943,11 +2253,17 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ react-devtools-inline@4.4.0:
+ resolution: {integrity: sha512-ES0GolSrKO8wsKbsEkVeiR/ZAaHQTY4zDh1UW8DImVmm8oaGLl3ijJDvSGe+qDRKPZdPRnDtWWnSvvrgxXdThQ==}
+
react-dom@19.1.1:
resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==}
peerDependencies:
react: ^19.1.1
+ react-is@17.0.2:
+ resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+
react-scan@0.4.3:
resolution: {integrity: sha512-jhAQuQ1nja6HUYrSpbmNFHqZPsRCXk8Yqu0lHoRIw9eb8N96uTfXCpVyQhTTnJ/nWqnwuvxbpKVG/oWZT8+iTQ==}
hasBin: true
@@ -2078,10 +2394,16 @@ packages:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
+ static-browser-server@1.0.3:
+ resolution: {integrity: sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==}
+
stop-iteration-iterator@1.1.0:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
+ strict-event-emitter@0.4.6:
+ resolution: {integrity: sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==}
+
string.prototype.trim@1.2.10:
resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
engines: {node: '>= 0.4'}
@@ -2102,6 +2424,9 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
+ style-mod@4.1.2:
+ resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==}
+
styled-jsx@5.1.6:
resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
engines: {node: '>= 12.0.0'}
@@ -2123,6 +2448,10 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
+ svgstore@3.0.1:
+ resolution: {integrity: sha512-nL6WTxYnsVl3e0G/mwGEFSnPAWUrzIwHAPOwInD4QUuLDKxaKMnXduf0Ipw3m/g9AldPhp1Y8E/nkReFBukJrA==}
+ engines: {node: '>= 12'}
+
tailwindcss@4.1.11:
resolution: {integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==}
@@ -2164,6 +2493,9 @@ packages:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
+ type@2.7.3:
+ resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
+
typed-array-buffer@1.0.3:
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
engines: {node: '>= 0.4'}
@@ -2208,6 +2540,9 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+ w3c-keyname@2.2.8:
+ resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
+
webpack-virtual-modules@0.6.2:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
@@ -2292,6 +2627,10 @@ snapshots:
'@jridgewell/trace-mapping': 0.3.29
jsesc: 3.1.0
+ '@babel/helper-annotate-as-pure@7.27.3':
+ dependencies:
+ '@babel/types': 7.28.2
+
'@babel/helper-compilation-targets@7.27.2':
dependencies:
'@babel/compat-data': 7.28.0
@@ -2300,8 +2639,28 @@ snapshots:
lru-cache: 5.1.1
semver: 6.3.1
+ '@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.28.0)':
+ dependencies:
+ '@babel/core': 7.28.0
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-member-expression-to-functions': 7.27.1
+ '@babel/helper-optimise-call-expression': 7.27.1
+ '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.0)
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ '@babel/traverse': 7.28.0
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/helper-globals@7.28.0': {}
+ '@babel/helper-member-expression-to-functions@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.28.0
+ '@babel/types': 7.28.2
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/helper-module-imports@7.27.1':
dependencies:
'@babel/traverse': 7.28.0
@@ -2318,6 +2677,28 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@babel/helper-optimise-call-expression@7.27.1':
+ dependencies:
+ '@babel/types': 7.28.2
+
+ '@babel/helper-plugin-utils@7.27.1': {}
+
+ '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.0)':
+ dependencies:
+ '@babel/core': 7.28.0
+ '@babel/helper-member-expression-to-functions': 7.27.1
+ '@babel/helper-optimise-call-expression': 7.27.1
+ '@babel/traverse': 7.28.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.28.0
+ '@babel/types': 7.28.2
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/helper-string-parser@7.27.1': {}
'@babel/helper-validator-identifier@7.27.1': {}
@@ -2333,6 +2714,87 @@ snapshots:
dependencies:
'@babel/types': 7.28.2
+ '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.0)':
+ dependencies:
+ '@babel/core': 7.28.0
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.0)':
+ dependencies:
+ '@babel/core': 7.28.0
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.0)':
+ dependencies:
+ '@babel/core': 7.28.0
+ '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0)
+ '@babel/helper-plugin-utils': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.28.0)':
+ dependencies:
+ '@babel/core': 7.28.0
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.28.0)':
+ dependencies:
+ '@babel/core': 7.28.0
+ '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.0)':
+ dependencies:
+ '@babel/core': 7.28.0
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0)
+ '@babel/types': 7.28.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.28.0)':
+ dependencies:
+ '@babel/core': 7.28.0
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.28.0)':
+ dependencies:
+ '@babel/core': 7.28.0
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.0)
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/preset-react@7.27.1(@babel/core@7.28.0)':
+ dependencies:
+ '@babel/core': 7.28.0
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-validator-option': 7.27.1
+ '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.0)
+ '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.0)
+ '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.0)
+ '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.28.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/preset-typescript@7.27.1(@babel/core@7.28.0)':
+ dependencies:
+ '@babel/core': 7.28.0
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-validator-option': 7.27.1
+ '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0)
+ '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.0)
+ '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.0)
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
@@ -2367,6 +2829,114 @@ snapshots:
picocolors: 1.1.1
sisteransi: 1.0.5
+ '@codemirror/autocomplete@6.18.6':
+ dependencies:
+ '@codemirror/language': 6.11.2
+ '@codemirror/state': 6.5.2
+ '@codemirror/view': 6.38.1
+ '@lezer/common': 1.2.3
+
+ '@codemirror/commands@6.8.1':
+ dependencies:
+ '@codemirror/language': 6.11.2
+ '@codemirror/state': 6.5.2
+ '@codemirror/view': 6.38.1
+ '@lezer/common': 1.2.3
+
+ '@codemirror/lang-css@6.3.1':
+ dependencies:
+ '@codemirror/autocomplete': 6.18.6
+ '@codemirror/language': 6.11.2
+ '@codemirror/state': 6.5.2
+ '@lezer/common': 1.2.3
+ '@lezer/css': 1.3.0
+
+ '@codemirror/lang-html@6.4.9':
+ dependencies:
+ '@codemirror/autocomplete': 6.18.6
+ '@codemirror/lang-css': 6.3.1
+ '@codemirror/lang-javascript': 6.2.4
+ '@codemirror/language': 6.11.2
+ '@codemirror/state': 6.5.2
+ '@codemirror/view': 6.38.1
+ '@lezer/common': 1.2.3
+ '@lezer/css': 1.3.0
+ '@lezer/html': 1.3.10
+
+ '@codemirror/lang-javascript@6.2.4':
+ dependencies:
+ '@codemirror/autocomplete': 6.18.6
+ '@codemirror/language': 6.11.2
+ '@codemirror/lint': 6.8.5
+ '@codemirror/state': 6.5.2
+ '@codemirror/view': 6.38.1
+ '@lezer/common': 1.2.3
+ '@lezer/javascript': 1.5.1
+
+ '@codemirror/language@6.11.2':
+ dependencies:
+ '@codemirror/state': 6.5.2
+ '@codemirror/view': 6.38.1
+ '@lezer/common': 1.2.3
+ '@lezer/highlight': 1.2.1
+ '@lezer/lr': 1.4.2
+ style-mod: 4.1.2
+
+ '@codemirror/lint@6.8.5':
+ dependencies:
+ '@codemirror/state': 6.5.2
+ '@codemirror/view': 6.38.1
+ crelt: 1.0.6
+
+ '@codemirror/state@6.5.2':
+ dependencies:
+ '@marijn/find-cluster-break': 1.0.2
+
+ '@codemirror/view@6.38.1':
+ dependencies:
+ '@codemirror/state': 6.5.2
+ crelt: 1.0.6
+ style-mod: 4.1.2
+ w3c-keyname: 2.2.8
+
+ '@codesandbox/nodebox@0.1.8':
+ dependencies:
+ outvariant: 1.4.0
+ strict-event-emitter: 0.4.6
+
+ '@codesandbox/sandpack-client@2.19.8':
+ dependencies:
+ '@codesandbox/nodebox': 0.1.8
+ buffer: 6.0.3
+ dequal: 2.0.3
+ mime-db: 1.54.0
+ outvariant: 1.4.0
+ static-browser-server: 1.0.3
+
+ '@codesandbox/sandpack-react@2.20.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ dependencies:
+ '@codemirror/autocomplete': 6.18.6
+ '@codemirror/commands': 6.8.1
+ '@codemirror/lang-css': 6.3.1
+ '@codemirror/lang-html': 6.4.9
+ '@codemirror/lang-javascript': 6.2.4
+ '@codemirror/language': 6.11.2
+ '@codemirror/state': 6.5.2
+ '@codemirror/view': 6.38.1
+ '@codesandbox/sandpack-client': 2.19.8
+ '@lezer/highlight': 1.2.1
+ '@react-hook/intersection-observer': 3.1.2(react@19.1.1)
+ '@stitches/core': 1.2.8
+ anser: 2.3.2
+ clean-set: 1.1.2
+ dequal: 2.0.3
+ escape-carriage: 1.3.1
+ lz-string: 1.5.0
+ react: 19.1.1
+ react-devtools-inline: 4.4.0
+ react-dom: 19.1.1(react@19.1.1)
+ react-is: 17.0.2
+
'@emnapi/runtime@1.4.5':
dependencies:
tslib: 2.8.1
@@ -2611,6 +3181,36 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.4
+ '@lezer/common@1.2.3': {}
+
+ '@lezer/css@1.3.0':
+ dependencies:
+ '@lezer/common': 1.2.3
+ '@lezer/highlight': 1.2.1
+ '@lezer/lr': 1.4.2
+
+ '@lezer/highlight@1.2.1':
+ dependencies:
+ '@lezer/common': 1.2.3
+
+ '@lezer/html@1.3.10':
+ dependencies:
+ '@lezer/common': 1.2.3
+ '@lezer/highlight': 1.2.1
+ '@lezer/lr': 1.4.2
+
+ '@lezer/javascript@1.5.1':
+ dependencies:
+ '@lezer/common': 1.2.3
+ '@lezer/highlight': 1.2.1
+ '@lezer/lr': 1.4.2
+
+ '@lezer/lr@1.4.2':
+ dependencies:
+ '@lezer/common': 1.2.3
+
+ '@marijn/find-cluster-break@1.0.2': {}
+
'@next/env@15.4.5': {}
'@next/swc-darwin-arm64@15.4.5':
@@ -2649,6 +3249,8 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.19.1
+ '@open-draft/deferred-promise@2.2.0': {}
+
'@pivanov/utils@0.0.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
react: 19.1.1
@@ -2665,18 +3267,15 @@ snapshots:
'@preact/signals-core': 1.11.0
preact: 10.27.0
- '@react-zero-ui/core@0.1.0(@tailwindcss/postcss@4.1.11)(postcss@8.5.6)(react@19.1.1)(tailwindcss@4.1.11)':
+ '@react-hook/intersection-observer@3.1.2(react@19.1.1)':
+ dependencies:
+ '@react-hook/passive-layout-effect': 1.2.1(react@19.1.1)
+ intersection-observer: 0.10.0
+ react: 19.1.1
+
+ '@react-hook/passive-layout-effect@1.2.1(react@19.1.1)':
dependencies:
- '@babel/generator': 7.28.0
- '@babel/parser': 7.28.0
- '@babel/traverse': 7.28.0
- '@babel/types': 7.28.2
- '@tailwindcss/postcss': 4.1.11
- postcss: 8.5.6
react: 19.1.1
- tailwindcss: 4.1.11
- transitivePeerDependencies:
- - supports-color
'@react-zero-ui/core@0.3.3(@tailwindcss/postcss@4.1.11)(react@19.1.1)':
dependencies:
@@ -2692,6 +3291,19 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@react-zero-ui/svg-sprite@0.1.0(react@19.1.1)':
+ dependencies:
+ '@babel/core': 7.28.0
+ '@babel/preset-react': 7.27.1(@babel/core@7.28.0)
+ '@babel/preset-typescript': 7.27.1(@babel/core@7.28.0)
+ '@babel/traverse': 7.28.0
+ lucide-react: 0.525.0(react@19.1.1)
+ lucide-static: 0.525.0
+ react: 19.1.1
+ svgstore: 3.0.1
+ transitivePeerDependencies:
+ - supports-color
+
'@rollup/pluginutils@5.2.0(rollup@4.46.2)':
dependencies:
'@types/estree': 1.0.8
@@ -2762,6 +3374,8 @@ snapshots:
'@rtsao/scc@1.1.0': {}
+ '@stitches/core@1.2.8': {}
+
'@swc/helpers@0.5.15':
dependencies:
tslib: 2.8.1
@@ -2840,10 +3454,23 @@ snapshots:
'@types/babel__code-frame@7.0.6': {}
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.28.0
+ '@babel/types': 7.28.2
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
'@types/babel__generator@7.27.0':
dependencies:
'@babel/types': 7.28.2
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.28.0
+ '@babel/types': 7.28.2
+
'@types/babel__traverse@7.28.0':
dependencies:
'@babel/types': 7.28.2
@@ -2956,6 +3583,8 @@ snapshots:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
+ anser@2.3.2: {}
+
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
@@ -3020,6 +3649,8 @@ snapshots:
balanced-match@1.0.2: {}
+ base64-js@1.5.1: {}
+
bippy@0.3.17(@types/react@19.1.9)(react@19.1.1):
dependencies:
'@types/react-reconciler': 0.28.9(@types/react@19.1.9)
@@ -3027,6 +3658,8 @@ snapshots:
transitivePeerDependencies:
- '@types/react'
+ boolbase@1.0.0: {}
+
brace-expansion@1.1.12:
dependencies:
balanced-match: 1.0.2
@@ -3047,6 +3680,11 @@ snapshots:
node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.25.1)
+ buffer@6.0.3:
+ dependencies:
+ base64-js: 1.5.1
+ ieee754: 1.2.1
+
call-bind-apply-helpers@1.0.2:
dependencies:
es-errors: 1.3.0
@@ -3073,8 +3711,28 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
+ cheerio-select@1.6.0:
+ dependencies:
+ css-select: 4.3.0
+ css-what: 6.2.2
+ domelementtype: 2.3.0
+ domhandler: 4.3.1
+ domutils: 2.8.0
+
+ cheerio@1.0.0-rc.10:
+ dependencies:
+ cheerio-select: 1.6.0
+ dom-serializer: 1.4.1
+ domhandler: 4.3.1
+ htmlparser2: 6.1.0
+ parse5: 6.0.1
+ parse5-htmlparser2-tree-adapter: 6.0.1
+ tslib: 2.8.1
+
chownr@3.0.0: {}
+ clean-set@1.1.2: {}
+
client-only@0.0.1: {}
clsx@2.1.1: {}
@@ -3101,14 +3759,31 @@ snapshots:
convert-source-map@2.0.0: {}
+ crelt@1.0.6: {}
+
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
+ css-select@4.3.0:
+ dependencies:
+ boolbase: 1.0.0
+ css-what: 6.2.2
+ domhandler: 4.3.1
+ domutils: 2.8.0
+ nth-check: 2.1.1
+
+ css-what@6.2.2: {}
+
csstype@3.1.3: {}
+ d@1.0.2:
+ dependencies:
+ es5-ext: 0.10.64
+ type: 2.7.3
+
data-view-buffer@1.0.2:
dependencies:
call-bound: 1.0.4
@@ -3149,12 +3824,34 @@ snapshots:
has-property-descriptors: 1.0.2
object-keys: 1.1.1
+ dequal@2.0.3: {}
+
detect-libc@2.0.4: {}
doctrine@2.1.0:
dependencies:
esutils: 2.0.3
+ dom-serializer@1.4.1:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 4.3.1
+ entities: 2.2.0
+
+ domelementtype@2.3.0: {}
+
+ domhandler@4.3.1:
+ dependencies:
+ domelementtype: 2.3.0
+
+ domutils@2.8.0:
+ dependencies:
+ dom-serializer: 1.4.1
+ domelementtype: 2.3.0
+ domhandler: 4.3.1
+
+ dotenv@16.6.1: {}
+
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -3168,6 +3865,8 @@ snapshots:
graceful-fs: 4.2.11
tapable: 2.2.2
+ entities@2.2.0: {}
+
es-abstract@1.24.0:
dependencies:
array-buffer-byte-length: 1.0.2
@@ -3250,6 +3949,24 @@ snapshots:
is-date-object: 1.1.0
is-symbol: 1.1.1
+ es5-ext@0.10.64:
+ dependencies:
+ es6-iterator: 2.0.3
+ es6-symbol: 3.1.4
+ esniff: 2.0.1
+ next-tick: 1.1.0
+
+ es6-iterator@2.0.3:
+ dependencies:
+ d: 1.0.2
+ es5-ext: 0.10.64
+ es6-symbol: 3.1.4
+
+ es6-symbol@3.1.4:
+ dependencies:
+ d: 1.0.2
+ ext: 1.7.0
+
esbuild@0.25.8:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.8
@@ -3281,6 +3998,8 @@ snapshots:
escalade@3.2.0: {}
+ escape-carriage@1.3.1: {}
+
escape-string-regexp@4.0.0: {}
eslint-compat-utils@0.5.1(eslint@9.32.0(jiti@2.5.1)):
@@ -3405,6 +4124,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ esniff@2.0.1:
+ dependencies:
+ d: 1.0.2
+ es5-ext: 0.10.64
+ event-emitter: 0.3.5
+ type: 2.7.3
+
espree@10.4.0:
dependencies:
acorn: 8.15.0
@@ -3429,6 +4155,15 @@ snapshots:
esutils@2.0.3: {}
+ event-emitter@0.3.5:
+ dependencies:
+ d: 1.0.2
+ es5-ext: 0.10.64
+
+ ext@1.7.0:
+ dependencies:
+ type: 2.7.3
+
fast-deep-equal@3.1.3: {}
fast-glob@3.3.3:
@@ -3574,6 +4309,15 @@ snapshots:
dependencies:
function-bind: 1.1.2
+ htmlparser2@6.1.0:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 4.3.1
+ domutils: 2.8.0
+ entities: 2.2.0
+
+ ieee754@1.2.1: {}
+
ignore@5.3.2: {}
import-fresh@3.3.1:
@@ -3589,6 +4333,8 @@ snapshots:
hasown: 2.0.2
side-channel: 1.1.0
+ intersection-observer@0.10.0: {}
+
is-array-buffer@3.0.5:
dependencies:
call-bind: 1.0.8
@@ -3793,6 +4539,14 @@ snapshots:
dependencies:
yallist: 3.1.1
+ lucide-react@0.525.0(react@19.1.1):
+ dependencies:
+ react: 19.1.1
+
+ lucide-static@0.525.0: {}
+
+ lz-string@1.5.0: {}
+
magic-string@0.30.17:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.4
@@ -3806,6 +4560,8 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
+ mime-db@1.54.0: {}
+
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.12
@@ -3846,6 +4602,8 @@ snapshots:
natural-compare@1.4.0: {}
+ next-tick@1.1.0: {}
+
next@15.4.5(@babel/core@7.28.0)(@playwright/test@1.54.2)(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
'@next/env': 15.4.5
@@ -3872,6 +4630,10 @@ snapshots:
node-releases@2.0.19: {}
+ nth-check@2.1.1:
+ dependencies:
+ boolbase: 1.0.0
+
object-inspect@1.13.4: {}
object-keys@1.1.1: {}
@@ -3914,6 +4676,8 @@ snapshots:
type-check: 0.4.0
word-wrap: 1.2.5
+ outvariant@1.4.0: {}
+
own-keys@1.0.1:
dependencies:
get-intrinsic: 1.3.0
@@ -3932,6 +4696,12 @@ snapshots:
dependencies:
callsites: 3.1.0
+ parse5-htmlparser2-tree-adapter@6.0.1:
+ dependencies:
+ parse5: 6.0.1
+
+ parse5@6.0.1: {}
+
path-exists@4.0.0: {}
path-key@3.1.1: {}
@@ -3976,11 +4746,17 @@ snapshots:
queue-microtask@1.2.3: {}
+ react-devtools-inline@4.4.0:
+ dependencies:
+ es6-symbol: 3.1.4
+
react-dom@19.1.1(react@19.1.1):
dependencies:
react: 19.1.1
scheduler: 0.26.0
+ react-is@17.0.2: {}
+
react-scan@0.4.3(@types/react@19.1.9)(next@15.4.5(@babel/core@7.28.0)(@playwright/test@1.54.2)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rollup@4.46.2):
dependencies:
'@babel/core': 7.28.0
@@ -4195,11 +4971,20 @@ snapshots:
source-map-js@1.2.1: {}
+ static-browser-server@1.0.3:
+ dependencies:
+ '@open-draft/deferred-promise': 2.2.0
+ dotenv: 16.6.1
+ mime-db: 1.54.0
+ outvariant: 1.4.0
+
stop-iteration-iterator@1.1.0:
dependencies:
es-errors: 1.3.0
internal-slot: 1.1.0
+ strict-event-emitter@0.4.6: {}
+
string.prototype.trim@1.2.10:
dependencies:
call-bind: 1.0.8
@@ -4227,6 +5012,8 @@ snapshots:
strip-json-comments@3.1.1: {}
+ style-mod@4.1.2: {}
+
styled-jsx@5.1.6(@babel/core@7.28.0)(react@19.1.1):
dependencies:
client-only: 0.0.1
@@ -4240,6 +5027,10 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
+ svgstore@3.0.1:
+ dependencies:
+ cheerio: 1.0.0-rc.10
+
tailwindcss@4.1.11: {}
tapable@2.2.2: {}
@@ -4286,6 +5077,8 @@ snapshots:
dependencies:
prelude-ls: 1.2.1
+ type@2.7.3: {}
+
typed-array-buffer@1.0.3:
dependencies:
call-bound: 1.0.4
@@ -4348,6 +5141,8 @@ snapshots:
dependencies:
punycode: 2.3.1
+ w3c-keyname@2.2.8: {}
+
webpack-virtual-modules@0.6.2:
optional: true