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.
Files changed (365) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/CONSUMER.md +82 -40
  3. package/README.md +47 -16
  4. package/package.json +9 -2
  5. package/packages/cli/README.md +17 -0
  6. package/packages/cli/dist/bin.d.ts +1 -0
  7. package/packages/cli/dist/bin.js +35 -0
  8. package/packages/cli/dist/chunk-IW5NT2Z2.js +103 -0
  9. package/packages/cli/dist/index.d.ts +10 -0
  10. package/packages/cli/dist/index.js +6 -0
  11. package/packages/cli/node_modules/.bin/esbuild +21 -0
  12. package/packages/cli/node_modules/.bin/jiti +21 -0
  13. package/packages/cli/node_modules/.bin/tsc +21 -0
  14. package/packages/cli/node_modules/.bin/tsserver +21 -0
  15. package/packages/cli/node_modules/.bin/tsup +21 -0
  16. package/packages/cli/node_modules/.bin/tsup-node +21 -0
  17. package/packages/cli/node_modules/.bin/tsx +21 -0
  18. package/packages/cli/node_modules/.bin/vite +21 -0
  19. package/packages/cli/node_modules/.bin/vitest +21 -0
  20. package/packages/cli/package.json +50 -0
  21. package/packages/cli/src/bin.ts +34 -0
  22. package/packages/cli/src/index.ts +1 -0
  23. package/packages/cli/src/init.ts +107 -0
  24. package/packages/cli/tsconfig.json +10 -0
  25. package/packages/core/README.md +7 -1
  26. package/packages/core/dist/index.d.ts +255 -7
  27. package/packages/core/dist/index.js +372 -2
  28. package/packages/core/node_modules/.bin/esbuild +4 -4
  29. package/packages/core/node_modules/.bin/vite +4 -4
  30. package/packages/core/node_modules/.bin/vitest +2 -2
  31. package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  32. package/packages/core/package.json +17 -5
  33. package/packages/core/src/anatomy.ts +234 -5
  34. package/packages/core/src/brand-theme.ts +172 -0
  35. package/packages/core/src/index.ts +1 -0
  36. package/packages/core/src/machine.test.ts +62 -1
  37. package/packages/core/src/machine.ts +93 -0
  38. package/packages/core/src/theme.ts +105 -0
  39. package/packages/panda/aeon.css +8 -0
  40. package/packages/panda/package.json +1 -1
  41. package/packages/panda/panda.config.ts +58 -1
  42. package/packages/panda/src/recipes/app-shell.ts +137 -0
  43. package/packages/panda/src/recipes/avatar.ts +131 -0
  44. package/packages/panda/src/recipes/bar.ts +206 -0
  45. package/packages/panda/src/recipes/button-group.ts +12 -4
  46. package/packages/panda/src/recipes/button.ts +1 -0
  47. package/packages/panda/src/recipes/combobox.ts +2 -1
  48. package/packages/panda/src/recipes/composer.ts +81 -0
  49. package/packages/panda/src/recipes/content.ts +104 -0
  50. package/packages/panda/src/recipes/conversation.ts +106 -0
  51. package/packages/panda/src/recipes/entry.ts +217 -0
  52. package/packages/panda/src/recipes/field.ts +12 -2
  53. package/packages/panda/src/recipes/floating.ts +26 -13
  54. package/packages/panda/src/recipes/identity.ts +73 -0
  55. package/packages/panda/src/recipes/index.ts +15 -0
  56. package/packages/panda/src/recipes/list-row.ts +109 -0
  57. package/packages/panda/src/recipes/metric-strip.ts +80 -0
  58. package/packages/panda/src/recipes/nav.ts +155 -0
  59. package/packages/panda/src/recipes/panel.ts +156 -0
  60. package/packages/panda/src/recipes/pin-input.ts +3 -2
  61. package/packages/panda/src/recipes/profile-header.ts +120 -0
  62. package/packages/panda/src/recipes/select.ts +2 -1
  63. package/packages/panda/src/recipes/slider.ts +6 -4
  64. package/packages/panda/src/recipes/theme-switcher.ts +110 -0
  65. package/packages/panda/src/recipes/thread.ts +88 -0
  66. package/packages/panda/src/theme/catalog-types.ts +2 -0
  67. package/packages/panda/src/theme/catalog.ts +23 -0
  68. package/packages/panda/src/theme/chrome.css +2 -1
  69. package/packages/panda/src/theme/consumer-shell.css +129 -0
  70. package/packages/panda/src/theme/electron.css +30 -0
  71. package/packages/panda/src/theme/global-theme-css.ts +9 -0
  72. package/packages/panda/src/theme/omarchy.themes.ts +607 -0
  73. package/packages/panda/src/theme/palette.ts +3 -0
  74. package/packages/panda/src/theme/scrollbars.css +58 -21
  75. package/packages/panda/src/theme/themes.config.ts +39 -1
  76. package/packages/panda/styled-system/css/conditions.mjs +1 -1
  77. package/packages/panda/styled-system/css/css.mjs +1 -1
  78. package/packages/panda/styled-system/css/sva.mjs +9 -4
  79. package/packages/panda/styled-system/helpers.mjs +11 -22
  80. package/packages/panda/styled-system/jsx/create-style-context.d.ts +54 -0
  81. package/packages/panda/styled-system/jsx/create-style-context.mjs +121 -0
  82. package/packages/panda/styled-system/jsx/factory.mjs +14 -5
  83. package/packages/panda/styled-system/jsx/index.d.ts +1 -0
  84. package/packages/panda/styled-system/jsx/index.mjs +1 -0
  85. package/packages/panda/styled-system/jsx/is-valid-prop.mjs +2 -2
  86. package/packages/panda/styled-system/patterns/aspect-ratio.d.ts +0 -1
  87. package/packages/panda/styled-system/patterns/bleed.d.ts +0 -1
  88. package/packages/panda/styled-system/patterns/box.d.ts +0 -1
  89. package/packages/panda/styled-system/patterns/center.d.ts +0 -1
  90. package/packages/panda/styled-system/patterns/circle.d.ts +0 -1
  91. package/packages/panda/styled-system/patterns/container.d.ts +0 -1
  92. package/packages/panda/styled-system/patterns/cq.d.ts +0 -1
  93. package/packages/panda/styled-system/patterns/divider.d.ts +0 -1
  94. package/packages/panda/styled-system/patterns/flex.d.ts +0 -1
  95. package/packages/panda/styled-system/patterns/float.d.ts +0 -1
  96. package/packages/panda/styled-system/patterns/grid-item.d.ts +0 -1
  97. package/packages/panda/styled-system/patterns/grid.d.ts +0 -1
  98. package/packages/panda/styled-system/patterns/grid.mjs +1 -1
  99. package/packages/panda/styled-system/patterns/hstack.d.ts +0 -1
  100. package/packages/panda/styled-system/patterns/hstack.mjs +1 -1
  101. package/packages/panda/styled-system/patterns/link-overlay.d.ts +0 -1
  102. package/packages/panda/styled-system/patterns/spacer.d.ts +0 -1
  103. package/packages/panda/styled-system/patterns/spacer.mjs +6 -2
  104. package/packages/panda/styled-system/patterns/square.d.ts +0 -1
  105. package/packages/panda/styled-system/patterns/stack.d.ts +0 -1
  106. package/packages/panda/styled-system/patterns/stack.mjs +1 -1
  107. package/packages/panda/styled-system/patterns/visually-hidden.d.ts +0 -1
  108. package/packages/panda/styled-system/patterns/vstack.d.ts +0 -1
  109. package/packages/panda/styled-system/patterns/vstack.mjs +1 -1
  110. package/packages/panda/styled-system/patterns/wrap.d.ts +0 -1
  111. package/packages/panda/styled-system/patterns/wrap.mjs +1 -1
  112. package/packages/panda/styled-system/recipes/aeon-accordion.d.ts +4 -3
  113. package/packages/panda/styled-system/recipes/aeon-accordion.mjs +1 -0
  114. package/packages/panda/styled-system/recipes/aeon-app-shell.d.ts +34 -0
  115. package/packages/panda/styled-system/recipes/aeon-app-shell.mjs +70 -0
  116. package/packages/panda/styled-system/recipes/aeon-async.d.ts +4 -3
  117. package/packages/panda/styled-system/recipes/aeon-async.mjs +1 -0
  118. package/packages/panda/styled-system/recipes/aeon-avatar.d.ts +36 -0
  119. package/packages/panda/styled-system/recipes/aeon-avatar.mjs +57 -0
  120. package/packages/panda/styled-system/recipes/aeon-badge.d.ts +3 -0
  121. package/packages/panda/styled-system/recipes/aeon-bar.d.ts +40 -0
  122. package/packages/panda/styled-system/recipes/aeon-bar.mjs +77 -0
  123. package/packages/panda/styled-system/recipes/aeon-button-group.d.ts +3 -2
  124. package/packages/panda/styled-system/recipes/aeon-button.d.ts +3 -2
  125. package/packages/panda/styled-system/recipes/aeon-checkbox.d.ts +4 -1
  126. package/packages/panda/styled-system/recipes/aeon-checkbox.mjs +1 -0
  127. package/packages/panda/styled-system/recipes/aeon-combobox.d.ts +4 -3
  128. package/packages/panda/styled-system/recipes/aeon-combobox.mjs +1 -0
  129. package/packages/panda/styled-system/recipes/aeon-composer.d.ts +31 -0
  130. package/packages/panda/styled-system/recipes/aeon-composer.mjs +45 -0
  131. package/packages/panda/styled-system/recipes/aeon-content.d.ts +34 -0
  132. package/packages/panda/styled-system/recipes/aeon-content.mjs +70 -0
  133. package/packages/panda/styled-system/recipes/aeon-conversation.d.ts +31 -0
  134. package/packages/panda/styled-system/recipes/aeon-conversation.mjs +61 -0
  135. package/packages/panda/styled-system/recipes/aeon-dialog.d.ts +4 -1
  136. package/packages/panda/styled-system/recipes/aeon-dialog.mjs +1 -0
  137. package/packages/panda/styled-system/recipes/aeon-entry.d.ts +38 -0
  138. package/packages/panda/styled-system/recipes/aeon-entry.mjs +100 -0
  139. package/packages/panda/styled-system/recipes/aeon-field.d.ts +4 -3
  140. package/packages/panda/styled-system/recipes/aeon-field.mjs +1 -0
  141. package/packages/panda/styled-system/recipes/aeon-identity.d.ts +36 -0
  142. package/packages/panda/styled-system/recipes/aeon-identity.mjs +63 -0
  143. package/packages/panda/styled-system/recipes/aeon-list-row.d.ts +33 -0
  144. package/packages/panda/styled-system/recipes/aeon-list-row.mjs +49 -0
  145. package/packages/panda/styled-system/recipes/aeon-menu.d.ts +4 -1
  146. package/packages/panda/styled-system/recipes/aeon-menu.mjs +1 -0
  147. package/packages/panda/styled-system/recipes/aeon-metric-strip.d.ts +36 -0
  148. package/packages/panda/styled-system/recipes/aeon-metric-strip.mjs +54 -0
  149. package/packages/panda/styled-system/recipes/aeon-nav.d.ts +38 -0
  150. package/packages/panda/styled-system/recipes/aeon-nav.mjs +69 -0
  151. package/packages/panda/styled-system/recipes/aeon-panel.d.ts +34 -0
  152. package/packages/panda/styled-system/recipes/aeon-panel.mjs +58 -0
  153. package/packages/panda/styled-system/recipes/aeon-pin-input.d.ts +4 -1
  154. package/packages/panda/styled-system/recipes/aeon-pin-input.mjs +1 -0
  155. package/packages/panda/styled-system/recipes/aeon-popover.d.ts +4 -1
  156. package/packages/panda/styled-system/recipes/aeon-popover.mjs +1 -0
  157. package/packages/panda/styled-system/recipes/aeon-profile-header.d.ts +36 -0
  158. package/packages/panda/styled-system/recipes/aeon-profile-header.mjs +62 -0
  159. package/packages/panda/styled-system/recipes/aeon-progress.d.ts +4 -1
  160. package/packages/panda/styled-system/recipes/aeon-progress.mjs +1 -0
  161. package/packages/panda/styled-system/recipes/aeon-radio-group.d.ts +4 -1
  162. package/packages/panda/styled-system/recipes/aeon-radio-group.mjs +1 -0
  163. package/packages/panda/styled-system/recipes/aeon-scroll.d.ts +4 -3
  164. package/packages/panda/styled-system/recipes/aeon-scroll.mjs +1 -0
  165. package/packages/panda/styled-system/recipes/aeon-select.d.ts +4 -3
  166. package/packages/panda/styled-system/recipes/aeon-select.mjs +1 -0
  167. package/packages/panda/styled-system/recipes/aeon-separator.d.ts +4 -1
  168. package/packages/panda/styled-system/recipes/aeon-separator.mjs +1 -0
  169. package/packages/panda/styled-system/recipes/aeon-slider.d.ts +4 -1
  170. package/packages/panda/styled-system/recipes/aeon-slider.mjs +1 -0
  171. package/packages/panda/styled-system/recipes/aeon-switch.d.ts +4 -1
  172. package/packages/panda/styled-system/recipes/aeon-switch.mjs +1 -0
  173. package/packages/panda/styled-system/recipes/aeon-tabs.d.ts +4 -1
  174. package/packages/panda/styled-system/recipes/aeon-tabs.mjs +1 -0
  175. package/packages/panda/styled-system/recipes/aeon-theme-switcher.d.ts +34 -0
  176. package/packages/panda/styled-system/recipes/aeon-theme-switcher.mjs +59 -0
  177. package/packages/panda/styled-system/recipes/aeon-thread.d.ts +31 -0
  178. package/packages/panda/styled-system/recipes/aeon-thread.mjs +53 -0
  179. package/packages/panda/styled-system/recipes/aeon-toast.d.ts +4 -1
  180. package/packages/panda/styled-system/recipes/aeon-toast.mjs +1 -0
  181. package/packages/panda/styled-system/recipes/aeon-tooltip.d.ts +4 -1
  182. package/packages/panda/styled-system/recipes/aeon-tooltip.mjs +1 -0
  183. package/packages/panda/styled-system/recipes/index.d.ts +16 -1
  184. package/packages/panda/styled-system/recipes/index.mjs +16 -1
  185. package/packages/panda/styled-system/styles.css +2317 -450
  186. package/packages/panda/styled-system/tokens/index.mjs +43 -7
  187. package/packages/panda/styled-system/tokens/tokens.d.ts +5 -5
  188. package/packages/panda/styled-system/types/composition.d.ts +86 -23
  189. package/packages/panda/styled-system/types/conditions.d.ts +34 -10
  190. package/packages/panda/styled-system/types/csstype.d.ts +6405 -5133
  191. package/packages/panda/styled-system/types/jsx.d.ts +27 -10
  192. package/packages/panda/styled-system/types/pattern.d.ts +4 -4
  193. package/packages/panda/styled-system/types/prop-type.d.ts +11 -1
  194. package/packages/panda/styled-system/types/style-props.d.ts +2763 -2165
  195. package/packages/panda/styled-system/types/system-types.d.ts +38 -80
  196. package/packages/panda/theme-runtime.css +945 -0
  197. package/packages/primitives/dist/chunk-4NOKRUSG.js +81 -0
  198. package/packages/primitives/dist/chunk-R25CUHNF.js +46 -0
  199. package/packages/primitives/dist/chunk-RO2DMFN5.js +89 -0
  200. package/packages/primitives/dist/chunk-V5D7LS4J.js +61 -0
  201. package/packages/primitives/dist/chunk-YBPNI2LI.js +70 -0
  202. package/packages/primitives/dist/index.d.ts +5 -0
  203. package/packages/primitives/dist/index.js +46 -14
  204. package/packages/primitives/dist/machines/app-nav.d.ts +96 -0
  205. package/packages/primitives/dist/machines/app-nav.js +12 -0
  206. package/packages/primitives/dist/machines/app-shell.d.ts +57 -0
  207. package/packages/primitives/dist/machines/app-shell.js +8 -0
  208. package/packages/primitives/dist/machines/async.d.ts +4 -4
  209. package/packages/primitives/dist/machines/content-region.d.ts +72 -0
  210. package/packages/primitives/dist/machines/content-region.js +8 -0
  211. package/packages/primitives/dist/machines/panel.d.ts +50 -0
  212. package/packages/primitives/dist/machines/panel.js +6 -0
  213. package/packages/primitives/dist/machines/sticky-bar.d.ts +62 -0
  214. package/packages/primitives/dist/machines/sticky-bar.js +8 -0
  215. package/packages/primitives/node_modules/.bin/esbuild +21 -0
  216. package/packages/primitives/node_modules/.bin/vite +21 -0
  217. package/packages/primitives/node_modules/.bin/vitest +2 -2
  218. package/packages/primitives/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  219. package/packages/primitives/package.json +15 -5
  220. package/packages/primitives/scripts/xstate-introspect.mjs +35 -0
  221. package/packages/primitives/src/__tests__/app-nav.test.ts +30 -0
  222. package/packages/primitives/src/index.ts +5 -0
  223. package/packages/primitives/src/machines/app-nav.ts +107 -0
  224. package/packages/primitives/src/machines/app-shell.ts +63 -0
  225. package/packages/primitives/src/machines/content-region.ts +107 -0
  226. package/packages/primitives/src/machines/panel.ts +71 -0
  227. package/packages/primitives/src/machines/sticky-bar.ts +89 -0
  228. package/packages/react/dist/index.d.ts +536 -17
  229. package/packages/react/dist/index.js +2163 -479
  230. package/packages/react/node_modules/.bin/esbuild +21 -0
  231. package/packages/react/node_modules/.bin/vite +21 -0
  232. package/packages/react/node_modules/.bin/vitest +2 -2
  233. package/packages/react/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  234. package/packages/react/package.json +18 -7
  235. package/packages/react/src/app-nav/index.tsx +249 -0
  236. package/packages/react/src/app-shell/index.tsx +154 -0
  237. package/packages/react/src/async/index.tsx +6 -1
  238. package/packages/react/src/async/map-content-state.ts +18 -0
  239. package/packages/react/src/avatar/index.tsx +23 -1
  240. package/packages/react/src/bar/index.tsx +101 -0
  241. package/packages/react/src/button/index.tsx +4 -2
  242. package/packages/react/src/composer/index.tsx +75 -0
  243. package/packages/react/src/content/index.tsx +149 -0
  244. package/packages/react/src/conversation/index.tsx +78 -0
  245. package/packages/react/src/entry/index.tsx +85 -0
  246. package/packages/react/src/field/index.tsx +31 -18
  247. package/packages/react/src/hooks/use-fit-text.ts +59 -0
  248. package/packages/react/src/identity/index.tsx +110 -0
  249. package/packages/react/src/index.ts +19 -0
  250. package/packages/react/src/list-row/index.tsx +80 -0
  251. package/packages/react/src/metric-strip/index.tsx +88 -0
  252. package/packages/react/src/nav/index.tsx +155 -0
  253. package/packages/react/src/panel/index.tsx +267 -0
  254. package/packages/react/src/profile-header/index.tsx +91 -0
  255. package/packages/react/src/prompt/index.tsx +161 -0
  256. package/packages/react/src/radio-group/index.tsx +19 -7
  257. package/packages/react/src/slider/index.tsx +3 -1
  258. package/packages/react/src/status-banner/index.tsx +84 -0
  259. package/packages/react/src/sticky-bar/index.tsx +135 -0
  260. package/packages/react/src/switch/index.tsx +7 -1
  261. package/packages/react/src/theme-switcher/index.tsx +227 -0
  262. package/packages/react/src/thread/index.tsx +95 -0
  263. package/packages/react/src/toast/primitives.tsx +2 -2
  264. package/packages/react/src/tooltip/tooltip.test.tsx +9 -1
  265. package/packages/react/tsconfig.json +2 -1
  266. package/packages/schemas/README.md +52 -0
  267. package/packages/schemas/dist/index.d.ts +287 -0
  268. package/packages/schemas/dist/index.js +701 -0
  269. package/packages/schemas/json/accordion.json +132 -0
  270. package/packages/schemas/json/async.json +161 -0
  271. package/packages/schemas/json/button.json +170 -0
  272. package/packages/schemas/json/catalog.json +2299 -0
  273. package/packages/schemas/json/checkbox.json +136 -0
  274. package/packages/schemas/json/combobox.json +130 -0
  275. package/packages/schemas/json/dialog.json +154 -0
  276. package/packages/schemas/json/field.json +171 -0
  277. package/packages/schemas/json/menu.json +138 -0
  278. package/packages/schemas/json/popover.json +133 -0
  279. package/packages/schemas/json/progress.json +137 -0
  280. package/packages/schemas/json/scroll.json +129 -0
  281. package/packages/schemas/json/select.json +140 -0
  282. package/packages/schemas/json/switch.json +136 -0
  283. package/packages/schemas/json/tabs.json +135 -0
  284. package/packages/schemas/json/toast.json +141 -0
  285. package/packages/schemas/json/tooltip.json +123 -0
  286. package/packages/schemas/node_modules/.bin/esbuild +21 -0
  287. package/packages/schemas/node_modules/.bin/jiti +21 -0
  288. package/packages/schemas/node_modules/.bin/tsc +21 -0
  289. package/packages/schemas/node_modules/.bin/tsserver +21 -0
  290. package/packages/schemas/node_modules/.bin/tsup +21 -0
  291. package/packages/schemas/node_modules/.bin/tsup-node +21 -0
  292. package/packages/schemas/node_modules/.bin/tsx +21 -0
  293. package/packages/schemas/node_modules/.bin/vite +21 -0
  294. package/packages/schemas/node_modules/.bin/vitest +21 -0
  295. package/packages/schemas/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  296. package/packages/schemas/package.json +56 -0
  297. package/packages/schemas/prompts/cursorrules.md +64 -0
  298. package/packages/schemas/prompts/statecharts.md +31 -0
  299. package/packages/schemas/prompts/system.md +30 -0
  300. package/packages/schemas/registry/catalog.json +54 -0
  301. package/packages/schemas/registry/components/accordion.json +132 -0
  302. package/packages/schemas/registry/components/async.json +161 -0
  303. package/packages/schemas/registry/components/button.json +170 -0
  304. package/packages/schemas/registry/components/checkbox.json +136 -0
  305. package/packages/schemas/registry/components/combobox.json +130 -0
  306. package/packages/schemas/registry/components/dialog.json +154 -0
  307. package/packages/schemas/registry/components/field.json +171 -0
  308. package/packages/schemas/registry/components/menu.json +138 -0
  309. package/packages/schemas/registry/components/popover.json +133 -0
  310. package/packages/schemas/registry/components/progress.json +137 -0
  311. package/packages/schemas/registry/components/scroll.json +129 -0
  312. package/packages/schemas/registry/components/select.json +140 -0
  313. package/packages/schemas/registry/components/switch.json +136 -0
  314. package/packages/schemas/registry/components/tabs.json +135 -0
  315. package/packages/schemas/registry/components/toast.json +141 -0
  316. package/packages/schemas/registry/components/tooltip.json +123 -0
  317. package/packages/schemas/registry/machines/async.json +219 -0
  318. package/packages/schemas/registry/machines/buttonLifecycle.json +224 -0
  319. package/packages/schemas/registry/machines/dialog.json +143 -0
  320. package/packages/schemas/registry/machines/field.json +275 -0
  321. package/packages/schemas/scripts/emit.ts +61 -0
  322. package/packages/schemas/src/aeon-to-schema.test.ts +70 -0
  323. package/packages/schemas/src/aeon-to-schema.ts +206 -0
  324. package/packages/schemas/src/agent-bridge.ts +82 -0
  325. package/packages/schemas/src/components.ts +307 -0
  326. package/packages/schemas/src/index.test.ts +25 -0
  327. package/packages/schemas/src/index.ts +25 -0
  328. package/packages/schemas/src/prompts.ts +121 -0
  329. package/packages/schemas/src/registry.ts +73 -0
  330. package/packages/schemas/src/schema-builder.ts +87 -0
  331. package/packages/schemas/tsconfig.json +9 -0
  332. package/packages/solid/package.json +2 -2
  333. package/packages/svelte/node_modules/.bin/esbuild +21 -0
  334. package/packages/svelte/node_modules/.bin/vite +4 -4
  335. package/packages/svelte/package.json +15 -5
  336. package/packages/ui/README.md +19 -6
  337. package/packages/ui/dist/index.d.ts +254 -9
  338. package/packages/ui/dist/index.js +690 -139
  339. package/packages/ui/src/app-nav.tsx +2 -0
  340. package/packages/ui/src/app-shell.tsx +44 -0
  341. package/packages/ui/src/async.tsx +2 -1
  342. package/packages/ui/src/avatar.tsx +35 -0
  343. package/packages/ui/src/bar.tsx +34 -0
  344. package/packages/ui/src/composer.tsx +27 -0
  345. package/packages/ui/src/content.tsx +43 -0
  346. package/packages/ui/src/conversation.tsx +43 -0
  347. package/packages/ui/src/entry.tsx +76 -0
  348. package/packages/ui/src/field.tsx +5 -0
  349. package/packages/ui/src/identity.tsx +35 -0
  350. package/packages/ui/src/index.ts +20 -0
  351. package/packages/ui/src/list-row.tsx +30 -0
  352. package/packages/ui/src/metric-strip.tsx +29 -0
  353. package/packages/ui/src/nav.tsx +46 -0
  354. package/packages/ui/src/panel.tsx +39 -0
  355. package/packages/ui/src/profile-header.tsx +35 -0
  356. package/packages/ui/src/prompt.tsx +29 -0
  357. package/packages/ui/src/status-banner.tsx +1 -0
  358. package/packages/ui/src/sticky-bar.tsx +42 -0
  359. package/packages/ui/src/switch.tsx +17 -4
  360. package/packages/ui/src/theme-switcher.tsx +41 -0
  361. package/packages/ui/src/thread.tsx +35 -0
  362. package/packages/vue/package.json +3 -3
  363. package/scripts/vite-aliases.mjs +65 -3
  364. package/scripts/xstate-introspect.mjs +68 -0
  365. 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
- * Play / game surface — mat shell with table, dice, and bank regions.
85
- * Use with normalized layout coords (@see docs/LAYOUT_COORDINATES.md).
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', ['mat', 'table', 'dice', 'bank'] as const)
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
+ }
@@ -6,3 +6,4 @@ export * from './types.js'
6
6
  export * from './scroll.js'
7
7
  export * from './layout.js'
8
8
  export * from './theme.js'
9
+ export * from './brand-theme.js'
@@ -1,5 +1,13 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import { stateToAttr } from './machine.js'
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
+ }