kerfjs 5.0.0-beta.9 → 5.0.0-beta.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2586 -107
- package/README.md +96 -37
- package/ai/cursorrules +110 -61
- package/ai/manifest.json +15 -7
- package/ai/skill.md +117 -72
- package/dist/actions.d.ts +1 -1
- package/dist/actions.js +10 -6
- package/dist/actions.js.map +1 -1
- package/dist/array-signal.js +1 -1
- package/dist/async.js +22 -19
- package/dist/async.js.map +1 -1
- package/dist/attach.js +1 -66
- package/dist/attach.js.map +1 -1
- package/dist/{chunk-HW7KSM2Y.js → chunk-3C6F6NM4.js} +1 -1
- package/dist/{chunk-HW7KSM2Y.js.map → chunk-3C6F6NM4.js.map} +1 -1
- package/dist/{chunk-GY4XV2UV.js → chunk-4TX6YJDM.js} +10 -3
- package/dist/chunk-4TX6YJDM.js.map +1 -0
- package/dist/{chunk-QIP723L4.js → chunk-5WRGJZV6.js} +3 -2
- package/dist/chunk-5WRGJZV6.js.map +1 -0
- package/dist/{chunk-KZJXHFIB.js → chunk-BI4HGICV.js} +3 -3
- package/dist/chunk-BI4HGICV.js.map +1 -0
- package/dist/chunk-FM6N2B4N.js +70 -0
- package/dist/chunk-FM6N2B4N.js.map +1 -0
- package/dist/{chunk-LVH3GC6B.js → chunk-H7WTRM6F.js} +3 -3
- package/dist/chunk-H7WTRM6F.js.map +1 -0
- package/dist/{chunk-U6FK33SG.js → chunk-IHMDBBUJ.js} +2 -2
- package/dist/chunk-IHMDBBUJ.js.map +1 -0
- package/dist/chunk-JN7EJM3B.js +86 -0
- package/dist/chunk-JN7EJM3B.js.map +1 -0
- package/dist/{chunk-UZJ6I4T6.js → chunk-MK42GLPV.js} +1 -1
- package/dist/chunk-MK42GLPV.js.map +1 -0
- package/dist/{chunk-SVATPF5R.js → chunk-T33U7SRR.js} +261 -165
- package/dist/chunk-T33U7SRR.js.map +1 -0
- package/dist/{chunk-ZDCJZCNO.js → chunk-UGNPDLNI.js} +78 -45
- package/dist/chunk-UGNPDLNI.js.map +1 -0
- package/dist/{chunk-V46JKE44.js → chunk-WKIPLNVO.js} +1 -1
- package/dist/chunk-WKIPLNVO.js.map +1 -0
- package/dist/{delegate-CL9VTZFb.d.ts → delegate--CrfgYtC.d.ts} +0 -34
- package/dist/dev.d.ts +31 -13
- package/dist/dev.js +101 -35
- package/dist/dev.js.map +1 -1
- package/dist/html.js +5 -4
- package/dist/html.js.map +1 -1
- package/dist/index.d.ts +14 -9
- package/dist/index.js +29 -20
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime.d.ts +13 -5
- package/dist/jsx-runtime.js +1 -1
- package/dist/list.d.ts +7 -3
- package/dist/list.js +153 -46
- package/dist/list.js.map +1 -1
- package/dist/overlay.d.ts +128 -129
- package/dist/overlay.js +675 -366
- package/dist/overlay.js.map +1 -1
- package/dist/remount.js +2 -2
- package/dist/router.d.ts +13 -4
- package/dist/router.js +37 -10
- package/dist/router.js.map +1 -1
- package/dist/scope.d.ts +35 -8
- package/dist/scope.js +61 -30
- package/dist/scope.js.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/timing.d.ts +3 -2
- package/dist/timing.js +1 -1
- package/dist/timing.js.map +1 -1
- package/llms.txt +13 -6
- package/package.json +33 -8
- package/setup/cli.mjs +90 -0
- package/setup/index.d.mts +37 -0
- package/setup/index.mjs +1347 -0
- package/setup/jsonc.mjs +201 -0
- package/setup/state.schema.json +56 -0
- package/dist/chunk-GY4XV2UV.js.map +0 -1
- package/dist/chunk-KPXIOG2C.js +0 -54
- package/dist/chunk-KPXIOG2C.js.map +0 -1
- package/dist/chunk-KZJXHFIB.js.map +0 -1
- package/dist/chunk-LVH3GC6B.js.map +0 -1
- package/dist/chunk-QIP723L4.js.map +0 -1
- package/dist/chunk-SVATPF5R.js.map +0 -1
- package/dist/chunk-U6FK33SG.js.map +0 -1
- package/dist/chunk-UZJ6I4T6.js.map +0 -1
- package/dist/chunk-V46JKE44.js.map +0 -1
- package/dist/chunk-ZDCJZCNO.js.map +0 -1
package/ai/manifest.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"kerfjsVersion": "5.0.0-beta.
|
|
2
|
+
"kerfjsVersion": "5.0.0-beta.90",
|
|
3
3
|
"files": [
|
|
4
4
|
{
|
|
5
5
|
"name": "skill",
|
|
6
6
|
"source": "kerf.claude-skill.md",
|
|
7
7
|
"bundle": "ai/skill.md",
|
|
8
8
|
"dest": ".claude/skills/kerf-app/SKILL.md",
|
|
9
|
-
"version": "1.
|
|
10
|
-
"sha256": "
|
|
9
|
+
"version": "1.25.0",
|
|
10
|
+
"sha256": "4bc2b292bd707639c13b8cafe3fb24f779bfea04ab38fd6a5d40c443eed89c9d",
|
|
11
11
|
"history": {
|
|
12
12
|
"1.0.0": "bef982cb743fd4c73e666ee5cf774112e57c93ebb13b24bbacbf08577db0930a",
|
|
13
13
|
"1.1.0": "f2aadb21e33dd389028951ae1ecbd9f8ba68648b3c5beba43fb4dfa2ab29f195",
|
|
@@ -36,7 +36,11 @@
|
|
|
36
36
|
"1.19.0": "bbbe87446cc4e1be250d27152adfd385431f4498cde320534f65f10f853ab7f9",
|
|
37
37
|
"1.20.0": "aa88ccdbf969624bbc6cefadba76ada1f090fae436597a7bab564e24c467287f",
|
|
38
38
|
"1.21.0": "265b747eeb9c3d445c60d66a1109eec42705075b7cd154d107bd79d543c1e7e5",
|
|
39
|
-
"1.22.0": "
|
|
39
|
+
"1.22.0": "dc99b0ca5e911be11cbfa2ae515c0e161a414a5185895069535c41ba56664e70",
|
|
40
|
+
"1.23.0": "28b82b4d31b98814c8fbbe1cb0bcf716192deb494c020b9af41a30830e6a0007",
|
|
41
|
+
"1.23.1": "e21d7db1097ad95c11af9248ad17d514716bebc5646becf57d19a7598d6e382c",
|
|
42
|
+
"1.24.0": "f74616ec20120fa97ec2ff7e92971f5721ee8dcb0b5b7da2dd2604bb87002b69",
|
|
43
|
+
"1.25.0": "4bc2b292bd707639c13b8cafe3fb24f779bfea04ab38fd6a5d40c443eed89c9d"
|
|
40
44
|
}
|
|
41
45
|
},
|
|
42
46
|
{
|
|
@@ -44,8 +48,8 @@
|
|
|
44
48
|
"source": "kerf.cursorrules",
|
|
45
49
|
"bundle": "ai/cursorrules",
|
|
46
50
|
"dest": ".cursorrules",
|
|
47
|
-
"version": "1.
|
|
48
|
-
"sha256": "
|
|
51
|
+
"version": "1.23.0",
|
|
52
|
+
"sha256": "908e6622f597108ca696c8ff6f1081b3d83996dc46771a3515b184884dab4b25",
|
|
49
53
|
"history": {
|
|
50
54
|
"1.0.0": "98f7faff2b7585ef9cc511add42ea5d9bf0cbae8307b066af090e9281136d2ab",
|
|
51
55
|
"1.1.0": "a2ec8c221dc694c98b81009cd2d99f9159a45fda94585de2f54471b227099eda",
|
|
@@ -72,7 +76,11 @@
|
|
|
72
76
|
"1.17.0": "6ad51fb0eec468f584346d1d7b4d1c2eca2cb52b04891b38ba44e93b57eb9e1c",
|
|
73
77
|
"1.18.0": "de3eea1bc181da629694d19a0817d252a868e4704437fbdd9e923856b8a0890a",
|
|
74
78
|
"1.19.0": "c5b91e449f6feccaa36a04cb7f8550d3e414eb2795ea6c5dccdf6071206b3261",
|
|
75
|
-
"1.20.0": "
|
|
79
|
+
"1.20.0": "5e7b8d8ddc41f60220961417c9ae01c193fae045378a10a3e9c96a0e3cf10a03",
|
|
80
|
+
"1.21.0": "a1b663b44650b1a04f89c60b26b002322fc6789650291477e14e13329c3ce25f",
|
|
81
|
+
"1.21.1": "9b3eb825a7bec05bb6f0138f77b45fadf896b83020178c34c5a2f531c38f4dee",
|
|
82
|
+
"1.22.0": "3515c663e908b2d6dc9a2a9cf42f2a5bf9a5b3a10c79d43e30cc8427e2ce209d",
|
|
83
|
+
"1.23.0": "908e6622f597108ca696c8ff6f1081b3d83996dc46771a3515b184884dab4b25"
|
|
76
84
|
}
|
|
77
85
|
}
|
|
78
86
|
]
|
package/ai/skill.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: kerf-app
|
|
3
3
|
description: Build UIs in the kerf reactive framework and its @kerfjs/ui component package (https://github.com/brianwestphal/kerf). Use this skill whenever the user is writing or modifying code that imports `kerfjs` or `@kerfjs/ui`, asks to add a feature to a kerf app, or asks "how do I do X in kerf?". Use it proactively the moment you spot a kerf import in the file you're editing.
|
|
4
|
-
kerf-skill-version: 1.
|
|
4
|
+
kerf-skill-version: 1.25.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Building apps with kerf
|
|
@@ -10,14 +10,14 @@ kerf-skill-version: 1.22.0
|
|
|
10
10
|
> project's `.claude/skills/kerf-app/SKILL.md`) so Claude Code activates
|
|
11
11
|
> it whenever you work on a kerf app.
|
|
12
12
|
|
|
13
|
-
kerf is a ~
|
|
13
|
+
kerf is a ~13 KB reactive UI framework (still ~13 KB with `arraySignal`): signals + DOM morphing + JSX → HTML strings. No virtual DOM, no compiler, no scheduler. The whole public surface fits in 17 exports.
|
|
14
14
|
|
|
15
15
|
## Setup
|
|
16
16
|
|
|
17
17
|
- Install: `npm install kerfjs`
|
|
18
18
|
- `tsconfig.json`: `"jsx": "react-jsx"`, `"jsxImportSource": "kerfjs"`
|
|
19
19
|
- Vite / esbuild need no extra config.
|
|
20
|
-
- **Dev diagnostics are opt-in by import, and only an APP installs them.** kerf does not infer dev mode. In the app entry add `if (import.meta.env.DEV) await import('kerfjs/dev');` (Vite) or `if (process.env.NODE_ENV !== 'production') await import('kerfjs/dev');` (webpack/Node). That enables the read-only store `get()` snapshot, the throwing dangerous-URL screen, and makes the `KERF_DEV_WARN_*` family available; omitting it is production shape and sheds ~4.7 KB min+gzip because the condition folds away and the chunk is never emitted. Put it FIRST if relying on the untracked-signal
|
|
20
|
+
- **Dev diagnostics are opt-in by import, and only an APP installs them.** kerf does not infer dev mode. In the app entry add `if (import.meta.env.DEV) await import('kerfjs/dev');` (Vite) or `if (process.env.NODE_ENV !== 'production') await import('kerfjs/dev');` (webpack/Node). That enables the read-only store `get()` snapshot, the throwing dangerous-URL screen, and makes the `KERF_DEV_WARN_*` family available; omitting it is production shape and sheds ~4.7 KB min+gzip because the condition folds away and the chunk is never emitted. Put it (and any `enableWarnings()` call) FIRST if relying on the untracked-signal, delegate-in-effect, or rebuilt-listeners warnings — `signal()`, `effect()`, and `mount()` decide those at creation time, so anything created earlier stays uncovered.
|
|
21
21
|
- **Switch individual warnings on with `enableWarnings()`**, which is the only switch that works in a browser (no `process` object there, and a bundler `define` cannot reach the read): `const dev = await import('kerfjs/dev'); dev.enableWarnings({ staleBinding: true, narrowSet: true, invariants: 'throw' });`. The `KERF_DEV_WARN_*` env vars do the same for Node/SSR/CI; an explicit call wins either way.
|
|
22
22
|
- **A component package must NEVER import `kerfjs/dev`.** The hooks are process-global, so installing them is the consuming app's decision — a library that does it forces the diagnostics (and the chunk) on every consumer. Put the import in your demo page or test harness instead.
|
|
23
23
|
- Recommended companion: `npm install --save-dev eslint-plugin-kerfjs` and add `kerfjs.configs.recommended` to the project's eslint config. Enforces five of the hard rules below (no inline JSX event handlers, require `data-key` in `each()`, capture `delegate()` disposers, no nested `mount()`, prefer module JSX augmentation) at edit time — useful as a self-correction signal when authoring kerf code.
|
|
@@ -53,13 +53,23 @@ explicit behavioral helpers and return disposers. Compose `AppTab` inside a
|
|
|
53
53
|
controlled `TabBar`; `wireTabBars()` supplies horizontal edge autoscroll during
|
|
54
54
|
dragging. Apply reorder reports with `reorderTabs()`, and keep order,
|
|
55
55
|
selection, close policy, routing, panels, and persistence in the host.
|
|
56
|
+
Toolbars draw no divider by default: call `wireScrollDividers()` once at the
|
|
57
|
+
app root so each `Pane` draws its header/footer divider, and each `TabBar` its
|
|
58
|
+
overflow divider, only while content is scrolled beneath or beyond it.
|
|
56
59
|
Icons and spinners are decorative unless labeled; use assertive banners only
|
|
57
60
|
for urgent interruption.
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
Components own their styles; configure, never override. Set how a component
|
|
63
|
+
looks and sits through its typed props (size, density, inset, presentation,
|
|
64
|
+
`dividerSides`, `ToolbarConfig`, `PaneConfig`, …); never write application
|
|
65
|
+
CSS that selects a `.kui-*` class, a `[data-component]` root, a `wa-*` element
|
|
66
|
+
a Kerf component renders, or its `::part()`s, and never add padding, margin,
|
|
67
|
+
width, or height around a component to size or space it (it owns its 8px
|
|
68
|
+
inset — adding more double-insets). A missing prop is a component gap to
|
|
69
|
+
report upstream, not a reason to override. The opinionated semantic ramps
|
|
70
|
+
match Hot Sheet 2 and Web Awesome: override `--kui-color-*` globally or
|
|
71
|
+
component properties such as `--kui-state-banner-background` at a tone or
|
|
72
|
+
instance boundary, for theming only.
|
|
63
73
|
|
|
64
74
|
For Web Awesome's broader free component set, import the CSS-only
|
|
65
75
|
`@kerfjs/ui/webawesome.css` theme once, then import only each Web Awesome
|
|
@@ -77,41 +87,53 @@ full AI contract is at `node_modules/@kerfjs/ui/ai/skill.md`.
|
|
|
77
87
|
|
|
78
88
|
```ts
|
|
79
89
|
import {
|
|
80
|
-
signal,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
signal,
|
|
91
|
+
computed,
|
|
92
|
+
effect,
|
|
93
|
+
batch, // reactivity
|
|
94
|
+
defineStore,
|
|
95
|
+
resetAllStores, // stores
|
|
96
|
+
mount,
|
|
97
|
+
morph,
|
|
98
|
+
each, // render (reactive + one-shot) + keyed list
|
|
99
|
+
delegate,
|
|
100
|
+
delegateCapture, // events
|
|
101
|
+
toElement, // direct JSX → DOM Element (or DocumentFragment for multi-root)
|
|
102
|
+
SafeHtml,
|
|
103
|
+
isSafeHtml,
|
|
104
|
+
raw,
|
|
105
|
+
Fragment,
|
|
106
|
+
type JSXChildren,
|
|
107
|
+
} from "kerfjs";
|
|
87
108
|
|
|
88
109
|
// Optional, only when you need granular collection updates:
|
|
89
|
-
import { arraySignal } from
|
|
110
|
+
import { arraySignal } from "kerfjs/array-signal";
|
|
90
111
|
|
|
91
112
|
// Development diagnostics — gate with YOUR build's dev flag, in YOUR code.
|
|
92
|
-
if (import.meta.env.DEV) await import(
|
|
113
|
+
if (import.meta.env.DEV) await import("kerfjs/dev");
|
|
93
114
|
```
|
|
94
115
|
|
|
95
|
-
| Export
|
|
96
|
-
|
|
|
97
|
-
| `signal(initial)`
|
|
98
|
-
| `computed(fn)`
|
|
99
|
-
| `effect(fn)`
|
|
100
|
-
| `batch(fn)`
|
|
101
|
-
| `defineStore({initial, actions})`
|
|
102
|
-
| `resetAllStores()`
|
|
103
|
-
| `mount(el, render)`
|
|
104
|
-
| `morph(liveRoot, template)`
|
|
105
|
-
| `each(items, render, cacheKey?)`
|
|
106
|
-
| `each(items, render, { cacheKey, key })`
|
|
107
|
-
| `delegate(root, type, sel, h)`
|
|
108
|
-
| `delegateCapture(root, type, sel, h, opts?)` | capture-phase escape hatch; `closest()` walk-up by default (same as `delegate`); pass `{ match: 'direct' }` for strict `target.matches()`
|
|
109
|
-
| `attr(name, value)`
|
|
110
|
-
| `attr(name)`
|
|
111
|
-
| `toElement(jsx)`
|
|
112
|
-
| `raw(html)`
|
|
113
|
-
| `
|
|
114
|
-
|
|
|
116
|
+
| Export | Use |
|
|
117
|
+
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
118
|
+
| `signal(initial)` | atomic reactive state; `.value` get/set |
|
|
119
|
+
| `computed(fn)` | derived value, read-only |
|
|
120
|
+
| `effect(fn)` | side effect on signal change; returns disposer |
|
|
121
|
+
| `batch(fn)` | coalesce multiple writes into one re-run |
|
|
122
|
+
| `defineStore({initial, actions})` | named multi-consumer state |
|
|
123
|
+
| `resetAllStores()` | reset every store (test teardown) |
|
|
124
|
+
| `mount(el, render)` | bind reactive render to a DOM element; returns disposer |
|
|
125
|
+
| `morph(liveRoot, template)` | one-shot reconcile against a populated element (SSR hydration, page-refresh diffs). Template = `Element`, `SafeHtml`, or HTML string |
|
|
126
|
+
| `each(items, render, cacheKey?)` | keyed list iteration; per-row memoization on identity (+ optional cacheKey — a passive comparator for external state). Distinct from `data-key` on the rendered element |
|
|
127
|
+
| `each(items, render, { cacheKey, key })` | same, options form. **`key` gives the list a stable identity** — required whenever a _conditional_ list can render before this one, else kerf rebuilds this list and its rows lose focus/scroll/IME. A keyed list takes no positional slot, so keying the conditional list usually fixes its siblings too |
|
|
128
|
+
| `delegate(root, type, sel, h)` | one listener at the root; `closest(selector)` walk from target |
|
|
129
|
+
| `delegateCapture(root, type, sel, h, opts?)` | capture-phase escape hatch; `closest()` walk-up by default (same as `delegate`); pass `{ match: 'direct' }` for strict `target.matches()` |
|
|
130
|
+
| `attr(name, value)` | pre-computed `AttrSpec<N,V>` — `.selector` for `delegate()`, `.attrs` to spread into JSX (rename-safe) |
|
|
131
|
+
| `attr(name)` | dynamic factory — `attr<N,V=string>(name)` returns `(value: V) => { readonly [name]: V }`; both generics off → N inferred, V defaults to string; specify both to constrain values |
|
|
132
|
+
| `toElement(jsx)` | parse JSX into a DOM node (SVG-aware). Single-root → `Element`; multi-root (`<><svg/> label</>`, two icons side by side) → `DocumentFragment` that `appendChild`/`replaceChildren`/`append` inlines into the parent. |
|
|
133
|
+
| `raw(html)` | inject pre-escaped HTML |
|
|
134
|
+
| `JSXChildren` | recursive type for general-purpose component `children`: SafeHtml, primitives, read-only signals, nullish/boolean empty values, and readonly arrays at any depth. Use a narrower union for semantically restricted slots |
|
|
135
|
+
| `arraySignal(initial?)` | granular keyed-list signal (subpath `kerfjs/array-signal`); `each()` reconciles in O(patches) |
|
|
136
|
+
| `` html`…` `` | tagged template (subpath `kerfjs/html`) — JSX-identical runtime semantics with NO build step, for CDN/importmap projects. Real HTML attribute names (`class`, not `className`); holes only in text positions or as a COMPLETE attribute value (`attr=${v}` / `attr="${v}"`) |
|
|
115
137
|
|
|
116
138
|
## Hard rules — every AI assistant gets these wrong at least once
|
|
117
139
|
|
|
@@ -123,9 +145,9 @@ if (import.meta.env.DEV) await import('kerfjs/dev');
|
|
|
123
145
|
- `data-morph-preserve` — element survives the trailing-removal pass even when the new template doesn't emit it. For imperatively-injected children (autoplay video, tooltip overlay, analytics pixel). Does NOT block a keyed-match move.
|
|
124
146
|
4. **Never `addEventListener` inside a `mount()`-managed tree** unless under `data-morph-skip`. A morph re-render may discard the node. Use `delegate` / `delegateCapture` instead.
|
|
125
147
|
5. **Capture the `delegate()` / `delegateCapture()` disposer** whenever the registration's scope is shorter than the page. Both helpers return `() => void`; the listener closure pins `rootEl`, `handler`, and everything the handler closes over (stores, signals, app state). Discarding the disposer on a transient root (modal, route view, mount swap, dynamic widget) leaks the listener AND the app graph it captures; re-mount cycles stack listeners linearly. `mount()`'s own disposer does NOT remove delegates for you. Safe to discard only when the registration is truly page-lifetime (root is `document.body` or equivalent, attached once at startup, never torn down).
|
|
126
|
-
6. **One `mount()` per root.** Don't nest `mount()` calls. Compose with plain functions returning JSX.
|
|
148
|
+
6. **One `mount()` per root.** Don't nest `mount()` calls. Compose with plain functions returning JSX. One exception: a modal `<dialog>` of your own that anchors kerf `popover()` / `tooltip()` surfaces must contain `<div data-kerf-overlay-host data-morph-skip></div>` — kerf renders those surfaces into it (inside the modal, so they are not inert) and that slot is the one allowed nested-mount boundary. Dialogs `kerfjs/overlay` opens get a slot automatically.
|
|
127
149
|
7. **Components are plain functions.** `<MyComponent props />` works — the JSX runtime calls `MyComponent(props)` and uses the returned JSX — but there's no hook system, no lifecycle, and no per-instance state. State lives in module-scope signals or stores, never in component closures.
|
|
128
|
-
8. **Values bind, structure re-renders.** For a value hole, pass the signal/computed ITSELF (`<span>{count}</span>`, `class={sig}`) — kerf updates that one node directly, no render re-run. Read `.value` only when the JSX
|
|
150
|
+
8. **Values bind, structure re-renders.** For a value hole, pass the signal/computed ITSELF (`<span>{count}</span>`, `class={sig}`) — kerf updates that one node directly, no render re-run. Read `.value` only when the JSX _structure_ depends on the signal — and then the read must happen INSIDE the render function to be tracked: `const x = count.value; mount(el, () => <span>{x}</span>)` does NOT re-render. One caveat on bound holes: bind a STABLE signal/computed instance per hole (`class={computed(() => …)}` that switches internally), never `class={cond ? sigA : sigB}` — switching instances can go silently stale (detectable via `enableWarnings({ staleBinding: true })` (or `KERF_DEV_WARN_STALE_BINDING=1` under Node)). The idiom's endpoint: a render that reads NO `.value` runs exactly once — a fully bound mount never re-renders. To find `.value` holes worth migrating, `enableWarnings({ valueOnlyRerender: true })` (or `KERF_DEV_WARN_VALUE_ONLY_RERENDER=1` under Node) flags re-renders whose only differences were text/attribute values.
|
|
129
151
|
9. **Store actions take `(set, get)`, not `(state)`.** `set(next)` replaces state; mutating `get()` does nothing.
|
|
130
152
|
10. **Use `data-action` attributes, not inline `onClick`.** Inline handlers are NOT supported by the JSX → string runtime; delegate from the root.
|
|
131
153
|
11. **`arraySignal` is opt-in for long keyed lists** where most updates are pointwise. For short lists / filter+sort pipelines, plain `signal` + `each(items.value, ...)` is simpler and equally fast.
|
|
@@ -138,22 +160,26 @@ if (import.meta.env.DEV) await import('kerfjs/dev');
|
|
|
138
160
|
When deciding which primitive to reach for, work down the axes:
|
|
139
161
|
|
|
140
162
|
**Events.**
|
|
163
|
+
|
|
141
164
|
- Originates inside the mount tree → `delegate(rootEl, type, sel, handler)`. Originates outside (window-level keyboard, online/offline, beforeunload) → native `window.addEventListener` at module top-level.
|
|
142
165
|
- Gesture that needs to follow an element after press (drag, draw, resize) → at the start event, `el.setPointerCapture(e.pointerId)`. Subsequent `pointermove` / `pointerup` redirect to the captured element and `delegate(rootEl, 'pointermove', '[data-card]', …)` still picks them up. Don't reach for `window.addEventListener` for in-mount-tree gestures.
|
|
143
166
|
- Well-known non-bubbler (`focus`, `blur`, `scroll`, `load`, `error`, `mouseenter`, `mouseleave`) → still `delegate()`; it auto-promotes to capture. Custom non-bubblers or capture-phase interception → `delegateCapture()` (also `closest()`-matched by default). Need strict element-match? Add `{ match: 'direct' }` on either helper.
|
|
144
167
|
|
|
145
168
|
**Lists.**
|
|
169
|
+
|
|
146
170
|
- Items change across renders (todos, chat messages, table rows) → `each(items, render)`.
|
|
147
171
|
- Static structural enumeration whose row render reads signals → `STATIC.map(item => <jsx/>)`. Inner `each(item.children, …)` still gets keyed reconcile.
|
|
148
172
|
- Long list with point-wise mutations → `arraySignal` + `each(arraySig, render)` for O(patches) updates.
|
|
149
173
|
|
|
150
174
|
**Side effects / imperative DOM.**
|
|
175
|
+
|
|
151
176
|
- Library-owned subtree survives across renders → `data-morph-skip` on host.
|
|
152
177
|
- Host attributes morph but subtree preserved → `data-morph-skip-children`.
|
|
153
178
|
- Imperatively-injected element survives the trailing-removal pass → `data-morph-preserve`.
|
|
154
179
|
- Focused input / contenteditable caret survives re-renders → automatic; no opt-in.
|
|
155
180
|
|
|
156
181
|
**Raw HTML.**
|
|
182
|
+
|
|
157
183
|
- User-controlled HTML → sanitize first (DOMPurify) then `raw(sanitized)`.
|
|
158
184
|
- Author-controlled trusted HTML → `raw(html)` directly.
|
|
159
185
|
|
|
@@ -168,20 +194,27 @@ When deciding which primitive to reach for, work down the axes:
|
|
|
168
194
|
// directly with no render re-run; read `.value` only when the JSX STRUCTURE
|
|
169
195
|
// depends on the signal (conditionals, list shape).
|
|
170
196
|
const count = signal(0);
|
|
171
|
-
const ACTIONS = { inc: attr(
|
|
197
|
+
const ACTIONS = { inc: attr("data-action", "inc") } as const satisfies Record<
|
|
198
|
+
string,
|
|
199
|
+
AttrSpec<"data-action">
|
|
200
|
+
>;
|
|
172
201
|
|
|
173
|
-
mount(document.getElementById(
|
|
202
|
+
mount(document.getElementById("app")!, () => (
|
|
174
203
|
<div>
|
|
175
204
|
<button {...ACTIONS.inc.attrs}>+</button>
|
|
176
205
|
<span>{count}</span>
|
|
177
206
|
</div>
|
|
178
207
|
));
|
|
179
|
-
delegate(rootEl,
|
|
208
|
+
delegate(rootEl, "click", ACTIONS.inc.selector, () => {
|
|
209
|
+
count.value += 1;
|
|
210
|
+
});
|
|
180
211
|
|
|
181
212
|
// Pattern 2: keyed list with per-row memoization
|
|
182
213
|
mount(listEl, () => (
|
|
183
214
|
<ul>
|
|
184
|
-
{each(rows.value, (row) =>
|
|
215
|
+
{each(rows.value, (row) => (
|
|
216
|
+
<li data-key={row.id}>{row.label}</li>
|
|
217
|
+
))}
|
|
185
218
|
</ul>
|
|
186
219
|
));
|
|
187
220
|
|
|
@@ -189,14 +222,15 @@ mount(listEl, () => (
|
|
|
189
222
|
const cart = defineStore({
|
|
190
223
|
initial: () => ({ items: [] as string[] }),
|
|
191
224
|
actions: (set, get) => ({
|
|
192
|
-
add:
|
|
193
|
-
clear: ()
|
|
225
|
+
add: (id: string) => set({ items: [...get().items, id] }),
|
|
226
|
+
clear: () => set({ items: [] }),
|
|
194
227
|
}),
|
|
195
228
|
});
|
|
196
229
|
// access: cart.state.value.items, cart.actions.add('x'), cart.reset()
|
|
197
230
|
|
|
198
231
|
// Pattern 4: one-shot reconcile (no signals, no effect)
|
|
199
|
-
|
|
232
|
+
// the template describes liveCard's CHILDREN; liveCard's own attributes are untouched
|
|
233
|
+
morph(liveCard, '<h2 class="card-title">…</h2><p>…</p>');
|
|
200
234
|
|
|
201
235
|
// Pattern 5: fine-grained binding (opt-in) — pass the signal/computed ITSELF
|
|
202
236
|
// into a hole so a change updates ONLY that node (no render re-run, no
|
|
@@ -205,9 +239,15 @@ morph(liveCard, '<article class="card">…</article>');
|
|
|
205
239
|
const selectedId = signal<number | null>(null);
|
|
206
240
|
mount(listEl, () => (
|
|
207
241
|
<ul>
|
|
208
|
-
{each(
|
|
209
|
-
|
|
210
|
-
|
|
242
|
+
{each(
|
|
243
|
+
rows.value,
|
|
244
|
+
(row) => (
|
|
245
|
+
<li class={computed(() => (row.id === selectedId.value ? "sel" : ""))}>
|
|
246
|
+
{row.label}
|
|
247
|
+
</li>
|
|
248
|
+
),
|
|
249
|
+
(row) => row.id,
|
|
250
|
+
)}
|
|
211
251
|
</ul>
|
|
212
252
|
));
|
|
213
253
|
// selectedId.value = 3 → only the ~2 affected <li> class attrs update.
|
|
@@ -215,34 +255,39 @@ mount(listEl, () => (
|
|
|
215
255
|
// Pattern 6: no build step (CDN / importmap) — the html tagged template
|
|
216
256
|
// instead of JSX. Same runtime semantics; real HTML attribute names; a hole
|
|
217
257
|
// must be a text position or a COMPLETE attribute value (partials throw).
|
|
218
|
-
import { html } from
|
|
219
|
-
mount(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
258
|
+
import { html } from "kerfjs/html";
|
|
259
|
+
mount(
|
|
260
|
+
rootEl,
|
|
261
|
+
() => html`
|
|
262
|
+
<div class="${cls}">Count: ${count}</div>
|
|
263
|
+
<ul>
|
|
264
|
+
${each(rows.value, (row) => html`<li data-key="${row.id}">${row.label}</li>`)}
|
|
265
|
+
</ul>
|
|
266
|
+
`,
|
|
267
|
+
);
|
|
223
268
|
```
|
|
224
269
|
|
|
225
270
|
## Diagnosing common errors
|
|
226
271
|
|
|
227
|
-
| Error / symptom
|
|
228
|
-
|
|
|
229
|
-
| `JSX: DOM elements cannot be passed as children`
|
|
230
|
-
| `draggable={true}` / `spellCheck={false}` / `contentEditable={false}` / `writingsuggestions={false}` / `translate={false}` / `autocorrect={false}` won't typecheck | these are HTML **enumerated** attributes, not boolean ones — they take keyword strings (`"true"`/`"false"`; `"yes"`/`"no"` for `translate`; `"on"`/`"off"` for `autocorrect`), and omitting one selects a third state, so the boolean form rendered the opposite of what was meant
|
|
231
|
-
| `<select value={x}>` / `<textarea value={x}>` won't typecheck
|
|
232
|
-
| Focus / cursor lost on every keystroke
|
|
233
|
-
| Click handler stops firing after re-render
|
|
234
|
-
| Render fn never re-runs
|
|
235
|
-
| SVG renders as broken / namespaceless markup
|
|
236
|
-
| Library widget destroyed on every render
|
|
237
|
-
| `<my-tag>` fails to typecheck
|
|
238
|
-
| `each(): row render at index N produced K top-level elements`
|
|
239
|
-
| Drag/drop / state change has no visible effect; only elements
|
|
240
|
-
| Row-enter CSS animation no longer replays when only a row's
|
|
241
|
-
| Want a hot spot to update without re-running the whole render
|
|
242
|
-
|
|
|
243
|
-
| An `each()` list's rows lose focus / scroll / typing state when an unrelated conditional list above them appears or disappears (kerf warns about this in dev)
|
|
244
|
-
| Keyed `each()` list suddenly renders zero rows — only its `<!--kf-list:N-->` marker — with no errors, and it never recovers (kerfjs ≤ 2.0.1)
|
|
245
|
-
| A numbered / zebra-striped / "N of M" `each()` list shows the wrong number on rows that MOVED (reorder, or non-tail insert/remove), while unmoved rows look right
|
|
272
|
+
| Error / symptom | Root cause | Fix |
|
|
273
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
274
|
+
| `JSX: DOM elements cannot be passed as children` | passed a `toElement()` result inside JSX | Build the whole tree in JSX; refs via `querySelector` after rendering |
|
|
275
|
+
| `draggable={true}` / `spellCheck={false}` / `contentEditable={false}` / `writingsuggestions={false}` / `translate={false}` / `autocorrect={false}` won't typecheck | these are HTML **enumerated** attributes, not boolean ones — they take keyword strings (`"true"`/`"false"`; `"yes"`/`"no"` for `translate`; `"on"`/`"off"` for `autocorrect`), and omitting one selects a third state, so the boolean form rendered the opposite of what was meant | Write the keyword: `draggable="true"`, `spellCheck="false"`, `writingsuggestions="false"`, `translate="no"`, `autocorrect="off"`. Omit for the default state. Real boolean attrs (`hidden`, `checked`, `disabled`, `autofocus`, `required`, `inert`) are unaffected, as is `popover` (bare form = the spec's `auto` state); for a signal use `signal('true')` |
|
|
276
|
+
| `<select value={x}>` / `<textarea value={x}>` won't typecheck | neither element has a `value` content attribute — the markup was inert | `<option value="b" selected>`; `<textarea>{draft}</textarea>` |
|
|
277
|
+
| Focus / cursor lost on every keystroke | list items lack `data-key` | Add `data-key` (or `id`) to each list item |
|
|
278
|
+
| Click handler stops firing after re-render | `el.addEventListener` was used | Replace with `delegate(rootEl, 'click', ACTIONS.foo.selector, ...)` (or a string literal for ad-hoc cases) |
|
|
279
|
+
| Render fn never re-runs | signal was read outside the render fn | Move `signal.value` read inside the render fn |
|
|
280
|
+
| SVG renders as broken / namespaceless markup | `innerHTML` used directly | Use `mount` or `toElement` (SVG-aware) |
|
|
281
|
+
| Library widget destroyed on every render | host reachable by the morph | Wrap host in `data-morph-skip`; mount the library imperatively after first render |
|
|
282
|
+
| `<my-tag>` fails to typecheck | declaration merging targeted global JSX | Use `declare module 'kerfjs/jsx-runtime' { namespace JSX { … } }` instead |
|
|
283
|
+
| `each(): row render at index N produced K top-level elements` | row returned multiple sibling elements or zero | Wrap them in one parent so the row renders exactly one element |
|
|
284
|
+
| Drag/drop / state change has no visible effect; only elements _outside_ `each()` update | Used `each(STATIC_ARRAY, …)` whose row render reads signals. Items never change identity → cache hits forever → row render never re-invoked → signal reads stop tracking | Replace outer with `STATIC_ARRAY.map(...)`; keep inner `each()` for the dynamic sub-list. See Hard Rule 14 |
|
|
285
|
+
| Row-enter CSS animation no longer replays when only a row's _content_ changed (kerf ≥ 0.15.0) | 0.15.0+ morphs a same-identity, same-position row _in place_ instead of recreating its node, so a mount-keyed `@keyframes` never re-triggers on a content-only update (≤ 0.14.x recreated the node, so it fired). Intentional flip side: focus, scroll, IME, and in-progress transitions now survive | Key the animation on a state-class toggle, not element creation. To force a remount, churn the row's identity (new object ref / `data-key`) so the reconciler replaces the node |
|
|
286
|
+
| Want a hot spot to update without re-running the whole render | Fine-grained binding: pass the signal/`computed` ITSELF into the attr/text hole (`class={computed(() => …)}`), not `.value`. Use `computed()` not a bare `() => …` (memoization keeps a shared-signal flip to ~O(changed nodes)). Opt-in per hole. Limit: a bound hole depending on the row's OWN mutated data goes stale on a granular in-place update — use plain interpolation there |
|
|
287
|
+
| ` html` ``: partial attribute values are not supported | In `kerfjs/html` templates a hole must be the COMPLETE attribute value | Build the full string first (``class="${`a ${b}`}"``), or bind `class="${computed(() => `a ${b.value}`)}"` for a reactive one |
|
|
288
|
+
| An `each()` list's rows lose focus / scroll / typing state when an unrelated conditional list above them appears or disappears (kerf warns about this in dev) | Lists without a key are identified by their position among the render's `each()` calls, so adding/removing one above shifts this list's identity and kerf rebuilds it | Give the lists stable keys: `each(items, render, { key: 'results' })`. Keying just the conditional list is usually enough |
|
|
289
|
+
| Keyed `each()` list suddenly renders zero rows — only its `<!--kf-list:N-->` marker — with no errors, and it never recovers (kerfjs ≤ 2.0.1) | A conditionally-rendered sibling BEFORE the list (possibly higher in the tree, e.g. an error banner) was removed that render; older kerfjs rebuilt the shifted list container from the template, permanently detaching the list's internal binding | Upgrade kerfjs (fixed after 2.0.1 — the morph now moves the shifted container up in place, keeping node identity). On older versions, keep the structure before the list stable: wrap the conditional in an always-present container (`<div class="banners">{cond ? <div/> : ''}</div>`) |
|
|
290
|
+
| A numbered / zebra-striped / "N of M" `each()` list shows the wrong number on rows that MOVED (reorder, or non-tail insert/remove), while unmoved rows look right | The render fn's `index` argument is NOT part of the memo key (only item identity + `cacheKey` + content version are), so a row that keeps identity but changes position keeps the HTML it rendered at its old index | Fold the index into the memo key so displaced rows re-render: `each(items, (it, i) => …, { cacheKey: (_, i) => i })` (add `key` if used). Opt-in dev warn: `enableWarnings({ staleIndex: true })` (or `KERF_DEV_WARN_STALE_INDEX=1` under Node) |
|
|
246
291
|
|
|
247
292
|
## Workflow guidance
|
|
248
293
|
|
package/dist/actions.d.ts
CHANGED
package/dist/actions.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { attr } from './chunk-
|
|
2
|
-
import {
|
|
1
|
+
import { attr } from './chunk-MK42GLPV.js';
|
|
2
|
+
import { _delegate, NON_BUBBLING } from './chunk-JN7EJM3B.js';
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
// src/actions.ts
|
|
@@ -9,15 +9,19 @@ function action(value) {
|
|
|
9
9
|
}
|
|
10
10
|
function delegateActions(root, eventType, table, options) {
|
|
11
11
|
const attrName = options?.attr ?? DEFAULT_ACTION_ATTR;
|
|
12
|
-
return
|
|
12
|
+
return _delegate(
|
|
13
13
|
root,
|
|
14
14
|
eventType,
|
|
15
15
|
`[${attrName}]`,
|
|
16
16
|
(event, el) => {
|
|
17
|
-
const
|
|
18
|
-
|
|
17
|
+
const value = el.getAttribute(attrName);
|
|
18
|
+
return () => {
|
|
19
|
+
if (Object.hasOwn(table, value)) table[value](event, el);
|
|
20
|
+
};
|
|
19
21
|
},
|
|
20
|
-
options
|
|
22
|
+
options,
|
|
23
|
+
NON_BUBBLING.has(eventType),
|
|
24
|
+
"delegate"
|
|
21
25
|
);
|
|
22
26
|
}
|
|
23
27
|
|
package/dist/actions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/actions.ts"],"names":[],"mappings":";;;;;AAmCA,IAAM,mBAAA,GAAsB,aAAA;
|
|
1
|
+
{"version":3,"sources":["../src/actions.ts"],"names":[],"mappings":";;;;;AAmCA,IAAM,mBAAA,GAAsB,aAAA;AAkBrB,SAAS,OAAyB,KAAA,EAAsC;AAC7E,EAAA,OAAO,IAAA,CAAK,qBAAqB,KAAK,CAAA;AACxC;AAwBO,SAAS,eAAA,CACd,IAAA,EACA,SAAA,EACA,KAAA,EACA,OAAA,EACY;AACZ,EAAA,MAAM,QAAA,GAAW,SAAS,IAAA,IAAQ,mBAAA;AAKlC,EAAA,OAAO,SAAA;AAAA,IACL,IAAA;AAAA,IACA,SAAA;AAAA,IACA,IAAI,QAAQ,CAAA,CAAA,CAAA;AAAA,IACZ,CAAC,OAAO,EAAA,KAAO;AAEb,MAAA,MAAM,KAAA,GAAQ,EAAA,CAAG,YAAA,CAAa,QAAQ,CAAA;AAKtC,MAAA,OAAO,MAAM;AACX,QAAA,IAAI,MAAA,CAAO,OAAO,KAAA,EAAO,KAAK,GAAG,KAAA,CAAM,KAAK,CAAA,CAAE,KAAA,EAAO,EAAO,CAAA;AAAA,MAC9D,CAAA;AAAA,IACF,CAAA;AAAA,IACA,OAAA;AAAA,IACA,YAAA,CAAa,IAAI,SAAS,CAAA;AAAA,IAC1B;AAAA,GACF;AACF","file":"actions.js","sourcesContent":["/**\n * `kerfjs/actions` — the delegated action-table helper.\n *\n * The most-reinvented idiom across real kerf apps: one table of `data-action`\n * attribute specs used as the single source of truth for BOTH the JSX attribute\n * and the delegate selector, plus a hand-rolled `switch (dataset.action)`\n * dispatcher. This subpath blesses it as two thin helpers over the existing\n * `attr()` + `delegate()` — it does NOT replace them.\n *\n * import { action, delegateActions } from 'kerfjs/actions';\n *\n * const A = {\n * select: action('select-file'),\n * remove: action('remove-file'),\n * };\n *\n * // JSX — spread the attr (rename-safe; no hardcoded attribute name):\n * // <button {...A.select.attrs} data-id={id}>…</button>\n *\n * // Wire the whole table with ONE delegated listener; returns a disposer:\n * const dispose = delegateActions(root, 'click', {\n * [A.select.value]: (_e, el) => selectFile(el.getAttribute('data-id')),\n * [A.remove.value]: (_e, el) => removeFile(el.getAttribute('data-id')),\n * });\n *\n * Contract: `delegateActions` returns a `() => void` disposer and holds no\n * per-instance state — the same shape as `delegate()`, which it builds on (so\n * it inherits the single-listener dispatch and the capture auto-promotion for\n * well-known non-bubbling event types). One event type per call, mirroring\n * `delegate()`; collect the disposers for a root that needs several.\n */\nimport { attr, type AttrSpec } from './attr.js';\nimport { _delegate, type DelegateOptions, NON_BUBBLING } from './delegate.js';\n\n/** The attribute an action table keys on by default. */\nconst DEFAULT_ACTION_ATTR = 'data-action';\n\n/**\n * A handler in a {@link delegateActions} table. Receives the DOM event and the\n * matched element (walk-up `closest()` match by default) — the same shape as a\n * `delegate()` handler.\n */\nexport type ActionHandler<E extends Element = Element> = (\n event: Event,\n el: E,\n) => void;\n\n/**\n * `action(value)` — an {@link AttrSpec} on `data-action`. A thin specialization\n * of `attr('data-action', value)`: spread its `.attrs` in JSX and use its\n * `.value` as the handler-table key, so the action name lives in exactly one\n * place and can't drift between the markup and the dispatcher.\n */\nexport function action<V extends string>(value: V): AttrSpec<'data-action', V> {\n return attr(DEFAULT_ACTION_ATTR, value);\n}\n\n/** Options for {@link delegateActions}. Extends {@link DelegateOptions}. */\nexport interface DelegateActionsOptions extends DelegateOptions {\n /**\n * The attribute the table keys on. Default `'data-action'`. Override it only\n * if you also author the specs with `attr(yourName, …)` instead of `action()`.\n */\n attr?: string;\n}\n\n/**\n * Wire a whole table of action handlers with ONE delegated listener.\n *\n * On `eventType`, the nearest element carrying the action attribute (walk-up\n * `closest()` by default; pass `{ match: 'direct' }` for an exact-element match)\n * is looked up in `table` by its attribute value, and the matching handler\n * runs. An element whose action is absent from the table is ignored — the same\n * behavior as a `switch (dataset.action)` with no matching `case`.\n *\n * Returns a `() => void` disposer. One event type per call (the smallest\n * surface, mirroring `delegate()`); collect the disposers when a root needs\n * several event types.\n */\nexport function delegateActions<E extends Element = Element>(\n root: HTMLElement,\n eventType: string,\n table: Readonly<Record<string, ActionHandler<E>>>,\n options?: DelegateActionsOptions,\n): () => void {\n const attrName = options?.attr ?? DEFAULT_ACTION_ATTR;\n // The action value is read when the event STARTS dispatching (with the\n // match itself — see `_delegate`), not from the live element when the\n // handler runs: an earlier handler's synchronous re-render may have\n // recycled the matched element into a different action by then.\n return _delegate(\n root,\n eventType,\n `[${attrName}]`,\n (event, el) => {\n // `el` matched `[${attrName}]`, so the attribute is always present.\n const value = el.getAttribute(attrName) as string;\n // Own entries only: the table is an ordinary object, so a bare\n // `table[value]` would resolve Object.prototype names (`__proto__`,\n // `constructor`, `toString`, `hasOwnProperty`, …) and dispatch or throw\n // on markup that names no action in the table.\n return () => {\n if (Object.hasOwn(table, value)) table[value](event, el as E);\n };\n },\n options,\n NON_BUBBLING.has(eventType),\n 'delegate',\n );\n}\n"]}
|
package/dist/array-signal.js
CHANGED
package/dist/async.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { signal } from './chunk-
|
|
1
|
+
import { signal } from './chunk-IHMDBBUJ.js';
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
// src/async.ts
|
|
@@ -49,7 +49,13 @@ function resource(options = {}) {
|
|
|
49
49
|
};
|
|
50
50
|
const fail = (error) => {
|
|
51
51
|
if (gen === generation) {
|
|
52
|
-
state.value = {
|
|
52
|
+
state.value = {
|
|
53
|
+
...state.value,
|
|
54
|
+
status: "failed",
|
|
55
|
+
error,
|
|
56
|
+
progress: void 0,
|
|
57
|
+
input
|
|
58
|
+
};
|
|
53
59
|
}
|
|
54
60
|
return void 0;
|
|
55
61
|
};
|
|
@@ -59,23 +65,20 @@ function resource(options = {}) {
|
|
|
59
65
|
} catch (error) {
|
|
60
66
|
return Promise.resolve(fail(error));
|
|
61
67
|
}
|
|
62
|
-
return pending.then(
|
|
63
|
-
(
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
},
|
|
77
|
-
fail
|
|
78
|
-
);
|
|
68
|
+
return pending.then((data) => {
|
|
69
|
+
if (gen === generation) {
|
|
70
|
+
if (key !== void 0) cache.set(key, data);
|
|
71
|
+
state.value = {
|
|
72
|
+
status: "completed",
|
|
73
|
+
data,
|
|
74
|
+
error: void 0,
|
|
75
|
+
progress: void 0,
|
|
76
|
+
input,
|
|
77
|
+
revision: commit(data)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return data;
|
|
81
|
+
}, fail);
|
|
79
82
|
}
|
|
80
83
|
function reset() {
|
|
81
84
|
generation++;
|