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.
- package/AGENTS.md +868 -0
- package/CHANGELOG.md +255 -0
- package/CLAUDE.md +178 -0
- package/LICENSE +21 -0
- package/README.md +229 -0
- package/cli/art.js +127 -0
- package/cli/commands/a11y.js +61 -0
- package/cli/commands/audit.js +225 -0
- package/cli/commands/build.js +38 -0
- package/cli/commands/dev.js +18 -0
- package/cli/commands/doctor.js +197 -0
- package/cli/commands/figma-sync.js +48 -0
- package/cli/commands/figma-tokens.js +55 -0
- package/cli/commands/generate.js +26 -0
- package/cli/commands/init.js +116 -0
- package/cli/commands/lint.js +209 -0
- package/cli/commands/mcp.js +530 -0
- package/cli/commands/migrate.js +175 -0
- package/cli/commands/test.js +38 -0
- package/cli/commands/validate.js +354 -0
- package/cli/index.js +113 -0
- package/package.json +95 -0
- package/reference/atoms.md +517 -0
- package/reference/behaviors.md +384 -0
- package/reference/build-tooling.md +275 -0
- package/reference/color-guidelines.md +965 -0
- package/reference/component-lifecycle.md +137 -0
- package/reference/compound-spacing.md +95 -0
- package/reference/decantation-process.md +499 -0
- package/reference/dev-server-routes.md +93 -0
- package/reference/form-system.md +253 -0
- package/reference/i18n.md +336 -0
- package/reference/icons.md +576 -0
- package/reference/llm-primer.md +953 -0
- package/reference/plugins.md +252 -0
- package/reference/registry-consumption.md +76 -0
- package/reference/router.md +217 -0
- package/reference/shells.md +116 -0
- package/reference/spatial-guidelines.md +541 -0
- package/reference/ssr.md +234 -0
- package/reference/state-data.md +215 -0
- package/reference/state-patterns.md +166 -0
- package/reference/state.md +194 -0
- package/reference/style-system.md +110 -0
- package/reference/tokens.md +460 -0
- package/src/app.js +19 -0
- package/src/chart/_animate.js +266 -0
- package/src/chart/_base.js +109 -0
- package/src/chart/_data.js +209 -0
- package/src/chart/_format.js +106 -0
- package/src/chart/_interact.js +364 -0
- package/src/chart/_palette.js +105 -0
- package/src/chart/_renderer.js +52 -0
- package/src/chart/_scene.js +262 -0
- package/src/chart/_shared.js +371 -0
- package/src/chart/index.js +637 -0
- package/src/chart/layouts/_layout-base.js +328 -0
- package/src/chart/layouts/cartesian.js +148 -0
- package/src/chart/layouts/hierarchy.js +562 -0
- package/src/chart/layouts/polar.js +101 -0
- package/src/chart/renderers/canvas.js +179 -0
- package/src/chart/renderers/svg.js +256 -0
- package/src/chart/renderers/webgpu.js +715 -0
- package/src/chart/types/_type-base.js +26 -0
- package/src/chart/types/area.js +134 -0
- package/src/chart/types/bar.js +173 -0
- package/src/chart/types/box-plot.js +125 -0
- package/src/chart/types/bubble.js +63 -0
- package/src/chart/types/candlestick.js +115 -0
- package/src/chart/types/chord.js +85 -0
- package/src/chart/types/combination.js +108 -0
- package/src/chart/types/funnel.js +68 -0
- package/src/chart/types/gauge.js +163 -0
- package/src/chart/types/heatmap.js +98 -0
- package/src/chart/types/histogram.js +71 -0
- package/src/chart/types/line.js +111 -0
- package/src/chart/types/org-chart.js +93 -0
- package/src/chart/types/pie.js +81 -0
- package/src/chart/types/radar.js +96 -0
- package/src/chart/types/radial.js +68 -0
- package/src/chart/types/range-area.js +55 -0
- package/src/chart/types/range-bar.js +61 -0
- package/src/chart/types/sankey.js +73 -0
- package/src/chart/types/scatter.js +66 -0
- package/src/chart/types/sparkline.js +81 -0
- package/src/chart/types/sunburst.js +69 -0
- package/src/chart/types/swimlane.js +88 -0
- package/src/chart/types/treemap.js +62 -0
- package/src/chart/types/waterfall.js +100 -0
- package/src/components/_base.js +1658 -0
- package/src/components/_behaviors.js +1140 -0
- package/src/components/_primitives.js +534 -0
- package/src/components/_qr-encoder.js +539 -0
- package/src/components/accordion.js +207 -0
- package/src/components/affix.js +62 -0
- package/src/components/alert-dialog.js +75 -0
- package/src/components/alert.js +47 -0
- package/src/components/aspect-ratio.js +24 -0
- package/src/components/avatar-group.js +55 -0
- package/src/components/avatar.js +38 -0
- package/src/components/back-top.js +75 -0
- package/src/components/badge.js +74 -0
- package/src/components/banner.js +68 -0
- package/src/components/breadcrumb.js +162 -0
- package/src/components/button.js +115 -0
- package/src/components/calendar.js +131 -0
- package/src/components/card.js +192 -0
- package/src/components/carousel.js +98 -0
- package/src/components/cascader.js +261 -0
- package/src/components/checkbox.js +80 -0
- package/src/components/chip.js +81 -0
- package/src/components/code-block.js +82 -0
- package/src/components/collapsible.js +50 -0
- package/src/components/color-palette.js +438 -0
- package/src/components/color-picker.js +314 -0
- package/src/components/combobox.js +181 -0
- package/src/components/command.js +174 -0
- package/src/components/comment.js +206 -0
- package/src/components/context-menu.js +76 -0
- package/src/components/data-table.js +724 -0
- package/src/components/date-picker.js +217 -0
- package/src/components/date-range-picker.js +244 -0
- package/src/components/datetime-picker.js +271 -0
- package/src/components/descriptions.js +68 -0
- package/src/components/drawer.js +179 -0
- package/src/components/dropdown.js +88 -0
- package/src/components/empty.js +41 -0
- package/src/components/float-button.js +90 -0
- package/src/components/form.js +106 -0
- package/src/components/hover-card.js +49 -0
- package/src/components/icon.js +87 -0
- package/src/components/image.js +97 -0
- package/src/components/index.js +117 -0
- package/src/components/input-group.js +75 -0
- package/src/components/input-number.js +155 -0
- package/src/components/input-otp.js +178 -0
- package/src/components/input.js +91 -0
- package/src/components/kbd.js +36 -0
- package/src/components/label.js +25 -0
- package/src/components/list.js +118 -0
- package/src/components/masked-input.js +236 -0
- package/src/components/mentions.js +165 -0
- package/src/components/menu.js +259 -0
- package/src/components/message.js +80 -0
- package/src/components/modal.js +147 -0
- package/src/components/navigation-menu.js +166 -0
- package/src/components/notification.js +84 -0
- package/src/components/pagination.js +104 -0
- package/src/components/placeholder.js +132 -0
- package/src/components/popconfirm.js +70 -0
- package/src/components/popover.js +58 -0
- package/src/components/progress.js +61 -0
- package/src/components/qrcode.js +251 -0
- package/src/components/radiogroup.js +120 -0
- package/src/components/range-slider.js +176 -0
- package/src/components/rate.js +186 -0
- package/src/components/resizable.js +83 -0
- package/src/components/result.js +57 -0
- package/src/components/scroll-area.js +43 -0
- package/src/components/segmented.js +97 -0
- package/src/components/select.js +165 -0
- package/src/components/separator.js +31 -0
- package/src/components/shell.js +407 -0
- package/src/components/skeleton.js +39 -0
- package/src/components/slider.js +141 -0
- package/src/components/sortable-list.js +176 -0
- package/src/components/space.js +42 -0
- package/src/components/spinner.js +112 -0
- package/src/components/splitter.js +147 -0
- package/src/components/statistic.js +136 -0
- package/src/components/steps.js +99 -0
- package/src/components/switch.js +95 -0
- package/src/components/table.js +44 -0
- package/src/components/tabs.js +216 -0
- package/src/components/tag.js +115 -0
- package/src/components/textarea.js +82 -0
- package/src/components/time-picker.js +153 -0
- package/src/components/time-range-picker.js +170 -0
- package/src/components/timeline.js +226 -0
- package/src/components/toast.js +71 -0
- package/src/components/toggle.js +213 -0
- package/src/components/tooltip.js +57 -0
- package/src/components/tour.js +159 -0
- package/src/components/transfer.js +163 -0
- package/src/components/tree-select.js +274 -0
- package/src/components/tree.js +141 -0
- package/src/components/typography.js +136 -0
- package/src/components/upload.js +118 -0
- package/src/components/visually-hidden.js +20 -0
- package/src/components/watermark.js +124 -0
- package/src/core/index.js +539 -0
- package/src/core/lifecycle.js +69 -0
- package/src/css/atoms.js +651 -0
- package/src/css/components.js +940 -0
- package/src/css/derive.js +1296 -0
- package/src/css/index.js +265 -0
- package/src/css/runtime.js +268 -0
- package/src/css/styles/addons/bioluminescent.js +93 -0
- package/src/css/styles/addons/clay.js +70 -0
- package/src/css/styles/addons/clean.js +57 -0
- package/src/css/styles/addons/command-center.js +143 -0
- package/src/css/styles/addons/dopamine.js +83 -0
- package/src/css/styles/addons/editorial.js +80 -0
- package/src/css/styles/addons/glassmorphism.js +99 -0
- package/src/css/styles/addons/liquid-glass.js +105 -0
- package/src/css/styles/addons/prismatic.js +100 -0
- package/src/css/styles/addons/retro.js +63 -0
- package/src/css/styles/auradecantism.js +96 -0
- package/src/css/theme-registry.js +444 -0
- package/src/data/entity.js +281 -0
- package/src/data/index.js +13 -0
- package/src/data/persist.js +225 -0
- package/src/data/query.js +839 -0
- package/src/data/realtime.js +299 -0
- package/src/data/url.js +177 -0
- package/src/data/worker.js +134 -0
- package/src/explorer/archetypes.js +243 -0
- package/src/explorer/atoms.js +228 -0
- package/src/explorer/charts.js +497 -0
- package/src/explorer/components.js +129 -0
- package/src/explorer/foundations.js +949 -0
- package/src/explorer/icons.js +178 -0
- package/src/explorer/patterns.js +247 -0
- package/src/explorer/recipes.js +194 -0
- package/src/explorer/shared/pattern-examples.js +1337 -0
- package/src/explorer/shared/showcase-renderer.js +958 -0
- package/src/explorer/shared/spec-table.js +41 -0
- package/src/explorer/shared/usage-links.js +87 -0
- package/src/explorer/shell-config.js +10 -0
- package/src/explorer/shells.js +551 -0
- package/src/explorer/styles.js +161 -0
- package/src/explorer/tokens.js +262 -0
- package/src/explorer/tools.js +525 -0
- package/src/form/index.js +804 -0
- package/src/i18n/index.js +251 -0
- package/src/icons/essential.js +479 -0
- package/src/icons/index.js +53 -0
- package/src/plugins/index.js +282 -0
- package/src/registry/archetypes/content-site.json +71 -0
- package/src/registry/archetypes/docs-explorer.json +23 -0
- package/src/registry/archetypes/ecommerce.json +104 -0
- package/src/registry/archetypes/financial-dashboard.json +77 -0
- package/src/registry/archetypes/index.json +41 -0
- package/src/registry/archetypes/portfolio.json +82 -0
- package/src/registry/archetypes/recipe-community.json +159 -0
- package/src/registry/archetypes/saas-dashboard.json +86 -0
- package/src/registry/architect/cross-cutting.json +45 -0
- package/src/registry/architect/domains/ecommerce.json +294 -0
- package/src/registry/architect/domains/financial-services.json +302 -0
- package/src/registry/architect/index.json +26 -0
- package/src/registry/architect/traits.json +379 -0
- package/src/registry/atoms.json +16 -0
- package/src/registry/chart-showcase.json +160 -0
- package/src/registry/chart.json +136 -0
- package/src/registry/components.json +8616 -0
- package/src/registry/core.json +216 -0
- package/src/registry/css.json +319 -0
- package/src/registry/data.json +135 -0
- package/src/registry/foundations.json +11 -0
- package/src/registry/icons.json +463 -0
- package/src/registry/index.json +101 -0
- package/src/registry/patterns/activity-feed.json +37 -0
- package/src/registry/patterns/article-content.json +27 -0
- package/src/registry/patterns/auth-form.json +37 -0
- package/src/registry/patterns/author-card.json +20 -0
- package/src/registry/patterns/card-grid.json +127 -0
- package/src/registry/patterns/category-nav.json +26 -0
- package/src/registry/patterns/chart-grid.json +36 -0
- package/src/registry/patterns/chat-interface.json +37 -0
- package/src/registry/patterns/checklist-card.json +55 -0
- package/src/registry/patterns/comparison-panel.json +27 -0
- package/src/registry/patterns/component-showcase.json +24 -0
- package/src/registry/patterns/contact-form.json +31 -0
- package/src/registry/patterns/cta-section.json +20 -0
- package/src/registry/patterns/data-table.json +37 -0
- package/src/registry/patterns/detail-header.json +83 -0
- package/src/registry/patterns/detail-panel.json +27 -0
- package/src/registry/patterns/explorer-shell.json +22 -0
- package/src/registry/patterns/filter-bar.json +33 -0
- package/src/registry/patterns/filter-sidebar.json +27 -0
- package/src/registry/patterns/form-sections.json +110 -0
- package/src/registry/patterns/goal-tracker.json +27 -0
- package/src/registry/patterns/hero.json +107 -0
- package/src/registry/patterns/index.json +47 -0
- package/src/registry/patterns/kpi-grid.json +36 -0
- package/src/registry/patterns/media-gallery.json +20 -0
- package/src/registry/patterns/order-history.json +20 -0
- package/src/registry/patterns/pagination.json +19 -0
- package/src/registry/patterns/photo-to-recipe.json +36 -0
- package/src/registry/patterns/pipeline-tracker.json +28 -0
- package/src/registry/patterns/post-list.json +27 -0
- package/src/registry/patterns/pricing-table.json +32 -0
- package/src/registry/patterns/scorecard.json +28 -0
- package/src/registry/patterns/search-bar.json +20 -0
- package/src/registry/patterns/specimen-grid.json +19 -0
- package/src/registry/patterns/stat-card.json +55 -0
- package/src/registry/patterns/stats-bar.json +55 -0
- package/src/registry/patterns/steps-card.json +55 -0
- package/src/registry/patterns/table-of-contents.json +19 -0
- package/src/registry/patterns/testimonials.json +21 -0
- package/src/registry/patterns/timeline.json +27 -0
- package/src/registry/patterns/token-inspector.json +21 -0
- package/src/registry/patterns/wizard.json +27 -0
- package/src/registry/recipe-auradecantism.json +69 -0
- package/src/registry/recipe-clean.json +65 -0
- package/src/registry/recipe-command-center.json +78 -0
- package/src/registry/router.json +73 -0
- package/src/registry/schema/README.md +197 -0
- package/src/registry/skeletons.json +259 -0
- package/src/registry/state.json +137 -0
- package/src/registry/tokens.json +40 -0
- package/src/router/hash.js +17 -0
- package/src/router/history.js +18 -0
- package/src/router/index.js +598 -0
- package/src/ssr/index.js +922 -0
- package/src/state/arrays.js +181 -0
- package/src/state/devtools.js +647 -0
- package/src/state/index.js +498 -0
- package/src/state/middleware.js +288 -0
- package/src/state/scheduler.js +206 -0
- package/src/state/store.js +300 -0
- package/src/tags/index.js +19 -0
- package/src/tannins/auth.js +396 -0
- package/src/test/dom.js +352 -0
- package/src/test/index.js +62 -0
- package/src/test/state.js +306 -0
- package/tools/a11y-audit.js +487 -0
- package/tools/analyzer.js +315 -0
- package/tools/audit.js +706 -0
- package/tools/builder.js +1422 -0
- package/tools/css-extract.js +188 -0
- package/tools/dev-server.js +316 -0
- package/tools/dts-gen.js +1260 -0
- package/tools/figma-components.js +329 -0
- package/tools/figma-patterns.js +516 -0
- package/tools/figma-plugin/code.js +453 -0
- package/tools/figma-plugin/manifest.json +14 -0
- package/tools/figma-plugin/ui.html +268 -0
- package/tools/figma-render.js +293 -0
- package/tools/figma-tokens.js +712 -0
- package/tools/figma-upload.js +318 -0
- package/tools/generate.js +738 -0
- package/tools/icons.js +133 -0
- package/tools/init-templates.js +265 -0
- package/tools/install-hooks.sh +5 -0
- package/tools/migrations/0.5.0.js +53 -0
- package/tools/migrations/0.6.0.js +95 -0
- package/tools/minify.js +170 -0
- package/tools/pre-commit +4 -0
- package/tools/registry.js +662 -0
- package/tools/reset-playground.js +61 -0
- package/tools/starter-templates/content-site/app.js +49 -0
- package/tools/starter-templates/content-site/essence.js +19 -0
- package/tools/starter-templates/content-site/pages.js +31 -0
- package/tools/starter-templates/ecommerce/app.js +50 -0
- package/tools/starter-templates/ecommerce/essence.js +19 -0
- package/tools/starter-templates/ecommerce/pages.js +31 -0
- package/tools/starter-templates/landing-page/app.js +38 -0
- package/tools/starter-templates/landing-page/essence.js +18 -0
- package/tools/starter-templates/landing-page/pages.js +21 -0
- package/tools/starter-templates/portfolio/app.js +45 -0
- package/tools/starter-templates/portfolio/essence.js +19 -0
- package/tools/starter-templates/portfolio/pages.js +33 -0
- package/tools/starter-templates/saas-dashboard/app.js +70 -0
- package/tools/starter-templates/saas-dashboard/essence.js +19 -0
- package/tools/starter-templates/saas-dashboard/pages.js +31 -0
- package/tools/verify-pack.js +203 -0
- package/types/chart.d.ts +77 -0
- package/types/components.d.ts +587 -0
- package/types/core.d.ts +89 -0
- package/types/css.d.ts +149 -0
- package/types/data.d.ts +238 -0
- package/types/form.d.ts +164 -0
- package/types/i18n.d.ts +51 -0
- package/types/icons.d.ts +27 -0
- package/types/index.d.ts +13 -0
- package/types/router.d.ts +116 -0
- package/types/ssr.d.ts +102 -0
- package/types/state.d.ts +83 -0
- package/types/tags.d.ts +62 -0
- package/types/tannins.d.ts +63 -0
- 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
|
+
}
|