decantr 0.9.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 (382) hide show
  1. package/AGENTS.md +868 -0
  2. package/CHANGELOG.md +255 -0
  3. package/CLAUDE.md +178 -0
  4. package/LICENSE +21 -0
  5. package/README.md +229 -0
  6. package/cli/art.js +127 -0
  7. package/cli/commands/a11y.js +61 -0
  8. package/cli/commands/audit.js +225 -0
  9. package/cli/commands/build.js +38 -0
  10. package/cli/commands/dev.js +18 -0
  11. package/cli/commands/doctor.js +197 -0
  12. package/cli/commands/figma-sync.js +48 -0
  13. package/cli/commands/figma-tokens.js +55 -0
  14. package/cli/commands/generate.js +26 -0
  15. package/cli/commands/init.js +116 -0
  16. package/cli/commands/lint.js +209 -0
  17. package/cli/commands/mcp.js +530 -0
  18. package/cli/commands/migrate.js +175 -0
  19. package/cli/commands/test.js +38 -0
  20. package/cli/commands/validate.js +354 -0
  21. package/cli/index.js +113 -0
  22. package/package.json +95 -0
  23. package/reference/atoms.md +517 -0
  24. package/reference/behaviors.md +384 -0
  25. package/reference/build-tooling.md +275 -0
  26. package/reference/color-guidelines.md +965 -0
  27. package/reference/component-lifecycle.md +137 -0
  28. package/reference/compound-spacing.md +95 -0
  29. package/reference/decantation-process.md +499 -0
  30. package/reference/dev-server-routes.md +93 -0
  31. package/reference/form-system.md +253 -0
  32. package/reference/i18n.md +336 -0
  33. package/reference/icons.md +576 -0
  34. package/reference/llm-primer.md +953 -0
  35. package/reference/plugins.md +252 -0
  36. package/reference/registry-consumption.md +76 -0
  37. package/reference/router.md +217 -0
  38. package/reference/shells.md +116 -0
  39. package/reference/spatial-guidelines.md +541 -0
  40. package/reference/ssr.md +234 -0
  41. package/reference/state-data.md +215 -0
  42. package/reference/state-patterns.md +166 -0
  43. package/reference/state.md +194 -0
  44. package/reference/style-system.md +110 -0
  45. package/reference/tokens.md +460 -0
  46. package/src/app.js +19 -0
  47. package/src/chart/_animate.js +266 -0
  48. package/src/chart/_base.js +109 -0
  49. package/src/chart/_data.js +209 -0
  50. package/src/chart/_format.js +106 -0
  51. package/src/chart/_interact.js +364 -0
  52. package/src/chart/_palette.js +105 -0
  53. package/src/chart/_renderer.js +52 -0
  54. package/src/chart/_scene.js +262 -0
  55. package/src/chart/_shared.js +371 -0
  56. package/src/chart/index.js +637 -0
  57. package/src/chart/layouts/_layout-base.js +328 -0
  58. package/src/chart/layouts/cartesian.js +148 -0
  59. package/src/chart/layouts/hierarchy.js +562 -0
  60. package/src/chart/layouts/polar.js +101 -0
  61. package/src/chart/renderers/canvas.js +179 -0
  62. package/src/chart/renderers/svg.js +256 -0
  63. package/src/chart/renderers/webgpu.js +715 -0
  64. package/src/chart/types/_type-base.js +26 -0
  65. package/src/chart/types/area.js +134 -0
  66. package/src/chart/types/bar.js +173 -0
  67. package/src/chart/types/box-plot.js +125 -0
  68. package/src/chart/types/bubble.js +63 -0
  69. package/src/chart/types/candlestick.js +115 -0
  70. package/src/chart/types/chord.js +85 -0
  71. package/src/chart/types/combination.js +108 -0
  72. package/src/chart/types/funnel.js +68 -0
  73. package/src/chart/types/gauge.js +163 -0
  74. package/src/chart/types/heatmap.js +98 -0
  75. package/src/chart/types/histogram.js +71 -0
  76. package/src/chart/types/line.js +111 -0
  77. package/src/chart/types/org-chart.js +93 -0
  78. package/src/chart/types/pie.js +81 -0
  79. package/src/chart/types/radar.js +96 -0
  80. package/src/chart/types/radial.js +68 -0
  81. package/src/chart/types/range-area.js +55 -0
  82. package/src/chart/types/range-bar.js +61 -0
  83. package/src/chart/types/sankey.js +73 -0
  84. package/src/chart/types/scatter.js +66 -0
  85. package/src/chart/types/sparkline.js +81 -0
  86. package/src/chart/types/sunburst.js +69 -0
  87. package/src/chart/types/swimlane.js +88 -0
  88. package/src/chart/types/treemap.js +62 -0
  89. package/src/chart/types/waterfall.js +100 -0
  90. package/src/components/_base.js +1658 -0
  91. package/src/components/_behaviors.js +1140 -0
  92. package/src/components/_primitives.js +534 -0
  93. package/src/components/_qr-encoder.js +539 -0
  94. package/src/components/accordion.js +207 -0
  95. package/src/components/affix.js +62 -0
  96. package/src/components/alert-dialog.js +75 -0
  97. package/src/components/alert.js +47 -0
  98. package/src/components/aspect-ratio.js +24 -0
  99. package/src/components/avatar-group.js +55 -0
  100. package/src/components/avatar.js +38 -0
  101. package/src/components/back-top.js +75 -0
  102. package/src/components/badge.js +74 -0
  103. package/src/components/banner.js +68 -0
  104. package/src/components/breadcrumb.js +162 -0
  105. package/src/components/button.js +115 -0
  106. package/src/components/calendar.js +131 -0
  107. package/src/components/card.js +192 -0
  108. package/src/components/carousel.js +98 -0
  109. package/src/components/cascader.js +261 -0
  110. package/src/components/checkbox.js +80 -0
  111. package/src/components/chip.js +81 -0
  112. package/src/components/code-block.js +82 -0
  113. package/src/components/collapsible.js +50 -0
  114. package/src/components/color-palette.js +438 -0
  115. package/src/components/color-picker.js +314 -0
  116. package/src/components/combobox.js +181 -0
  117. package/src/components/command.js +174 -0
  118. package/src/components/comment.js +206 -0
  119. package/src/components/context-menu.js +76 -0
  120. package/src/components/data-table.js +724 -0
  121. package/src/components/date-picker.js +217 -0
  122. package/src/components/date-range-picker.js +244 -0
  123. package/src/components/datetime-picker.js +271 -0
  124. package/src/components/descriptions.js +68 -0
  125. package/src/components/drawer.js +179 -0
  126. package/src/components/dropdown.js +88 -0
  127. package/src/components/empty.js +41 -0
  128. package/src/components/float-button.js +90 -0
  129. package/src/components/form.js +106 -0
  130. package/src/components/hover-card.js +49 -0
  131. package/src/components/icon.js +87 -0
  132. package/src/components/image.js +97 -0
  133. package/src/components/index.js +117 -0
  134. package/src/components/input-group.js +75 -0
  135. package/src/components/input-number.js +155 -0
  136. package/src/components/input-otp.js +178 -0
  137. package/src/components/input.js +91 -0
  138. package/src/components/kbd.js +36 -0
  139. package/src/components/label.js +25 -0
  140. package/src/components/list.js +118 -0
  141. package/src/components/masked-input.js +236 -0
  142. package/src/components/mentions.js +165 -0
  143. package/src/components/menu.js +259 -0
  144. package/src/components/message.js +80 -0
  145. package/src/components/modal.js +147 -0
  146. package/src/components/navigation-menu.js +166 -0
  147. package/src/components/notification.js +84 -0
  148. package/src/components/pagination.js +104 -0
  149. package/src/components/placeholder.js +132 -0
  150. package/src/components/popconfirm.js +70 -0
  151. package/src/components/popover.js +58 -0
  152. package/src/components/progress.js +61 -0
  153. package/src/components/qrcode.js +251 -0
  154. package/src/components/radiogroup.js +120 -0
  155. package/src/components/range-slider.js +176 -0
  156. package/src/components/rate.js +186 -0
  157. package/src/components/resizable.js +83 -0
  158. package/src/components/result.js +57 -0
  159. package/src/components/scroll-area.js +43 -0
  160. package/src/components/segmented.js +97 -0
  161. package/src/components/select.js +165 -0
  162. package/src/components/separator.js +31 -0
  163. package/src/components/shell.js +407 -0
  164. package/src/components/skeleton.js +39 -0
  165. package/src/components/slider.js +141 -0
  166. package/src/components/sortable-list.js +176 -0
  167. package/src/components/space.js +42 -0
  168. package/src/components/spinner.js +112 -0
  169. package/src/components/splitter.js +147 -0
  170. package/src/components/statistic.js +136 -0
  171. package/src/components/steps.js +99 -0
  172. package/src/components/switch.js +95 -0
  173. package/src/components/table.js +44 -0
  174. package/src/components/tabs.js +216 -0
  175. package/src/components/tag.js +115 -0
  176. package/src/components/textarea.js +82 -0
  177. package/src/components/time-picker.js +153 -0
  178. package/src/components/time-range-picker.js +170 -0
  179. package/src/components/timeline.js +226 -0
  180. package/src/components/toast.js +71 -0
  181. package/src/components/toggle.js +213 -0
  182. package/src/components/tooltip.js +57 -0
  183. package/src/components/tour.js +159 -0
  184. package/src/components/transfer.js +163 -0
  185. package/src/components/tree-select.js +274 -0
  186. package/src/components/tree.js +141 -0
  187. package/src/components/typography.js +136 -0
  188. package/src/components/upload.js +118 -0
  189. package/src/components/visually-hidden.js +20 -0
  190. package/src/components/watermark.js +124 -0
  191. package/src/core/index.js +539 -0
  192. package/src/core/lifecycle.js +69 -0
  193. package/src/css/atoms.js +651 -0
  194. package/src/css/components.js +940 -0
  195. package/src/css/derive.js +1296 -0
  196. package/src/css/index.js +265 -0
  197. package/src/css/runtime.js +268 -0
  198. package/src/css/styles/addons/bioluminescent.js +93 -0
  199. package/src/css/styles/addons/clay.js +70 -0
  200. package/src/css/styles/addons/clean.js +57 -0
  201. package/src/css/styles/addons/command-center.js +143 -0
  202. package/src/css/styles/addons/dopamine.js +83 -0
  203. package/src/css/styles/addons/editorial.js +80 -0
  204. package/src/css/styles/addons/glassmorphism.js +99 -0
  205. package/src/css/styles/addons/liquid-glass.js +105 -0
  206. package/src/css/styles/addons/prismatic.js +100 -0
  207. package/src/css/styles/addons/retro.js +63 -0
  208. package/src/css/styles/auradecantism.js +96 -0
  209. package/src/css/theme-registry.js +444 -0
  210. package/src/data/entity.js +281 -0
  211. package/src/data/index.js +13 -0
  212. package/src/data/persist.js +225 -0
  213. package/src/data/query.js +839 -0
  214. package/src/data/realtime.js +299 -0
  215. package/src/data/url.js +177 -0
  216. package/src/data/worker.js +134 -0
  217. package/src/explorer/archetypes.js +243 -0
  218. package/src/explorer/atoms.js +228 -0
  219. package/src/explorer/charts.js +497 -0
  220. package/src/explorer/components.js +129 -0
  221. package/src/explorer/foundations.js +949 -0
  222. package/src/explorer/icons.js +178 -0
  223. package/src/explorer/patterns.js +247 -0
  224. package/src/explorer/recipes.js +194 -0
  225. package/src/explorer/shared/pattern-examples.js +1337 -0
  226. package/src/explorer/shared/showcase-renderer.js +958 -0
  227. package/src/explorer/shared/spec-table.js +41 -0
  228. package/src/explorer/shared/usage-links.js +87 -0
  229. package/src/explorer/shell-config.js +10 -0
  230. package/src/explorer/shells.js +551 -0
  231. package/src/explorer/styles.js +161 -0
  232. package/src/explorer/tokens.js +262 -0
  233. package/src/explorer/tools.js +525 -0
  234. package/src/form/index.js +804 -0
  235. package/src/i18n/index.js +251 -0
  236. package/src/icons/essential.js +479 -0
  237. package/src/icons/index.js +53 -0
  238. package/src/plugins/index.js +282 -0
  239. package/src/registry/archetypes/content-site.json +71 -0
  240. package/src/registry/archetypes/docs-explorer.json +23 -0
  241. package/src/registry/archetypes/ecommerce.json +104 -0
  242. package/src/registry/archetypes/financial-dashboard.json +77 -0
  243. package/src/registry/archetypes/index.json +41 -0
  244. package/src/registry/archetypes/portfolio.json +82 -0
  245. package/src/registry/archetypes/recipe-community.json +159 -0
  246. package/src/registry/archetypes/saas-dashboard.json +86 -0
  247. package/src/registry/architect/cross-cutting.json +45 -0
  248. package/src/registry/architect/domains/ecommerce.json +294 -0
  249. package/src/registry/architect/domains/financial-services.json +302 -0
  250. package/src/registry/architect/index.json +26 -0
  251. package/src/registry/architect/traits.json +379 -0
  252. package/src/registry/atoms.json +16 -0
  253. package/src/registry/chart-showcase.json +160 -0
  254. package/src/registry/chart.json +136 -0
  255. package/src/registry/components.json +8616 -0
  256. package/src/registry/core.json +216 -0
  257. package/src/registry/css.json +319 -0
  258. package/src/registry/data.json +135 -0
  259. package/src/registry/foundations.json +11 -0
  260. package/src/registry/icons.json +463 -0
  261. package/src/registry/index.json +101 -0
  262. package/src/registry/patterns/activity-feed.json +37 -0
  263. package/src/registry/patterns/article-content.json +27 -0
  264. package/src/registry/patterns/auth-form.json +37 -0
  265. package/src/registry/patterns/author-card.json +20 -0
  266. package/src/registry/patterns/card-grid.json +127 -0
  267. package/src/registry/patterns/category-nav.json +26 -0
  268. package/src/registry/patterns/chart-grid.json +36 -0
  269. package/src/registry/patterns/chat-interface.json +37 -0
  270. package/src/registry/patterns/checklist-card.json +55 -0
  271. package/src/registry/patterns/comparison-panel.json +27 -0
  272. package/src/registry/patterns/component-showcase.json +24 -0
  273. package/src/registry/patterns/contact-form.json +31 -0
  274. package/src/registry/patterns/cta-section.json +20 -0
  275. package/src/registry/patterns/data-table.json +37 -0
  276. package/src/registry/patterns/detail-header.json +83 -0
  277. package/src/registry/patterns/detail-panel.json +27 -0
  278. package/src/registry/patterns/explorer-shell.json +22 -0
  279. package/src/registry/patterns/filter-bar.json +33 -0
  280. package/src/registry/patterns/filter-sidebar.json +27 -0
  281. package/src/registry/patterns/form-sections.json +110 -0
  282. package/src/registry/patterns/goal-tracker.json +27 -0
  283. package/src/registry/patterns/hero.json +107 -0
  284. package/src/registry/patterns/index.json +47 -0
  285. package/src/registry/patterns/kpi-grid.json +36 -0
  286. package/src/registry/patterns/media-gallery.json +20 -0
  287. package/src/registry/patterns/order-history.json +20 -0
  288. package/src/registry/patterns/pagination.json +19 -0
  289. package/src/registry/patterns/photo-to-recipe.json +36 -0
  290. package/src/registry/patterns/pipeline-tracker.json +28 -0
  291. package/src/registry/patterns/post-list.json +27 -0
  292. package/src/registry/patterns/pricing-table.json +32 -0
  293. package/src/registry/patterns/scorecard.json +28 -0
  294. package/src/registry/patterns/search-bar.json +20 -0
  295. package/src/registry/patterns/specimen-grid.json +19 -0
  296. package/src/registry/patterns/stat-card.json +55 -0
  297. package/src/registry/patterns/stats-bar.json +55 -0
  298. package/src/registry/patterns/steps-card.json +55 -0
  299. package/src/registry/patterns/table-of-contents.json +19 -0
  300. package/src/registry/patterns/testimonials.json +21 -0
  301. package/src/registry/patterns/timeline.json +27 -0
  302. package/src/registry/patterns/token-inspector.json +21 -0
  303. package/src/registry/patterns/wizard.json +27 -0
  304. package/src/registry/recipe-auradecantism.json +69 -0
  305. package/src/registry/recipe-clean.json +65 -0
  306. package/src/registry/recipe-command-center.json +78 -0
  307. package/src/registry/router.json +73 -0
  308. package/src/registry/schema/README.md +197 -0
  309. package/src/registry/skeletons.json +259 -0
  310. package/src/registry/state.json +137 -0
  311. package/src/registry/tokens.json +40 -0
  312. package/src/router/hash.js +17 -0
  313. package/src/router/history.js +18 -0
  314. package/src/router/index.js +598 -0
  315. package/src/ssr/index.js +922 -0
  316. package/src/state/arrays.js +181 -0
  317. package/src/state/devtools.js +647 -0
  318. package/src/state/index.js +498 -0
  319. package/src/state/middleware.js +288 -0
  320. package/src/state/scheduler.js +206 -0
  321. package/src/state/store.js +300 -0
  322. package/src/tags/index.js +19 -0
  323. package/src/tannins/auth.js +396 -0
  324. package/src/test/dom.js +352 -0
  325. package/src/test/index.js +62 -0
  326. package/src/test/state.js +306 -0
  327. package/tools/a11y-audit.js +487 -0
  328. package/tools/analyzer.js +315 -0
  329. package/tools/audit.js +706 -0
  330. package/tools/builder.js +1422 -0
  331. package/tools/css-extract.js +188 -0
  332. package/tools/dev-server.js +316 -0
  333. package/tools/dts-gen.js +1260 -0
  334. package/tools/figma-components.js +329 -0
  335. package/tools/figma-patterns.js +516 -0
  336. package/tools/figma-plugin/code.js +453 -0
  337. package/tools/figma-plugin/manifest.json +14 -0
  338. package/tools/figma-plugin/ui.html +268 -0
  339. package/tools/figma-render.js +293 -0
  340. package/tools/figma-tokens.js +712 -0
  341. package/tools/figma-upload.js +318 -0
  342. package/tools/generate.js +738 -0
  343. package/tools/icons.js +133 -0
  344. package/tools/init-templates.js +265 -0
  345. package/tools/install-hooks.sh +5 -0
  346. package/tools/migrations/0.5.0.js +53 -0
  347. package/tools/migrations/0.6.0.js +95 -0
  348. package/tools/minify.js +170 -0
  349. package/tools/pre-commit +4 -0
  350. package/tools/registry.js +662 -0
  351. package/tools/reset-playground.js +61 -0
  352. package/tools/starter-templates/content-site/app.js +49 -0
  353. package/tools/starter-templates/content-site/essence.js +19 -0
  354. package/tools/starter-templates/content-site/pages.js +31 -0
  355. package/tools/starter-templates/ecommerce/app.js +50 -0
  356. package/tools/starter-templates/ecommerce/essence.js +19 -0
  357. package/tools/starter-templates/ecommerce/pages.js +31 -0
  358. package/tools/starter-templates/landing-page/app.js +38 -0
  359. package/tools/starter-templates/landing-page/essence.js +18 -0
  360. package/tools/starter-templates/landing-page/pages.js +21 -0
  361. package/tools/starter-templates/portfolio/app.js +45 -0
  362. package/tools/starter-templates/portfolio/essence.js +19 -0
  363. package/tools/starter-templates/portfolio/pages.js +33 -0
  364. package/tools/starter-templates/saas-dashboard/app.js +70 -0
  365. package/tools/starter-templates/saas-dashboard/essence.js +19 -0
  366. package/tools/starter-templates/saas-dashboard/pages.js +31 -0
  367. package/tools/verify-pack.js +203 -0
  368. package/types/chart.d.ts +77 -0
  369. package/types/components.d.ts +587 -0
  370. package/types/core.d.ts +89 -0
  371. package/types/css.d.ts +149 -0
  372. package/types/data.d.ts +238 -0
  373. package/types/form.d.ts +164 -0
  374. package/types/i18n.d.ts +51 -0
  375. package/types/icons.d.ts +27 -0
  376. package/types/index.d.ts +13 -0
  377. package/types/router.d.ts +116 -0
  378. package/types/ssr.d.ts +102 -0
  379. package/types/state.d.ts +83 -0
  380. package/types/tags.d.ts +62 -0
  381. package/types/tannins.d.ts +63 -0
  382. package/types/test.d.ts +48 -0
@@ -0,0 +1,288 @@
1
+ import { createSignal, createEffect, batch } from './index.js';
2
+
3
+ /**
4
+ * Apply a middleware chain to a signal [getter, setter] tuple.
5
+ * Each middleware factory returns `{ onGet?, onSet? }`.
6
+ * - `onGet(value)` — intercept reads, return transformed value.
7
+ * - `onSet(newValue, prevValue)` — intercept writes, return value to set
8
+ * or `undefined` to reject the write.
9
+ * Middlewares run in order: first middleware's onSet executes first (pipeline).
10
+ *
11
+ * @template T
12
+ * @param {[() => T, (v: T | ((prev: T) => T)) => void]} signal
13
+ * @param {Array<() => { onGet?: (v: T) => T, onSet?: (next: T, prev: T) => T | undefined }>} middlewares
14
+ * @param {{ name?: string }} [options]
15
+ * @returns {[() => T, (v: T | ((prev: T) => T)) => void]}
16
+ */
17
+ export function withMiddleware(signal, middlewares, options) {
18
+ const [rawGet, rawSet] = signal;
19
+ const chain = middlewares.map(m => m());
20
+
21
+ // Wire _attach hooks (used by undoMiddleware to get a reference to rawSet)
22
+ for (let i = 0; i < chain.length; i++) {
23
+ if (typeof chain[i]._attach === 'function') chain[i]._attach(rawSet);
24
+ }
25
+
26
+ // Hydrate from persistMiddleware if present
27
+ for (let i = 0; i < chain.length; i++) {
28
+ if (chain[i]._hasHydrated) rawSet(chain[i]._hydrated);
29
+ }
30
+
31
+ function getter() {
32
+ let value = rawGet();
33
+ for (let i = 0; i < chain.length; i++) {
34
+ if (chain[i].onGet) value = chain[i].onGet(value);
35
+ }
36
+ return value;
37
+ }
38
+
39
+ function setter(next) {
40
+ const prev = rawGet();
41
+ let resolved = typeof next === 'function' ? next(prev) : next;
42
+ for (let i = 0; i < chain.length; i++) {
43
+ if (chain[i].onSet) {
44
+ resolved = chain[i].onSet(resolved, prev);
45
+ if (resolved === undefined) return;
46
+ }
47
+ }
48
+ rawSet(resolved);
49
+ }
50
+
51
+ // Preserve _subscribers for reactive tracking
52
+ getter._subscribers = rawGet._subscribers;
53
+
54
+ return [getter, setter];
55
+ }
56
+
57
+ /**
58
+ * Apply a middleware chain to a store proxy.
59
+ * Returns a new Proxy with middleware interception on property get/set.
60
+ *
61
+ * @template {object} T
62
+ * @param {T} store - Proxy-based store from createStore
63
+ * @param {Array<() => { onGet?: (v: any, prop: string|symbol) => any, onSet?: (next: any, prev: any, prop: string|symbol) => any }>} middlewares
64
+ * @returns {T}
65
+ */
66
+ export function withStoreMiddleware(store, middlewares) {
67
+ const chain = middlewares.map(m => m());
68
+
69
+ return new Proxy(store, {
70
+ get(target, prop, receiver) {
71
+ let value = Reflect.get(target, prop, receiver);
72
+ for (let i = 0; i < chain.length; i++) {
73
+ if (chain[i].onGet) value = chain[i].onGet(value, prop);
74
+ }
75
+ return value;
76
+ },
77
+ set(target, prop, value, receiver) {
78
+ const prev = target[prop];
79
+ let resolved = value;
80
+ for (let i = 0; i < chain.length; i++) {
81
+ if (chain[i].onSet) {
82
+ resolved = chain[i].onSet(resolved, prev, prop);
83
+ if (resolved === undefined) return true;
84
+ }
85
+ }
86
+ return Reflect.set(target, prop, resolved, receiver);
87
+ }
88
+ });
89
+ }
90
+
91
+ /**
92
+ * Console logging middleware for signals and stores.
93
+ * Logs `[label] set: prev -> next` on every setter call.
94
+ *
95
+ * @param {{ label?: string, collapsed?: boolean }} [options]
96
+ * @returns {() => { onSet: (next: any, prev: any) => any }}
97
+ */
98
+ export function loggerMiddleware(options) {
99
+ const label = (options && options.label) || 'signal';
100
+ const collapsed = (options && options.collapsed) || false;
101
+
102
+ return () => ({
103
+ onSet(next, prev) {
104
+ const group = collapsed ? console.groupCollapsed : console.group;
105
+ group.call(console, `[${label}] set`);
106
+ console.log('prev:', prev);
107
+ console.log('next:', next);
108
+ console.groupEnd();
109
+ return next;
110
+ }
111
+ });
112
+ }
113
+
114
+ /**
115
+ * Auto-persist middleware. Reads initial value from storage on creation
116
+ * and writes to storage on every set (debounced if configured).
117
+ *
118
+ * On init, if a stored value exists under `options.key`, it is hydrated
119
+ * into the signal via the `_hasHydrated` / `_hydrated` protocol.
120
+ *
121
+ * @param {{ key: string, storage?: 'local' | 'session', debounce?: number }} options
122
+ * @returns {() => { _hasHydrated: boolean, _hydrated: any, onSet: (next: any, prev: any) => any }}
123
+ */
124
+ export function persistMiddleware(options) {
125
+ const key = options.key;
126
+ const storageType = options.storage || 'local';
127
+ const debounceMs = options.debounce || 0;
128
+ /** @type {ReturnType<typeof setTimeout> | null} */
129
+ let timer = null;
130
+
131
+ function getStorage() {
132
+ return storageType === 'session'
133
+ ? (typeof sessionStorage !== 'undefined' ? sessionStorage : null)
134
+ : (typeof localStorage !== 'undefined' ? localStorage : null);
135
+ }
136
+
137
+ function writeTo(store, value) {
138
+ if (!store) return;
139
+ try {
140
+ store.setItem(key, JSON.stringify(value));
141
+ } catch (_) { /* quota exceeded or private browsing */ }
142
+ }
143
+
144
+ return () => {
145
+ const store = getStorage();
146
+ let hydrated = false;
147
+ /** @type {any} */
148
+ let hydratedValue;
149
+
150
+ if (store) {
151
+ try {
152
+ const raw = store.getItem(key);
153
+ if (raw !== null) {
154
+ hydratedValue = JSON.parse(raw);
155
+ hydrated = true;
156
+ }
157
+ } catch (_) { /* corrupt data */ }
158
+ }
159
+
160
+ return {
161
+ _hasHydrated: hydrated,
162
+ _hydrated: hydrated ? hydratedValue : undefined,
163
+
164
+ onSet(next, _prev) {
165
+ const s = getStorage();
166
+ if (debounceMs > 0) {
167
+ if (timer !== null) clearTimeout(timer);
168
+ timer = setTimeout(() => writeTo(s, next), debounceMs);
169
+ } else {
170
+ writeTo(s, next);
171
+ }
172
+ return next;
173
+ }
174
+ };
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Validation middleware. Rejects writes that fail the validator function.
180
+ * If validation fails, the set is silently rejected (returns `undefined`).
181
+ *
182
+ * @template T
183
+ * @param {(value: T) => true | string | Error} validator
184
+ * Return `true` if valid, or an error string / Error object if invalid.
185
+ * @param {{ onError?: (error: string | Error, value: T) => void }} [options]
186
+ * @returns {() => { onSet: (next: T, prev: T) => T | undefined }}
187
+ */
188
+ export function validationMiddleware(validator, options) {
189
+ const onError = options && options.onError;
190
+
191
+ return () => ({
192
+ onSet(next, _prev) {
193
+ const result = validator(next);
194
+ if (result === true) return next;
195
+ if (onError) onError(result, next);
196
+ return undefined;
197
+ }
198
+ });
199
+ }
200
+
201
+ /**
202
+ * Undo/redo middleware. Tracks every set call and exposes controls.
203
+ *
204
+ * Returns an object with:
205
+ * - `middleware` — factory to pass into withMiddleware's array
206
+ * - `undo()` / `redo()` — restore previous/next value
207
+ * - `canUndo` / `canRedo` — signal getters (boolean)
208
+ * - `history()` — returns shallow copy of the undo stack
209
+ *
210
+ * @param {{ maxLength?: number }} [options]
211
+ * @returns {{
212
+ * middleware: () => { _attach: (setter: Function) => void, onSet: (next: any, prev: any) => any },
213
+ * undo: () => void,
214
+ * redo: () => void,
215
+ * canUndo: () => boolean,
216
+ * canRedo: () => boolean,
217
+ * history: () => any[]
218
+ * }}
219
+ */
220
+ export function undoMiddleware(options) {
221
+ const maxLength = (options && options.maxLength) || 100;
222
+
223
+ /** @type {any[]} */
224
+ const stack = [];
225
+ /** @type {any[]} */
226
+ const redoStack = [];
227
+
228
+ const [canUndo, setCanUndo] = createSignal(false);
229
+ const [canRedo, setCanRedo] = createSignal(false);
230
+
231
+ let skip = false;
232
+ /** @type {((v: any) => void) | null} */
233
+ let rawSetter = null;
234
+
235
+ function updateFlags() {
236
+ batch(() => {
237
+ setCanUndo(stack.length > 1);
238
+ setCanRedo(redoStack.length > 0);
239
+ });
240
+ }
241
+
242
+ return {
243
+ middleware() {
244
+ return {
245
+ /** @internal Called by withMiddleware to wire the raw setter. */
246
+ _attach(setter) { rawSetter = setter; },
247
+
248
+ onSet(next, prev) {
249
+ if (skip) return next;
250
+ // Capture initial value on first write
251
+ if (stack.length === 0) stack.push(prev);
252
+ stack.push(next);
253
+ if (stack.length > maxLength + 1) stack.shift();
254
+ redoStack.length = 0;
255
+ updateFlags();
256
+ return next;
257
+ }
258
+ };
259
+ },
260
+
261
+ undo() {
262
+ if (stack.length <= 1 || !rawSetter) return;
263
+ redoStack.push(stack.pop());
264
+ skip = true;
265
+ rawSetter(stack[stack.length - 1]);
266
+ skip = false;
267
+ updateFlags();
268
+ },
269
+
270
+ redo() {
271
+ if (redoStack.length === 0 || !rawSetter) return;
272
+ const value = redoStack.pop();
273
+ stack.push(value);
274
+ skip = true;
275
+ rawSetter(value);
276
+ skip = false;
277
+ updateFlags();
278
+ },
279
+
280
+ canUndo,
281
+ canRedo,
282
+
283
+ /** @returns {any[]} Shallow copy of the undo history stack. */
284
+ history() {
285
+ return stack.slice();
286
+ }
287
+ };
288
+ }
@@ -0,0 +1,206 @@
1
+ /** @typedef {{ run: Function, level: number, sources?: Set, disposed?: boolean }} ReactiveNode */
2
+
3
+ /** @type {Set<ReactiveNode>} */
4
+ const pending = new Set();
5
+ let flushing = false;
6
+ let scheduled = false;
7
+
8
+ /** @type {ReactiveNode|null} */
9
+ export let currentEffect = null;
10
+
11
+ /**
12
+ * @param {ReactiveNode|null} effect
13
+ */
14
+ export function setCurrentEffect(effect) {
15
+ currentEffect = effect;
16
+ }
17
+
18
+ // ─── Ownership Tree ─────────────────────────────────────────
19
+
20
+ /** @typedef {{ children: Set<Owner>, cleanups: Function[], onError?: Function, context?: Map }} Owner */
21
+
22
+ /** @type {Owner|null} */
23
+ let currentOwner = null;
24
+
25
+ /**
26
+ * @returns {Owner|null}
27
+ */
28
+ export function getOwner() {
29
+ return currentOwner;
30
+ }
31
+
32
+ /**
33
+ * @param {Owner|null} owner
34
+ * @param {Function} fn
35
+ * @returns {*}
36
+ */
37
+ export function runWithOwner(owner, fn) {
38
+ const prev = currentOwner;
39
+ currentOwner = owner;
40
+ try {
41
+ return fn();
42
+ } finally {
43
+ currentOwner = prev;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Create an independent reactive scope.
49
+ * @param {Function} fn
50
+ * @returns {*}
51
+ */
52
+ export function createRoot(fn) {
53
+ /** @type {Owner} */
54
+ const owner = { children: new Set(), cleanups: [] };
55
+ const prev = currentOwner;
56
+ currentOwner = owner;
57
+ let result;
58
+ try {
59
+ result = fn(() => disposeOwner(owner));
60
+ } finally {
61
+ currentOwner = prev;
62
+ }
63
+ return result;
64
+ }
65
+
66
+ /**
67
+ * Register current node with owner tree.
68
+ * @param {Function} cleanup
69
+ */
70
+ export function registerCleanup(cleanup) {
71
+ if (currentOwner) {
72
+ currentOwner.cleanups.push(cleanup);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Create a child owner under the current owner.
78
+ * @returns {Owner}
79
+ */
80
+ export function createChildOwner() {
81
+ /** @type {Owner} */
82
+ const child = { children: new Set(), cleanups: [] };
83
+ if (currentOwner) {
84
+ currentOwner.children.add(child);
85
+ child._parent = currentOwner;
86
+ }
87
+ return child;
88
+ }
89
+
90
+ /**
91
+ * Dispose an owner and all its descendants.
92
+ * @param {Owner} owner
93
+ */
94
+ export function disposeOwner(owner) {
95
+ // Dispose children first (depth-first)
96
+ for (const child of owner.children) {
97
+ disposeOwner(child);
98
+ }
99
+ owner.children.clear();
100
+ // Run cleanups in reverse order
101
+ for (let i = owner.cleanups.length - 1; i >= 0; i--) {
102
+ try { owner.cleanups[i](); } catch (_) {}
103
+ }
104
+ owner.cleanups.length = 0;
105
+ // Remove from parent
106
+ if (owner._parent) {
107
+ owner._parent.children.delete(owner);
108
+ owner._parent = null;
109
+ }
110
+ }
111
+
112
+ // ─── Error Boundaries ───────────────────────────────────────
113
+
114
+ /**
115
+ * Set error handler on current owner.
116
+ * @param {Function} handler — (error) => void
117
+ */
118
+ export function onError(handler) {
119
+ if (currentOwner) {
120
+ currentOwner.onError = handler;
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Walk up owner tree to find error handler.
126
+ * @param {Error} error
127
+ * @param {Owner|null} owner
128
+ */
129
+ export function handleError(error, owner) {
130
+ let current = owner;
131
+ while (current) {
132
+ if (current.onError) {
133
+ try {
134
+ current.onError(error);
135
+ return;
136
+ } catch (e) {
137
+ // Handler itself threw — continue up
138
+ error = e instanceof Error ? e : new Error(String(e));
139
+ }
140
+ }
141
+ current = current._parent || null;
142
+ }
143
+ // No handler found — rethrow
144
+ throw error;
145
+ }
146
+
147
+ // ─── Scheduling & Flush ─────────────────────────────────────
148
+
149
+ /**
150
+ * @param {ReactiveNode} effect
151
+ */
152
+ export function scheduleEffect(effect) {
153
+ if (effect.disposed) return;
154
+ pending.add(effect);
155
+ if (!scheduled) {
156
+ scheduled = true;
157
+ queueMicrotask(flush);
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Topological flush — sort pending effects by level (lowest first).
163
+ * Effects with lower levels (closer to signals) run first,
164
+ * preventing diamond-problem double-firing.
165
+ */
166
+ export function flush() {
167
+ if (flushing) return;
168
+ flushing = true;
169
+ scheduled = false;
170
+
171
+ while (pending.size > 0) {
172
+ // Sort by level for topological ordering
173
+ const effects = [...pending].sort((a, b) => (a.level || 0) - (b.level || 0));
174
+ pending.clear();
175
+ for (let i = 0; i < effects.length; i++) {
176
+ const effect = effects[i];
177
+ if (!effect.disposed) {
178
+ effect.run();
179
+ }
180
+ }
181
+ }
182
+
183
+ flushing = false;
184
+ }
185
+
186
+ let batchDepth = 0;
187
+
188
+ /**
189
+ * @param {Function} fn
190
+ */
191
+ export function batch(fn) {
192
+ batchDepth++;
193
+ try {
194
+ fn();
195
+ } finally {
196
+ batchDepth--;
197
+ if (batchDepth === 0) flush();
198
+ }
199
+ }
200
+
201
+ /**
202
+ * @returns {boolean}
203
+ */
204
+ export function isBatching() {
205
+ return batchDepth > 0;
206
+ }