aeon-ui-engine 1.0.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/CONSUMER.md +82 -40
- package/README.md +47 -16
- package/package.json +9 -2
- package/packages/cli/README.md +17 -0
- package/packages/cli/dist/bin.d.ts +1 -0
- package/packages/cli/dist/bin.js +35 -0
- package/packages/cli/dist/chunk-IW5NT2Z2.js +103 -0
- package/packages/cli/dist/index.d.ts +10 -0
- package/packages/cli/dist/index.js +6 -0
- package/packages/cli/node_modules/.bin/esbuild +21 -0
- package/packages/cli/node_modules/.bin/jiti +21 -0
- package/packages/cli/node_modules/.bin/tsc +21 -0
- package/packages/cli/node_modules/.bin/tsserver +21 -0
- package/packages/cli/node_modules/.bin/tsup +21 -0
- package/packages/cli/node_modules/.bin/tsup-node +21 -0
- package/packages/cli/node_modules/.bin/tsx +21 -0
- package/packages/cli/node_modules/.bin/vite +21 -0
- package/packages/cli/node_modules/.bin/vitest +21 -0
- package/packages/cli/package.json +50 -0
- package/packages/cli/src/bin.ts +34 -0
- package/packages/cli/src/index.ts +1 -0
- package/packages/cli/src/init.ts +107 -0
- package/packages/cli/tsconfig.json +10 -0
- package/packages/core/README.md +7 -1
- package/packages/core/dist/index.d.ts +255 -7
- package/packages/core/dist/index.js +372 -2
- package/packages/core/node_modules/.bin/esbuild +4 -4
- package/packages/core/node_modules/.bin/vite +4 -4
- package/packages/core/node_modules/.bin/vitest +2 -2
- package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/core/package.json +17 -5
- package/packages/core/src/anatomy.ts +234 -5
- package/packages/core/src/brand-theme.ts +172 -0
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/machine.test.ts +62 -1
- package/packages/core/src/machine.ts +93 -0
- package/packages/core/src/theme.ts +105 -0
- package/packages/panda/aeon.css +8 -0
- package/packages/panda/package.json +1 -1
- package/packages/panda/panda.config.ts +58 -1
- package/packages/panda/src/recipes/app-shell.ts +137 -0
- package/packages/panda/src/recipes/avatar.ts +131 -0
- package/packages/panda/src/recipes/bar.ts +206 -0
- package/packages/panda/src/recipes/button-group.ts +12 -4
- package/packages/panda/src/recipes/button.ts +1 -0
- package/packages/panda/src/recipes/combobox.ts +2 -1
- package/packages/panda/src/recipes/composer.ts +81 -0
- package/packages/panda/src/recipes/content.ts +104 -0
- package/packages/panda/src/recipes/conversation.ts +106 -0
- package/packages/panda/src/recipes/entry.ts +217 -0
- package/packages/panda/src/recipes/field.ts +12 -2
- package/packages/panda/src/recipes/floating.ts +26 -13
- package/packages/panda/src/recipes/identity.ts +73 -0
- package/packages/panda/src/recipes/index.ts +15 -0
- package/packages/panda/src/recipes/list-row.ts +109 -0
- package/packages/panda/src/recipes/metric-strip.ts +80 -0
- package/packages/panda/src/recipes/nav.ts +155 -0
- package/packages/panda/src/recipes/panel.ts +156 -0
- package/packages/panda/src/recipes/pin-input.ts +3 -2
- package/packages/panda/src/recipes/profile-header.ts +120 -0
- package/packages/panda/src/recipes/select.ts +2 -1
- package/packages/panda/src/recipes/slider.ts +6 -4
- package/packages/panda/src/recipes/theme-switcher.ts +110 -0
- package/packages/panda/src/recipes/thread.ts +88 -0
- package/packages/panda/src/theme/catalog-types.ts +2 -0
- package/packages/panda/src/theme/catalog.ts +23 -0
- package/packages/panda/src/theme/chrome.css +2 -1
- package/packages/panda/src/theme/consumer-shell.css +129 -0
- package/packages/panda/src/theme/electron.css +30 -0
- package/packages/panda/src/theme/global-theme-css.ts +9 -0
- package/packages/panda/src/theme/omarchy.themes.ts +607 -0
- package/packages/panda/src/theme/palette.ts +3 -0
- package/packages/panda/src/theme/scrollbars.css +58 -21
- package/packages/panda/src/theme/themes.config.ts +39 -1
- package/packages/panda/styled-system/css/conditions.mjs +1 -1
- package/packages/panda/styled-system/css/css.mjs +1 -1
- package/packages/panda/styled-system/css/sva.mjs +9 -4
- package/packages/panda/styled-system/helpers.mjs +11 -22
- package/packages/panda/styled-system/jsx/create-style-context.d.ts +54 -0
- package/packages/panda/styled-system/jsx/create-style-context.mjs +121 -0
- package/packages/panda/styled-system/jsx/factory.mjs +14 -5
- package/packages/panda/styled-system/jsx/index.d.ts +1 -0
- package/packages/panda/styled-system/jsx/index.mjs +1 -0
- package/packages/panda/styled-system/jsx/is-valid-prop.mjs +2 -2
- package/packages/panda/styled-system/patterns/aspect-ratio.d.ts +0 -1
- package/packages/panda/styled-system/patterns/bleed.d.ts +0 -1
- package/packages/panda/styled-system/patterns/box.d.ts +0 -1
- package/packages/panda/styled-system/patterns/center.d.ts +0 -1
- package/packages/panda/styled-system/patterns/circle.d.ts +0 -1
- package/packages/panda/styled-system/patterns/container.d.ts +0 -1
- package/packages/panda/styled-system/patterns/cq.d.ts +0 -1
- package/packages/panda/styled-system/patterns/divider.d.ts +0 -1
- package/packages/panda/styled-system/patterns/flex.d.ts +0 -1
- package/packages/panda/styled-system/patterns/float.d.ts +0 -1
- package/packages/panda/styled-system/patterns/grid-item.d.ts +0 -1
- package/packages/panda/styled-system/patterns/grid.d.ts +0 -1
- package/packages/panda/styled-system/patterns/grid.mjs +1 -1
- package/packages/panda/styled-system/patterns/hstack.d.ts +0 -1
- package/packages/panda/styled-system/patterns/hstack.mjs +1 -1
- package/packages/panda/styled-system/patterns/link-overlay.d.ts +0 -1
- package/packages/panda/styled-system/patterns/spacer.d.ts +0 -1
- package/packages/panda/styled-system/patterns/spacer.mjs +6 -2
- package/packages/panda/styled-system/patterns/square.d.ts +0 -1
- package/packages/panda/styled-system/patterns/stack.d.ts +0 -1
- package/packages/panda/styled-system/patterns/stack.mjs +1 -1
- package/packages/panda/styled-system/patterns/visually-hidden.d.ts +0 -1
- package/packages/panda/styled-system/patterns/vstack.d.ts +0 -1
- package/packages/panda/styled-system/patterns/vstack.mjs +1 -1
- package/packages/panda/styled-system/patterns/wrap.d.ts +0 -1
- package/packages/panda/styled-system/patterns/wrap.mjs +1 -1
- package/packages/panda/styled-system/recipes/aeon-accordion.d.ts +4 -3
- package/packages/panda/styled-system/recipes/aeon-accordion.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-app-shell.d.ts +34 -0
- package/packages/panda/styled-system/recipes/aeon-app-shell.mjs +70 -0
- package/packages/panda/styled-system/recipes/aeon-async.d.ts +4 -3
- package/packages/panda/styled-system/recipes/aeon-async.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-avatar.d.ts +36 -0
- package/packages/panda/styled-system/recipes/aeon-avatar.mjs +57 -0
- package/packages/panda/styled-system/recipes/aeon-badge.d.ts +3 -0
- package/packages/panda/styled-system/recipes/aeon-bar.d.ts +40 -0
- package/packages/panda/styled-system/recipes/aeon-bar.mjs +77 -0
- package/packages/panda/styled-system/recipes/aeon-button-group.d.ts +3 -2
- package/packages/panda/styled-system/recipes/aeon-button.d.ts +3 -2
- package/packages/panda/styled-system/recipes/aeon-checkbox.d.ts +4 -1
- package/packages/panda/styled-system/recipes/aeon-checkbox.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-combobox.d.ts +4 -3
- package/packages/panda/styled-system/recipes/aeon-combobox.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-composer.d.ts +31 -0
- package/packages/panda/styled-system/recipes/aeon-composer.mjs +45 -0
- package/packages/panda/styled-system/recipes/aeon-content.d.ts +34 -0
- package/packages/panda/styled-system/recipes/aeon-content.mjs +70 -0
- package/packages/panda/styled-system/recipes/aeon-conversation.d.ts +31 -0
- package/packages/panda/styled-system/recipes/aeon-conversation.mjs +61 -0
- package/packages/panda/styled-system/recipes/aeon-dialog.d.ts +4 -1
- package/packages/panda/styled-system/recipes/aeon-dialog.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-entry.d.ts +38 -0
- package/packages/panda/styled-system/recipes/aeon-entry.mjs +100 -0
- package/packages/panda/styled-system/recipes/aeon-field.d.ts +4 -3
- package/packages/panda/styled-system/recipes/aeon-field.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-identity.d.ts +36 -0
- package/packages/panda/styled-system/recipes/aeon-identity.mjs +63 -0
- package/packages/panda/styled-system/recipes/aeon-list-row.d.ts +33 -0
- package/packages/panda/styled-system/recipes/aeon-list-row.mjs +49 -0
- package/packages/panda/styled-system/recipes/aeon-menu.d.ts +4 -1
- package/packages/panda/styled-system/recipes/aeon-menu.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-metric-strip.d.ts +36 -0
- package/packages/panda/styled-system/recipes/aeon-metric-strip.mjs +54 -0
- package/packages/panda/styled-system/recipes/aeon-nav.d.ts +38 -0
- package/packages/panda/styled-system/recipes/aeon-nav.mjs +69 -0
- package/packages/panda/styled-system/recipes/aeon-panel.d.ts +34 -0
- package/packages/panda/styled-system/recipes/aeon-panel.mjs +58 -0
- package/packages/panda/styled-system/recipes/aeon-pin-input.d.ts +4 -1
- package/packages/panda/styled-system/recipes/aeon-pin-input.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-popover.d.ts +4 -1
- package/packages/panda/styled-system/recipes/aeon-popover.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-profile-header.d.ts +36 -0
- package/packages/panda/styled-system/recipes/aeon-profile-header.mjs +62 -0
- package/packages/panda/styled-system/recipes/aeon-progress.d.ts +4 -1
- package/packages/panda/styled-system/recipes/aeon-progress.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-radio-group.d.ts +4 -1
- package/packages/panda/styled-system/recipes/aeon-radio-group.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-scroll.d.ts +4 -3
- package/packages/panda/styled-system/recipes/aeon-scroll.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-select.d.ts +4 -3
- package/packages/panda/styled-system/recipes/aeon-select.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-separator.d.ts +4 -1
- package/packages/panda/styled-system/recipes/aeon-separator.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-slider.d.ts +4 -1
- package/packages/panda/styled-system/recipes/aeon-slider.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-switch.d.ts +4 -1
- package/packages/panda/styled-system/recipes/aeon-switch.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-tabs.d.ts +4 -1
- package/packages/panda/styled-system/recipes/aeon-tabs.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-theme-switcher.d.ts +34 -0
- package/packages/panda/styled-system/recipes/aeon-theme-switcher.mjs +59 -0
- package/packages/panda/styled-system/recipes/aeon-thread.d.ts +31 -0
- package/packages/panda/styled-system/recipes/aeon-thread.mjs +53 -0
- package/packages/panda/styled-system/recipes/aeon-toast.d.ts +4 -1
- package/packages/panda/styled-system/recipes/aeon-toast.mjs +1 -0
- package/packages/panda/styled-system/recipes/aeon-tooltip.d.ts +4 -1
- package/packages/panda/styled-system/recipes/aeon-tooltip.mjs +1 -0
- package/packages/panda/styled-system/recipes/index.d.ts +16 -1
- package/packages/panda/styled-system/recipes/index.mjs +16 -1
- package/packages/panda/styled-system/styles.css +2317 -450
- package/packages/panda/styled-system/tokens/index.mjs +43 -7
- package/packages/panda/styled-system/tokens/tokens.d.ts +5 -5
- package/packages/panda/styled-system/types/composition.d.ts +86 -23
- package/packages/panda/styled-system/types/conditions.d.ts +34 -10
- package/packages/panda/styled-system/types/csstype.d.ts +6405 -5133
- package/packages/panda/styled-system/types/jsx.d.ts +27 -10
- package/packages/panda/styled-system/types/pattern.d.ts +4 -4
- package/packages/panda/styled-system/types/prop-type.d.ts +11 -1
- package/packages/panda/styled-system/types/style-props.d.ts +2763 -2165
- package/packages/panda/styled-system/types/system-types.d.ts +38 -80
- package/packages/panda/theme-runtime.css +945 -0
- package/packages/primitives/dist/chunk-4NOKRUSG.js +81 -0
- package/packages/primitives/dist/chunk-R25CUHNF.js +46 -0
- package/packages/primitives/dist/chunk-RO2DMFN5.js +89 -0
- package/packages/primitives/dist/chunk-V5D7LS4J.js +61 -0
- package/packages/primitives/dist/chunk-YBPNI2LI.js +70 -0
- package/packages/primitives/dist/index.d.ts +5 -0
- package/packages/primitives/dist/index.js +46 -14
- package/packages/primitives/dist/machines/app-nav.d.ts +96 -0
- package/packages/primitives/dist/machines/app-nav.js +12 -0
- package/packages/primitives/dist/machines/app-shell.d.ts +57 -0
- package/packages/primitives/dist/machines/app-shell.js +8 -0
- package/packages/primitives/dist/machines/async.d.ts +4 -4
- package/packages/primitives/dist/machines/content-region.d.ts +72 -0
- package/packages/primitives/dist/machines/content-region.js +8 -0
- package/packages/primitives/dist/machines/panel.d.ts +50 -0
- package/packages/primitives/dist/machines/panel.js +6 -0
- package/packages/primitives/dist/machines/sticky-bar.d.ts +62 -0
- package/packages/primitives/dist/machines/sticky-bar.js +8 -0
- package/packages/primitives/node_modules/.bin/esbuild +21 -0
- package/packages/primitives/node_modules/.bin/vite +21 -0
- package/packages/primitives/node_modules/.bin/vitest +2 -2
- package/packages/primitives/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/primitives/package.json +15 -5
- package/packages/primitives/scripts/xstate-introspect.mjs +35 -0
- package/packages/primitives/src/__tests__/app-nav.test.ts +30 -0
- package/packages/primitives/src/index.ts +5 -0
- package/packages/primitives/src/machines/app-nav.ts +107 -0
- package/packages/primitives/src/machines/app-shell.ts +63 -0
- package/packages/primitives/src/machines/content-region.ts +107 -0
- package/packages/primitives/src/machines/panel.ts +71 -0
- package/packages/primitives/src/machines/sticky-bar.ts +89 -0
- package/packages/react/dist/index.d.ts +536 -17
- package/packages/react/dist/index.js +2163 -479
- package/packages/react/node_modules/.bin/esbuild +21 -0
- package/packages/react/node_modules/.bin/vite +21 -0
- package/packages/react/node_modules/.bin/vitest +2 -2
- package/packages/react/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/react/package.json +18 -7
- package/packages/react/src/app-nav/index.tsx +249 -0
- package/packages/react/src/app-shell/index.tsx +154 -0
- package/packages/react/src/async/index.tsx +6 -1
- package/packages/react/src/async/map-content-state.ts +18 -0
- package/packages/react/src/avatar/index.tsx +23 -1
- package/packages/react/src/bar/index.tsx +101 -0
- package/packages/react/src/button/index.tsx +4 -2
- package/packages/react/src/composer/index.tsx +75 -0
- package/packages/react/src/content/index.tsx +149 -0
- package/packages/react/src/conversation/index.tsx +78 -0
- package/packages/react/src/entry/index.tsx +85 -0
- package/packages/react/src/field/index.tsx +31 -18
- package/packages/react/src/hooks/use-fit-text.ts +59 -0
- package/packages/react/src/identity/index.tsx +110 -0
- package/packages/react/src/index.ts +19 -0
- package/packages/react/src/list-row/index.tsx +80 -0
- package/packages/react/src/metric-strip/index.tsx +88 -0
- package/packages/react/src/nav/index.tsx +155 -0
- package/packages/react/src/panel/index.tsx +267 -0
- package/packages/react/src/profile-header/index.tsx +91 -0
- package/packages/react/src/prompt/index.tsx +161 -0
- package/packages/react/src/radio-group/index.tsx +19 -7
- package/packages/react/src/slider/index.tsx +3 -1
- package/packages/react/src/status-banner/index.tsx +84 -0
- package/packages/react/src/sticky-bar/index.tsx +135 -0
- package/packages/react/src/switch/index.tsx +7 -1
- package/packages/react/src/theme-switcher/index.tsx +227 -0
- package/packages/react/src/thread/index.tsx +95 -0
- package/packages/react/src/toast/primitives.tsx +2 -2
- package/packages/react/src/tooltip/tooltip.test.tsx +9 -1
- package/packages/react/tsconfig.json +2 -1
- package/packages/schemas/README.md +52 -0
- package/packages/schemas/dist/index.d.ts +287 -0
- package/packages/schemas/dist/index.js +701 -0
- package/packages/schemas/json/accordion.json +132 -0
- package/packages/schemas/json/async.json +161 -0
- package/packages/schemas/json/button.json +170 -0
- package/packages/schemas/json/catalog.json +2299 -0
- package/packages/schemas/json/checkbox.json +136 -0
- package/packages/schemas/json/combobox.json +130 -0
- package/packages/schemas/json/dialog.json +154 -0
- package/packages/schemas/json/field.json +171 -0
- package/packages/schemas/json/menu.json +138 -0
- package/packages/schemas/json/popover.json +133 -0
- package/packages/schemas/json/progress.json +137 -0
- package/packages/schemas/json/scroll.json +129 -0
- package/packages/schemas/json/select.json +140 -0
- package/packages/schemas/json/switch.json +136 -0
- package/packages/schemas/json/tabs.json +135 -0
- package/packages/schemas/json/toast.json +141 -0
- package/packages/schemas/json/tooltip.json +123 -0
- package/packages/schemas/node_modules/.bin/esbuild +21 -0
- package/packages/schemas/node_modules/.bin/jiti +21 -0
- package/packages/schemas/node_modules/.bin/tsc +21 -0
- package/packages/schemas/node_modules/.bin/tsserver +21 -0
- package/packages/schemas/node_modules/.bin/tsup +21 -0
- package/packages/schemas/node_modules/.bin/tsup-node +21 -0
- package/packages/schemas/node_modules/.bin/tsx +21 -0
- package/packages/schemas/node_modules/.bin/vite +21 -0
- package/packages/schemas/node_modules/.bin/vitest +21 -0
- package/packages/schemas/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/schemas/package.json +56 -0
- package/packages/schemas/prompts/cursorrules.md +64 -0
- package/packages/schemas/prompts/statecharts.md +31 -0
- package/packages/schemas/prompts/system.md +30 -0
- package/packages/schemas/registry/catalog.json +54 -0
- package/packages/schemas/registry/components/accordion.json +132 -0
- package/packages/schemas/registry/components/async.json +161 -0
- package/packages/schemas/registry/components/button.json +170 -0
- package/packages/schemas/registry/components/checkbox.json +136 -0
- package/packages/schemas/registry/components/combobox.json +130 -0
- package/packages/schemas/registry/components/dialog.json +154 -0
- package/packages/schemas/registry/components/field.json +171 -0
- package/packages/schemas/registry/components/menu.json +138 -0
- package/packages/schemas/registry/components/popover.json +133 -0
- package/packages/schemas/registry/components/progress.json +137 -0
- package/packages/schemas/registry/components/scroll.json +129 -0
- package/packages/schemas/registry/components/select.json +140 -0
- package/packages/schemas/registry/components/switch.json +136 -0
- package/packages/schemas/registry/components/tabs.json +135 -0
- package/packages/schemas/registry/components/toast.json +141 -0
- package/packages/schemas/registry/components/tooltip.json +123 -0
- package/packages/schemas/registry/machines/async.json +219 -0
- package/packages/schemas/registry/machines/buttonLifecycle.json +224 -0
- package/packages/schemas/registry/machines/dialog.json +143 -0
- package/packages/schemas/registry/machines/field.json +275 -0
- package/packages/schemas/scripts/emit.ts +61 -0
- package/packages/schemas/src/aeon-to-schema.test.ts +70 -0
- package/packages/schemas/src/aeon-to-schema.ts +206 -0
- package/packages/schemas/src/agent-bridge.ts +82 -0
- package/packages/schemas/src/components.ts +307 -0
- package/packages/schemas/src/index.test.ts +25 -0
- package/packages/schemas/src/index.ts +25 -0
- package/packages/schemas/src/prompts.ts +121 -0
- package/packages/schemas/src/registry.ts +73 -0
- package/packages/schemas/src/schema-builder.ts +87 -0
- package/packages/schemas/tsconfig.json +9 -0
- package/packages/solid/package.json +2 -2
- package/packages/svelte/node_modules/.bin/esbuild +21 -0
- package/packages/svelte/node_modules/.bin/vite +4 -4
- package/packages/svelte/package.json +15 -5
- package/packages/ui/README.md +19 -6
- package/packages/ui/dist/index.d.ts +254 -9
- package/packages/ui/dist/index.js +690 -139
- package/packages/ui/src/app-nav.tsx +2 -0
- package/packages/ui/src/app-shell.tsx +44 -0
- package/packages/ui/src/async.tsx +2 -1
- package/packages/ui/src/avatar.tsx +35 -0
- package/packages/ui/src/bar.tsx +34 -0
- package/packages/ui/src/composer.tsx +27 -0
- package/packages/ui/src/content.tsx +43 -0
- package/packages/ui/src/conversation.tsx +43 -0
- package/packages/ui/src/entry.tsx +76 -0
- package/packages/ui/src/field.tsx +5 -0
- package/packages/ui/src/identity.tsx +35 -0
- package/packages/ui/src/index.ts +20 -0
- package/packages/ui/src/list-row.tsx +30 -0
- package/packages/ui/src/metric-strip.tsx +29 -0
- package/packages/ui/src/nav.tsx +46 -0
- package/packages/ui/src/panel.tsx +39 -0
- package/packages/ui/src/profile-header.tsx +35 -0
- package/packages/ui/src/prompt.tsx +29 -0
- package/packages/ui/src/status-banner.tsx +1 -0
- package/packages/ui/src/sticky-bar.tsx +42 -0
- package/packages/ui/src/switch.tsx +17 -4
- package/packages/ui/src/theme-switcher.tsx +41 -0
- package/packages/ui/src/thread.tsx +35 -0
- package/packages/vue/package.json +3 -3
- package/scripts/vite-aliases.mjs +65 -3
- package/scripts/xstate-introspect.mjs +68 -0
- package/packages/react/src/async/legacy-region.tsx +0 -38
|
@@ -40,6 +40,44 @@ export const dialogAnatomy = anatomy('dialog', [
|
|
|
40
40
|
'closeTrigger',
|
|
41
41
|
] as const)
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Prompt — confirm / permission / update-style dialog on top of Dialog.
|
|
45
|
+
* Universal parts: eyebrow, meta, amount, actions (primary + secondary).
|
|
46
|
+
*/
|
|
47
|
+
export const promptAnatomy = anatomy('prompt', [
|
|
48
|
+
'root',
|
|
49
|
+
'eyebrow',
|
|
50
|
+
'meta',
|
|
51
|
+
'amount',
|
|
52
|
+
'actions',
|
|
53
|
+
'primary',
|
|
54
|
+
'secondary',
|
|
55
|
+
] as const)
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* AppNav — section + detail stack (list→detail, settings drill-in).
|
|
59
|
+
*/
|
|
60
|
+
export const appNavAnatomy = anatomy('appNav', [
|
|
61
|
+
'root',
|
|
62
|
+
'rail',
|
|
63
|
+
'section',
|
|
64
|
+
'stage',
|
|
65
|
+
'breadcrumb',
|
|
66
|
+
'crumb',
|
|
67
|
+
'back',
|
|
68
|
+
] as const)
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* StatusBanner — non-blocking top-of-app notice (update ready, syncing, offline).
|
|
72
|
+
*/
|
|
73
|
+
export const statusBannerAnatomy = anatomy('statusBanner', [
|
|
74
|
+
'root',
|
|
75
|
+
'copy',
|
|
76
|
+
'title',
|
|
77
|
+
'body',
|
|
78
|
+
'actions',
|
|
79
|
+
] as const)
|
|
80
|
+
|
|
43
81
|
export const tabsAnatomy = anatomy('tabs', [
|
|
44
82
|
'root',
|
|
45
83
|
'list',
|
|
@@ -81,10 +119,10 @@ export const fieldAnatomy = anatomy('field', [
|
|
|
81
119
|
export const scrollAnatomy = anatomy('scroll', ['root', 'viewport', 'content'] as const)
|
|
82
120
|
|
|
83
121
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
122
|
+
* Spatial play surface — universal mat + regions + entities (not a specific game).
|
|
123
|
+
* App logic names regions; entity labels come from compose `items`. See LAYOUT_COORDINATES.md.
|
|
86
124
|
*/
|
|
87
|
-
export const playSurfaceAnatomy = anatomy('play', ['
|
|
125
|
+
export const playSurfaceAnatomy = anatomy('play', ['root', 'mat', 'region', 'entity'] as const)
|
|
88
126
|
|
|
89
127
|
export const selectAnatomy = anatomy('select', [
|
|
90
128
|
'root',
|
|
@@ -140,8 +178,118 @@ export const toastAnatomy = anatomy('toast', [
|
|
|
140
178
|
'closeTrigger',
|
|
141
179
|
] as const)
|
|
142
180
|
|
|
143
|
-
/** Profile image — loaded / loading / error on root `data-aeon-state`. */
|
|
144
|
-
export const avatarAnatomy = anatomy('avatar', ['root', 'image', 'fallback'] as const)
|
|
181
|
+
/** Profile image — loaded / loading / error on root `data-aeon-state`. Badge = presence / status corner. */
|
|
182
|
+
export const avatarAnatomy = anatomy('avatar', ['root', 'image', 'fallback', 'badge'] as const)
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Identity strip — avatar + primary/secondary text (items-market AvatarWithName).
|
|
186
|
+
* Presentational; compose into ProfileHeader / ListRow / AccountCluster.
|
|
187
|
+
*/
|
|
188
|
+
export const identityAnatomy = anatomy('identity', [
|
|
189
|
+
'root',
|
|
190
|
+
'avatar',
|
|
191
|
+
'title',
|
|
192
|
+
'subtitle',
|
|
193
|
+
'meta',
|
|
194
|
+
'trailing',
|
|
195
|
+
] as const)
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Profile header — dense account hero (not dating). Media optional; metrics + actions zones.
|
|
199
|
+
* Spatial: identity leading, metrics inline, actions trailing / bottom leading pair.
|
|
200
|
+
*/
|
|
201
|
+
export const profileHeaderAnatomy = anatomy('profileHeader', [
|
|
202
|
+
'root',
|
|
203
|
+
'media',
|
|
204
|
+
'identity',
|
|
205
|
+
'metrics',
|
|
206
|
+
'actions',
|
|
207
|
+
'body',
|
|
208
|
+
] as const)
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Entry — compact multi-value content card (feed post, catalog listing, activity item).
|
|
212
|
+
* Universal layout only — not a social/marketplace niche.
|
|
213
|
+
* List stacks cards; Root is one card (idle | selected | muted).
|
|
214
|
+
* Zones: header (leading · heading · meta) · media · body · values · actions · footer.
|
|
215
|
+
*/
|
|
216
|
+
export const entryAnatomy = anatomy('entry', [
|
|
217
|
+
'list',
|
|
218
|
+
'root',
|
|
219
|
+
'header',
|
|
220
|
+
'leading',
|
|
221
|
+
'heading',
|
|
222
|
+
'title',
|
|
223
|
+
'subtitle',
|
|
224
|
+
'meta',
|
|
225
|
+
'media',
|
|
226
|
+
'body',
|
|
227
|
+
'values',
|
|
228
|
+
'value',
|
|
229
|
+
'actions',
|
|
230
|
+
'footer',
|
|
231
|
+
] as const)
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Metric strip — ultra-dense value/label chips (items-market im-stat cluster).
|
|
235
|
+
* Generic stats only — not wallet/currency product niches.
|
|
236
|
+
*/
|
|
237
|
+
export const metricStripAnatomy = anatomy('metricStrip', [
|
|
238
|
+
'root',
|
|
239
|
+
'chip',
|
|
240
|
+
'value',
|
|
241
|
+
'label',
|
|
242
|
+
] as const)
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* List row — settings / account menu row (h-12 hit target, leading icon, trailing control).
|
|
246
|
+
*/
|
|
247
|
+
export const listRowAnatomy = anatomy('listRow', [
|
|
248
|
+
'root',
|
|
249
|
+
'leading',
|
|
250
|
+
'label',
|
|
251
|
+
'description',
|
|
252
|
+
'trailing',
|
|
253
|
+
] as const)
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Conversation — inbox / DM list row (universal messaging chrome, not dating).
|
|
257
|
+
* Leading avatar · title · preview · meta (time) · badge (unread).
|
|
258
|
+
* Item state: idle | unread | selected.
|
|
259
|
+
*/
|
|
260
|
+
export const conversationAnatomy = anatomy('conversation', [
|
|
261
|
+
'root',
|
|
262
|
+
'item',
|
|
263
|
+
'leading',
|
|
264
|
+
'body',
|
|
265
|
+
'title',
|
|
266
|
+
'preview',
|
|
267
|
+
'meta',
|
|
268
|
+
'badge',
|
|
269
|
+
] as const)
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Thread — message stream. Items project mine | theirs | pending | failed.
|
|
273
|
+
*/
|
|
274
|
+
export const threadAnatomy = anatomy('thread', [
|
|
275
|
+
'root',
|
|
276
|
+
'list',
|
|
277
|
+
'item',
|
|
278
|
+
'bubble',
|
|
279
|
+
'meta',
|
|
280
|
+
'day',
|
|
281
|
+
] as const)
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Composer — bottom compose chrome for threads (input + send).
|
|
285
|
+
* Root state: idle | sending | disabled | error.
|
|
286
|
+
*/
|
|
287
|
+
export const composerAnatomy = anatomy('composer', [
|
|
288
|
+
'root',
|
|
289
|
+
'input',
|
|
290
|
+
'actions',
|
|
291
|
+
'send',
|
|
292
|
+
] as const)
|
|
145
293
|
|
|
146
294
|
export const separatorAnatomy = anatomy('separator', ['root'] as const)
|
|
147
295
|
|
|
@@ -165,3 +313,84 @@ export const sliderAnatomy = anatomy('slider', [
|
|
|
165
313
|
] as const)
|
|
166
314
|
|
|
167
315
|
export const pinInputAnatomy = anatomy('pinInput', ['root', 'input'] as const)
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Bar — horizontal layout primitive for toolbars, headers, footers.
|
|
319
|
+
* Enforces safe flex behavior: no overlap, responsive collapse via overflow.
|
|
320
|
+
* Parts: root (flex row), leading, center, trailing, optional seam (sticky stack join).
|
|
321
|
+
* Drive sticky lifecycle with stickyBarMachine → data-aeon-state on root.
|
|
322
|
+
*/
|
|
323
|
+
export const barAnatomy = anatomy('bar', [
|
|
324
|
+
'root',
|
|
325
|
+
'leading',
|
|
326
|
+
'center',
|
|
327
|
+
'trailing',
|
|
328
|
+
'seam',
|
|
329
|
+
] as const)
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Panel — collapsible layout region for split views.
|
|
333
|
+
* Group lays out siblings; Root is expanded|collapsed; Label rails vertically when collapsed.
|
|
334
|
+
*/
|
|
335
|
+
export const panelAnatomy = anatomy('panel', [
|
|
336
|
+
'group',
|
|
337
|
+
'root',
|
|
338
|
+
'trigger',
|
|
339
|
+
'label',
|
|
340
|
+
'content',
|
|
341
|
+
] as const)
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* App shell — layered application chrome (items-market MainLayout pattern).
|
|
345
|
+
* header / subheader / content / aside / footer / dock / scrim under root.
|
|
346
|
+
*/
|
|
347
|
+
export const appShellAnatomy = anatomy('appShell', [
|
|
348
|
+
'root',
|
|
349
|
+
'header',
|
|
350
|
+
'subheader',
|
|
351
|
+
'content',
|
|
352
|
+
'aside',
|
|
353
|
+
'footer',
|
|
354
|
+
'dock',
|
|
355
|
+
'scrim',
|
|
356
|
+
] as const)
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Content region — status-slotted body. Every contentRegionMachine state has a part.
|
|
360
|
+
* Pair with Async for fetch; use slots for idle/pending/empty/error/ready/loadingMore/success.
|
|
361
|
+
*/
|
|
362
|
+
export const contentAnatomy = anatomy('content', [
|
|
363
|
+
'root',
|
|
364
|
+
'toolbar',
|
|
365
|
+
'body',
|
|
366
|
+
'pending',
|
|
367
|
+
'empty',
|
|
368
|
+
'error',
|
|
369
|
+
'success',
|
|
370
|
+
'sentinel',
|
|
371
|
+
] as const)
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Nav — navigational collection (top links, bottom tabs, side rail).
|
|
375
|
+
* Selection reuses tabsMachine; items project inactive|active|disabled.
|
|
376
|
+
*/
|
|
377
|
+
export const navAnatomy = anatomy('nav', [
|
|
378
|
+
'root',
|
|
379
|
+
'item',
|
|
380
|
+
'indicator',
|
|
381
|
+
'label',
|
|
382
|
+
'icon',
|
|
383
|
+
'badge',
|
|
384
|
+
] as const)
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* ThemeSwitcher — mode toggle (light/dark) + theme preset selector.
|
|
388
|
+
* Parts: root, modes (button group), modeBtn, themeSelect, themeTrigger.
|
|
389
|
+
*/
|
|
390
|
+
export const themeSwitcherAnatomy = anatomy('themeSwitcher', [
|
|
391
|
+
'root',
|
|
392
|
+
'modes',
|
|
393
|
+
'modeBtn',
|
|
394
|
+
'themeSelect',
|
|
395
|
+
'themeTrigger',
|
|
396
|
+
] as const)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brand → Aeon CSS variable bridge.
|
|
3
|
+
*
|
|
4
|
+
* Product apps often already have design tokens. Instead of maintaining a parallel
|
|
5
|
+
* `--product-*` layer forever, map them onto Aeon’s runtime vars so `@aeon-ui/ui`
|
|
6
|
+
* recipes and `aeon.css` pick up the brand without forking Panda.
|
|
7
|
+
*/
|
|
8
|
+
import type { AeonColorMode } from './theme.js'
|
|
9
|
+
|
|
10
|
+
/** Minimal brand fields — anything omitted gets a safe default. */
|
|
11
|
+
export type BrandPaletteInput = {
|
|
12
|
+
bg: string
|
|
13
|
+
surface: string
|
|
14
|
+
surfaceRaised?: string
|
|
15
|
+
border: string
|
|
16
|
+
text: string
|
|
17
|
+
muted: string
|
|
18
|
+
accent: string
|
|
19
|
+
accentDim?: string
|
|
20
|
+
danger?: string
|
|
21
|
+
dangerDim?: string
|
|
22
|
+
gridLine?: string
|
|
23
|
+
/** Optional product fonts (applied as --aeon-font / --aeon-font-display). */
|
|
24
|
+
font?: string
|
|
25
|
+
fontDisplay?: string
|
|
26
|
+
radius?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type BrandPalette = Required<
|
|
30
|
+
Pick<
|
|
31
|
+
BrandPaletteInput,
|
|
32
|
+
| 'bg'
|
|
33
|
+
| 'surface'
|
|
34
|
+
| 'surfaceRaised'
|
|
35
|
+
| 'border'
|
|
36
|
+
| 'text'
|
|
37
|
+
| 'muted'
|
|
38
|
+
| 'accent'
|
|
39
|
+
| 'accentDim'
|
|
40
|
+
| 'danger'
|
|
41
|
+
| 'dangerDim'
|
|
42
|
+
| 'gridLine'
|
|
43
|
+
>
|
|
44
|
+
> & {
|
|
45
|
+
font?: string
|
|
46
|
+
fontDisplay?: string
|
|
47
|
+
radius?: string
|
|
48
|
+
focusRing: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const DEFAULT_DANGER = { light: '#dc2626', dark: '#f87171' } as const
|
|
52
|
+
|
|
53
|
+
function focusRing(bg: string, accent: string) {
|
|
54
|
+
return `0 0 0 2px ${bg}, 0 0 0 4px ${accent}`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Normalize a partial brand palette into a full Aeon-compatible palette. */
|
|
58
|
+
export function normalizeBrandPalette(
|
|
59
|
+
input: BrandPaletteInput,
|
|
60
|
+
mode: AeonColorMode = 'dark',
|
|
61
|
+
): BrandPalette {
|
|
62
|
+
const danger = input.danger ?? DEFAULT_DANGER[mode]
|
|
63
|
+
return {
|
|
64
|
+
bg: input.bg,
|
|
65
|
+
surface: input.surface,
|
|
66
|
+
surfaceRaised: input.surfaceRaised ?? input.surface,
|
|
67
|
+
border: input.border,
|
|
68
|
+
text: input.text,
|
|
69
|
+
muted: input.muted,
|
|
70
|
+
accent: input.accent,
|
|
71
|
+
accentDim: input.accentDim ?? `${input.accent}2e`,
|
|
72
|
+
danger,
|
|
73
|
+
dangerDim: input.dangerDim ?? `${danger}18`,
|
|
74
|
+
gridLine:
|
|
75
|
+
input.gridLine ??
|
|
76
|
+
(mode === 'light' ? 'rgba(15, 23, 42, 0.07)' : 'rgba(255, 255, 255, 0.05)'),
|
|
77
|
+
font: input.font,
|
|
78
|
+
fontDisplay: input.fontDisplay,
|
|
79
|
+
radius: input.radius,
|
|
80
|
+
focusRing: focusRing(input.bg, input.accent),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** CSS custom properties Aeon recipes already read. */
|
|
85
|
+
export function brandPaletteToCssVars(palette: BrandPalette): Record<string, string> {
|
|
86
|
+
const vars: Record<string, string> = {
|
|
87
|
+
'--colors-aeon-bg': palette.bg,
|
|
88
|
+
'--colors-aeon-surface': palette.surface,
|
|
89
|
+
'--colors-aeon-surface-raised': palette.surfaceRaised,
|
|
90
|
+
'--colors-aeon-border': palette.border,
|
|
91
|
+
'--colors-aeon-text': palette.text,
|
|
92
|
+
'--colors-aeon-muted': palette.muted,
|
|
93
|
+
'--colors-aeon-accent': palette.accent,
|
|
94
|
+
'--colors-aeon-accent-dim': palette.accentDim,
|
|
95
|
+
'--colors-aeon-danger': palette.danger,
|
|
96
|
+
'--colors-aeon-danger-dim': palette.dangerDim,
|
|
97
|
+
'--shadows-focus-ring': palette.focusRing,
|
|
98
|
+
'--aeon-grid-line': palette.gridLine,
|
|
99
|
+
}
|
|
100
|
+
if (palette.font) vars['--aeon-font'] = palette.font
|
|
101
|
+
if (palette.fontDisplay) vars['--aeon-font-display'] = palette.fontDisplay
|
|
102
|
+
if (palette.radius) vars['--aeon-radius'] = palette.radius
|
|
103
|
+
return vars
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type ApplyBrandPaletteOptions = {
|
|
107
|
+
mode?: AeonColorMode
|
|
108
|
+
/** Theme id stamped on <html> (default: `brand`). */
|
|
109
|
+
themeId?: string
|
|
110
|
+
/** Persist mode/theme to localStorage (default: false — brand apps usually own prefs). */
|
|
111
|
+
persist?: boolean
|
|
112
|
+
root?: HTMLElement
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Apply a product brand palette to the document so Aeon CSS vars match the app.
|
|
117
|
+
* Does not require registering a catalog theme — one call at boot is enough.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* applyBrandPalette({
|
|
121
|
+
* bg: '#000', surface: '#0a0a0a', border: '#262626',
|
|
122
|
+
* text: '#fafafa', muted: '#a1a1aa', accent: '#57ff97',
|
|
123
|
+
* }, { mode: 'dark' })
|
|
124
|
+
*/
|
|
125
|
+
export function applyBrandPalette(
|
|
126
|
+
input: BrandPaletteInput,
|
|
127
|
+
options: ApplyBrandPaletteOptions = {},
|
|
128
|
+
): BrandPalette {
|
|
129
|
+
const mode = options.mode ?? 'dark'
|
|
130
|
+
const palette = normalizeBrandPalette(input, mode)
|
|
131
|
+
if (typeof document === 'undefined') return palette
|
|
132
|
+
|
|
133
|
+
const root = options.root ?? document.documentElement
|
|
134
|
+
const themeId = options.themeId ?? 'brand'
|
|
135
|
+
root.dataset.aeonTheme = themeId
|
|
136
|
+
root.dataset.aeonMode = mode
|
|
137
|
+
root.style.colorScheme = mode
|
|
138
|
+
|
|
139
|
+
const vars = brandPaletteToCssVars(palette)
|
|
140
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
141
|
+
root.style.setProperty(key, value)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const meta = document.querySelector('meta[name="theme-color"]')
|
|
145
|
+
if (meta) meta.setAttribute('content', palette.bg)
|
|
146
|
+
|
|
147
|
+
if (options.persist) {
|
|
148
|
+
try {
|
|
149
|
+
localStorage.setItem('aeon-theme', themeId)
|
|
150
|
+
localStorage.setItem('aeon-mode', mode)
|
|
151
|
+
} catch {
|
|
152
|
+
/* private mode */
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return palette
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Serialize palette vars for injecting a `<style>` tag (SSR / non-DOM). */
|
|
160
|
+
export function brandPaletteToCssText(
|
|
161
|
+
input: BrandPaletteInput,
|
|
162
|
+
options: { mode?: AeonColorMode; selector?: string } = {},
|
|
163
|
+
): string {
|
|
164
|
+
const mode = options.mode ?? 'dark'
|
|
165
|
+
const palette = normalizeBrandPalette(input, mode)
|
|
166
|
+
const vars = brandPaletteToCssVars(palette)
|
|
167
|
+
const body = Object.entries(vars)
|
|
168
|
+
.map(([k, v]) => ` ${k}: ${v};`)
|
|
169
|
+
.join('\n')
|
|
170
|
+
const selector = options.selector ?? 'html'
|
|
171
|
+
return `${selector} {\n color-scheme: ${mode};\n${body}\n}`
|
|
172
|
+
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
fieldFace,
|
|
4
|
+
fieldRegions,
|
|
5
|
+
getRegionValue,
|
|
6
|
+
regionLeaf,
|
|
7
|
+
regionMatches,
|
|
8
|
+
regionPath,
|
|
9
|
+
stateToAttr,
|
|
10
|
+
} from './machine.js'
|
|
3
11
|
|
|
4
12
|
describe('stateToAttr', () => {
|
|
5
13
|
it('stringifies string states', () => {
|
|
@@ -16,3 +24,56 @@ describe('stateToAttr', () => {
|
|
|
16
24
|
).toBe('interaction:dirty validation:invalid submission:idle')
|
|
17
25
|
})
|
|
18
26
|
})
|
|
27
|
+
|
|
28
|
+
describe('fieldFace', () => {
|
|
29
|
+
it('projects a single leaf face (pending > invalid > dirty > idle)', () => {
|
|
30
|
+
expect(fieldFace({ interaction: 'pristine', validation: 'valid', submission: 'idle' })).toBe(
|
|
31
|
+
'idle',
|
|
32
|
+
)
|
|
33
|
+
expect(fieldFace({ interaction: 'dirty', validation: 'valid', submission: 'idle' })).toBe(
|
|
34
|
+
'dirty',
|
|
35
|
+
)
|
|
36
|
+
expect(fieldFace({ interaction: 'dirty', validation: 'invalid', submission: 'idle' })).toBe(
|
|
37
|
+
'invalid',
|
|
38
|
+
)
|
|
39
|
+
expect(fieldFace({ interaction: 'dirty', validation: 'valid', submission: 'pending' })).toBe(
|
|
40
|
+
'pending',
|
|
41
|
+
)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('exposes orthogonal regions separately', () => {
|
|
45
|
+
expect(
|
|
46
|
+
fieldRegions({ interaction: 'dirty', validation: 'invalid', submission: 'idle' }),
|
|
47
|
+
).toEqual({
|
|
48
|
+
interaction: 'dirty',
|
|
49
|
+
validation: 'invalid',
|
|
50
|
+
submission: 'idle',
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('region helpers', () => {
|
|
56
|
+
const value = {
|
|
57
|
+
session: { authenticating: 'verifyCode' },
|
|
58
|
+
route: 'discover',
|
|
59
|
+
deck: { ready: 'matched' },
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
it('reads a region value', () => {
|
|
63
|
+
expect(getRegionValue(value, 'route')).toBe('discover')
|
|
64
|
+
expect(getRegionValue(value, 'deck')).toEqual({ ready: 'matched' })
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('walks compound paths to a leaf', () => {
|
|
68
|
+
expect(regionPath(value, 'deck')).toEqual(['ready', 'matched'])
|
|
69
|
+
expect(regionLeaf(value, 'deck')).toBe('matched')
|
|
70
|
+
expect(regionLeaf(value, 'session')).toBe('verifyCode')
|
|
71
|
+
expect(regionLeaf(value, 'route')).toBe('discover')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('matches leaves and path prefixes', () => {
|
|
75
|
+
expect(regionMatches(value, 'deck', 'matched')).toBe(true)
|
|
76
|
+
expect(regionMatches(value, 'deck', ['ready', 'matched'])).toBe(true)
|
|
77
|
+
expect(regionMatches(value, 'deck', 'loading')).toBe(false)
|
|
78
|
+
})
|
|
79
|
+
})
|
|
@@ -13,3 +13,96 @@ export function stateToAttr(value: unknown): string {
|
|
|
13
13
|
.map(([k, v]) => `${k}:${stateToAttr(v)}`)
|
|
14
14
|
.join(' ')
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Primary visual face for parallel field charts — same hierarchy as Button/Dialog:
|
|
19
|
+
* one leaf on `data-aeon-state`, not a region dump.
|
|
20
|
+
* Priority: pending > invalid > dirty > idle.
|
|
21
|
+
*/
|
|
22
|
+
export type FieldFace = 'idle' | 'dirty' | 'invalid' | 'pending'
|
|
23
|
+
|
|
24
|
+
export function fieldFace(value: unknown): FieldFace {
|
|
25
|
+
if (value == null || typeof value !== 'object') return 'idle'
|
|
26
|
+
const v = value as Record<string, unknown>
|
|
27
|
+
if (v.submission === 'pending') return 'pending'
|
|
28
|
+
if (v.validation === 'invalid') return 'invalid'
|
|
29
|
+
if (v.interaction === 'dirty') return 'dirty'
|
|
30
|
+
return 'idle'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Orthogonal field regions for advanced CSS (`data-aeon-interaction`, etc.). */
|
|
34
|
+
export function fieldRegions(value: unknown): {
|
|
35
|
+
interaction: string
|
|
36
|
+
validation: string
|
|
37
|
+
submission: string
|
|
38
|
+
} {
|
|
39
|
+
if (value == null || typeof value !== 'object') {
|
|
40
|
+
return { interaction: 'pristine', validation: 'valid', submission: 'idle' }
|
|
41
|
+
}
|
|
42
|
+
const v = value as Record<string, unknown>
|
|
43
|
+
return {
|
|
44
|
+
interaction: typeof v.interaction === 'string' ? v.interaction : 'pristine',
|
|
45
|
+
validation: typeof v.validation === 'string' ? v.validation : 'valid',
|
|
46
|
+
submission: typeof v.submission === 'string' ? v.submission : 'idle',
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Read one region from a (possibly parallel) snapshot value. */
|
|
51
|
+
export function getRegionValue(value: unknown, region: string): unknown {
|
|
52
|
+
if (value == null) return undefined
|
|
53
|
+
if (typeof value === 'string') return value === region ? value : undefined
|
|
54
|
+
if (typeof value === 'object') {
|
|
55
|
+
return (value as Record<string, unknown>)[region]
|
|
56
|
+
}
|
|
57
|
+
return undefined
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Active path under a region — e.g. `{ deck: { ready: 'matched' } }` →
|
|
62
|
+
* `regionPath(v, 'deck')` → `['ready', 'matched']`.
|
|
63
|
+
*/
|
|
64
|
+
export function regionPath(value: unknown, region: string): string[] {
|
|
65
|
+
const path: string[] = []
|
|
66
|
+
let cur: unknown = getRegionValue(value, region)
|
|
67
|
+
while (cur != null) {
|
|
68
|
+
if (typeof cur === 'string') {
|
|
69
|
+
path.push(cur)
|
|
70
|
+
break
|
|
71
|
+
}
|
|
72
|
+
if (typeof cur !== 'object') break
|
|
73
|
+
const entries = Object.entries(cur as Record<string, unknown>)
|
|
74
|
+
if (entries.length === 0) break
|
|
75
|
+
if (entries.length > 1) {
|
|
76
|
+
path.push(entries.map(([k]) => k).sort().join('+'))
|
|
77
|
+
break
|
|
78
|
+
}
|
|
79
|
+
const [key, next] = entries[0]!
|
|
80
|
+
path.push(key)
|
|
81
|
+
cur = next
|
|
82
|
+
}
|
|
83
|
+
return path
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Deepest active key under a region (`['ready','matched']` → `'matched'`). */
|
|
87
|
+
export function regionLeaf(value: unknown, region: string): string | undefined {
|
|
88
|
+
const path = regionPath(value, region)
|
|
89
|
+
return path[path.length - 1]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** True when the region path equals or ends with the given leaf / path prefix. */
|
|
93
|
+
export function regionMatches(
|
|
94
|
+
value: unknown,
|
|
95
|
+
region: string,
|
|
96
|
+
match: string | readonly string[],
|
|
97
|
+
): boolean {
|
|
98
|
+
const path = regionPath(value, region)
|
|
99
|
+
if (path.length === 0) return false
|
|
100
|
+
if (typeof match === 'string') {
|
|
101
|
+
return path.includes(match) || path[path.length - 1] === match
|
|
102
|
+
}
|
|
103
|
+
if (match.length === 0) return false
|
|
104
|
+
if (match.length <= path.length) {
|
|
105
|
+
return match.every((part, i) => path[i] === part)
|
|
106
|
+
}
|
|
107
|
+
return false
|
|
108
|
+
}
|