kerfjs 5.0.0-beta.52 → 5.0.0-beta.53

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 (56) hide show
  1. package/CHANGELOG.md +329 -0
  2. package/README.md +4 -3
  3. package/ai/cursorrules +6 -5
  4. package/ai/manifest.json +9 -7
  5. package/ai/skill.md +6 -5
  6. package/dist/actions.js +3 -3
  7. package/dist/actions.js.map +1 -1
  8. package/dist/array-signal.js +1 -1
  9. package/dist/async.js +1 -1
  10. package/dist/attach.js +1 -68
  11. package/dist/attach.js.map +1 -1
  12. package/dist/{chunk-CEQMZYLR.js → chunk-3C6F6NM4.js} +1 -1
  13. package/dist/{chunk-CEQMZYLR.js.map → chunk-3C6F6NM4.js.map} +1 -1
  14. package/dist/{chunk-ZOIERTUW.js → chunk-BI4HGICV.js} +3 -3
  15. package/dist/{chunk-ZOIERTUW.js.map → chunk-BI4HGICV.js.map} +1 -1
  16. package/dist/{chunk-3GSWKMBZ.js → chunk-DU35YGKW.js} +3 -3
  17. package/dist/{chunk-3GSWKMBZ.js.map → chunk-DU35YGKW.js.map} +1 -1
  18. package/dist/chunk-FM6N2B4N.js +70 -0
  19. package/dist/chunk-FM6N2B4N.js.map +1 -0
  20. package/dist/{chunk-ELXVRKY2.js → chunk-H7WTRM6F.js} +2 -2
  21. package/dist/{chunk-ELXVRKY2.js.map → chunk-H7WTRM6F.js.map} +1 -1
  22. package/dist/{chunk-H3TTM7PQ.js → chunk-HTXQR2KL.js} +108 -106
  23. package/dist/chunk-HTXQR2KL.js.map +1 -0
  24. package/dist/{chunk-Y2FOYPBV.js → chunk-IHMDBBUJ.js} +2 -2
  25. package/dist/chunk-IHMDBBUJ.js.map +1 -0
  26. package/dist/{chunk-E5R5GNKE.js → chunk-X6OQ2HTR.js} +2 -2
  27. package/dist/{chunk-E5R5GNKE.js.map → chunk-X6OQ2HTR.js.map} +1 -1
  28. package/dist/dev.d.ts +31 -13
  29. package/dist/dev.js +34 -18
  30. package/dist/dev.js.map +1 -1
  31. package/dist/html.js +2 -2
  32. package/dist/index.d.ts +12 -7
  33. package/dist/index.js +11 -11
  34. package/dist/index.js.map +1 -1
  35. package/dist/jsx-runtime.js +1 -1
  36. package/dist/list.d.ts +3 -2
  37. package/dist/list.js +27 -13
  38. package/dist/list.js.map +1 -1
  39. package/dist/overlay.d.ts +0 -22
  40. package/dist/overlay.js +107 -30
  41. package/dist/overlay.js.map +1 -1
  42. package/dist/remount.js +2 -2
  43. package/dist/router.d.ts +13 -4
  44. package/dist/router.js +8 -4
  45. package/dist/router.js.map +1 -1
  46. package/dist/scope.d.ts +10 -2
  47. package/dist/scope.js +22 -28
  48. package/dist/scope.js.map +1 -1
  49. package/dist/testing.js +1 -1
  50. package/dist/timing.d.ts +3 -2
  51. package/dist/timing.js +1 -1
  52. package/dist/timing.js.map +1 -1
  53. package/llms.txt +1 -1
  54. package/package.json +1 -1
  55. package/dist/chunk-H3TTM7PQ.js.map +0 -1
  56. package/dist/chunk-Y2FOYPBV.js.map +0 -1
@@ -0,0 +1,70 @@
1
+ // src/attach.ts
2
+ function attach(node, setup) {
3
+ const teardown = setup(node);
4
+ let done = false;
5
+ let hasConnected = node.isConnected;
6
+ let observedRoot;
7
+ let connectionFrame = 0;
8
+ const finish = () => {
9
+ if (done) return;
10
+ done = true;
11
+ globalThis.cancelAnimationFrame(connectionFrame);
12
+ observer.disconnect();
13
+ if (typeof teardown === "function") teardown();
14
+ };
15
+ const addedWithin = (candidate) => {
16
+ let current = node;
17
+ while (current !== null) {
18
+ if (current === candidate) return true;
19
+ const root = current.getRootNode();
20
+ current = current.parentNode ?? (root instanceof ShadowRoot ? root.host : null);
21
+ }
22
+ return false;
23
+ };
24
+ const observer = new MutationObserver((records) => {
25
+ if (done) return;
26
+ if (!hasConnected) {
27
+ if (!node.isConnected) {
28
+ const connectedAndRemovedInBatch = records.some(
29
+ (record) => [...record.addedNodes].some(addedWithin)
30
+ );
31
+ if (!connectedAndRemovedInBatch) return;
32
+ hasConnected = true;
33
+ finish();
34
+ return;
35
+ }
36
+ hasConnected = true;
37
+ globalThis.cancelAnimationFrame(connectionFrame);
38
+ } else if (!node.isConnected) {
39
+ finish();
40
+ return;
41
+ }
42
+ const root = node.getRootNode();
43
+ if (root !== observedRoot) observe(root);
44
+ });
45
+ const observe = (root) => {
46
+ observer.disconnect();
47
+ observedRoot = root;
48
+ const options = { childList: true, subtree: true };
49
+ observer.observe(node.ownerDocument, options);
50
+ if (root !== node.ownerDocument) observer.observe(root, options);
51
+ };
52
+ const watchForConnection = () => {
53
+ connectionFrame = globalThis.requestAnimationFrame(() => {
54
+ if (done || hasConnected) return;
55
+ if (node.isConnected) {
56
+ hasConnected = true;
57
+ observe(node.getRootNode());
58
+ return;
59
+ }
60
+ watchForConnection();
61
+ });
62
+ };
63
+ observe(hasConnected ? node.getRootNode() : node.ownerDocument);
64
+ if (!hasConnected) watchForConnection();
65
+ return finish;
66
+ }
67
+
68
+ export { attach };
69
+
70
+ //# sourceMappingURL=chunk-FM6N2B4N.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/attach.ts"],"names":[],"mappings":"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","file":"chunk-FM6N2B4N.js","sourcesContent":["/**\n * `kerfjs/attach` — bind a non-kerf widget's lifecycle to a single DOM node.\n *\n * `data-morph-skip` lets a library own a subtree so kerf won't touch it — but\n * nothing manages that widget's LIFECYCLE. You set it up imperatively after\n * render and must remember to tear it down when the node is replaced/removed\n * (dropping document-level listeners the widget added, etc.). `attach` closes\n * that seam: run a setup against one **existing** DOM node and auto-run its\n * teardown when that node leaves the document.\n *\n * import { attach } from 'kerfjs/attach';\n *\n * attach(canvasEl, (el) => {\n * const chart = D3.mount(el);\n * return () => chart.destroy(); // runs when el leaves the DOM (or on dispose)\n * });\n *\n * `setup(node)` runs immediately — the node already exists, so there is nothing\n * to wait for (this is NOT React's `useEffect`: no dependency array, no re-run,\n * no render-phase or hook-order scoping; it is closer to a Web Component's\n * `connectedCallback`/`disconnectedCallback` pair, Svelte's\n * `onMount(() => () => cleanup)`, or Solid's `onCleanup`). A node may start\n * disconnected: setup still runs now, and teardown waits until it has first\n * connected and subsequently leaves the document. The returned teardown runs\n * once — whichever comes first — on that removal (detected by a\n * `MutationObserver`, so a morph swap, a `remountOn` replacement, or any removal\n * triggers it) or when the returned disposer is called. Re-creation is NOT\n * handled here: a fresh node is a fresh `attach()` call — pair it with\n * `kerfjs/remount`, which replaces the node and re-runs your render (and thus\n * this call) on the new one.\n *\n * Related: `kerfjs/scope`'s `observeRemovals` also auto-disposes on removal via a\n * `MutationObserver`, but scoped to a whole subtree's registered disposers rather\n * than one node's setup/teardown pair — reach for that when you're collecting\n * many disposers under an element, and for `attach` when you're binding one\n * widget's lifecycle to one node.\n */\n\n/** The setup callback for {@link attach}: run against `node`, optionally return a teardown. */\nexport type AttachSetup = (node: Element) => (() => void) | void;\n\n/**\n * Run `setup(node)` now, and its returned teardown once — after `node` has been\n * connected and then leaves the document, or when the returned disposer is\n * called, whichever is first. Returns an idempotent disposer so a `mount()` /\n * `Scope` can drive teardown explicitly.\n */\nexport function attach(node: Element, setup: AttachSetup): () => void {\n const teardown = setup(node);\n let done = false;\n let hasConnected = node.isConnected;\n let observedRoot: Node | undefined;\n let connectionFrame = 0;\n\n const finish = (): void => {\n if (done) return;\n done = true;\n globalThis.cancelAnimationFrame(connectionFrame);\n observer.disconnect();\n if (typeof teardown === 'function') teardown();\n };\n\n const addedWithin = (candidate: Node): boolean => {\n let current: Node | null = node;\n while (current !== null) {\n if (current === candidate) return true;\n const root = current.getRootNode();\n current =\n current.parentNode ?? (root instanceof ShadowRoot ? root.host : null);\n }\n return false;\n };\n\n const observer = new MutationObserver((records) => {\n if (done) return;\n\n if (!hasConnected) {\n // A node may be prepared before insertion. Its own detached root cannot\n // observe becoming someone else's child, so wait on ownerDocument until\n // it becomes connected; only a later disconnection is a teardown event.\n if (!node.isConnected) {\n const connectedAndRemovedInBatch = records.some((record) =>\n [...record.addedNodes].some(addedWithin),\n );\n if (!connectedAndRemovedInBatch) return;\n hasConnected = true;\n finish();\n return;\n }\n hasConnected = true;\n globalThis.cancelAnimationFrame(connectionFrame);\n } else if (!node.isConnected) {\n finish();\n return;\n }\n\n // A connected node can move between documents or shadow roots without\n // ending its lifecycle. Follow its live root so a later removal remains\n // observable after that move.\n const root = node.getRootNode();\n if (root !== observedRoot) observe(root);\n });\n\n const observe = (root: Node): void => {\n observer.disconnect();\n observedRoot = root;\n const options = { childList: true, subtree: true } as const;\n observer.observe(node.ownerDocument, options);\n // Document observation sees a shadow host leave the document, while shadow\n // observation sees the node (or one of its in-shadow ancestors) removed.\n // Both are required to cover the node's composed lifecycle.\n if (root !== node.ownerDocument) observer.observe(root, options);\n };\n\n const watchForConnection = (): void => {\n connectionFrame = globalThis.requestAnimationFrame(() => {\n if (done || hasConnected) return;\n if (node.isConnected) {\n hasConnected = true;\n observe(node.getRootNode());\n return;\n }\n watchForConnection();\n });\n };\n\n // Connected nodes are watched from their live document/shadow root. For a\n // disconnected node, watch ownerDocument plus animation frames: the document\n // catches ordinary insertion, while the frame check also catches direct\n // insertion across a shadow boundary that document observation cannot see.\n observe(hasConnected ? node.getRootNode() : node.ownerDocument);\n if (!hasConnected) watchForConnection();\n\n return finish;\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { bumpItemVersion } from './chunk-5WRGJZV6.js';
2
- import { signal } from './chunk-Y2FOYPBV.js';
2
+ import { signal } from './chunk-IHMDBBUJ.js';
3
3
 
4
4
  // src/array-signal.ts
5
5
  var ARRAY_SIGNAL_BRAND = /* @__PURE__ */ Symbol.for("kerfjs.ArraySignal");
@@ -106,4 +106,4 @@ function arraySignal(initial = []) {
106
106
 
107
107
  export { ARRAY_SIGNAL_BRAND, ArraySignal, arraySignal };
108
108
 
109
- //# sourceMappingURL=chunk-ELXVRKY2.js.map
109
+ //# sourceMappingURL=chunk-H7WTRM6F.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/array-signal.ts"],"names":[],"mappings":";;;;AA6CO,IAAM,kBAAA,mBAAqB,MAAA,CAAO,GAAA,CAAI,oBAAoB;AAEjE,SAAS,YAAA,CACP,KAAA,EACA,MAAA,EACA,QAAA,GAAW,KAAA,EACF;AACT,EAAA,OACE,MAAA,CAAO,UAAU,KAAK,CAAA,IACtB,SAAS,CAAA,KACR,QAAA,GAAW,KAAA,IAAS,MAAA,GAAS,KAAA,GAAQ,MAAA,CAAA;AAE1C;AAEO,IAAM,cAAN,MAAqB;AAAA,EAClB,MAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA;AAAA,EAER,CAAU,kBAAkB,IAAI,IAAA;AAAA,EAEhC,WAAA,CAAY,OAAA,GAAwB,EAAC,EAAG;AACtC,IAAA,IAAA,CAAK,MAAA,GAAS,CAAC,GAAG,OAAO,CAAA;AACzB,IAAA,IAAA,CAAK,QAAA,GAAW,OAAO,CAAC,CAAA;AACxB,IAAA,IAAA,CAAK,WAAW,EAAC;AAAA,EACnB;AAAA;AAAA,EAGA,IAAI,KAAA,GAAsB;AAExB,IAAA,KAAK,KAAK,QAAA,CAAS,KAAA;AACnB,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAA,CAAO,OAAe,EAAA,EAA0B;AAC9C,IAAA,IAAI,CAAC,YAAA,CAAa,KAAA,EAAO,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA,EAAG;AAC5C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,0BAAA,EAA6B,KAAK,CAAA,mBAAA,EAAsB,IAAA,CAAK,OAAO,MAAM,CAAA,EAAA;AAAA,OAC5E;AAAA,IACF;AACA,IAAA,MAAM,IAAA,GAAO,EAAA,CAAG,IAAA,CAAK,MAAA,CAAO,KAAK,CAAC,CAAA;AAClC,IAAA,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA,GAAI,IAAA;AACrB,IAAA,IAAA,CAAK,QAAA,CAAS,KAAK,EAAE,IAAA,EAAM,UAAU,KAAA,EAAO,IAAA,EAAM,MAAM,CAAA;AAOxD,IAAA,eAAA,CAAgB,IAAI,CAAA;AACpB,IAAA,IAAA,CAAK,QAAA,CAAS,KAAA,EAAA;AAAA,EAChB;AAAA;AAAA,EAGA,MAAA,CAAO,OAAe,IAAA,EAAe;AACnC,IAAA,IAAI,CAAC,YAAA,CAAa,KAAA,EAAO,KAAK,MAAA,CAAO,MAAA,EAAQ,IAAI,CAAA,EAAG;AAClD,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,0BAAA,EAA6B,KAAK,CAAA,mBAAA,EAAsB,IAAA,CAAK,OAAO,MAAM,CAAA,EAAA;AAAA,OAC5E;AAAA,IACF;AACA,IAAA,IAAA,CAAK,MAAA,CAAO,MAAA,CAAO,KAAA,EAAO,CAAA,EAAG,IAAI,CAAA;AACjC,IAAA,IAAA,CAAK,SAAS,IAAA,CAAK,EAAE,MAAM,QAAA,EAAU,KAAA,EAAO,MAAM,CAAA;AAClD,IAAA,IAAA,CAAK,QAAA,CAAS,KAAA,EAAA;AAAA,EAChB;AAAA;AAAA,EAGA,KAAK,IAAA,EAAe;AAClB,IAAA,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,MAAA,CAAO,MAAA,EAAQ,IAAI,CAAA;AAAA,EACtC;AAAA;AAAA,EAGA,OAAO,KAAA,EAAkB;AACvB,IAAA,IAAI,CAAC,YAAA,CAAa,KAAA,EAAO,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA,EAAG;AAC5C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,0BAAA,EAA6B,KAAK,CAAA,mBAAA,EAAsB,IAAA,CAAK,OAAO,MAAM,CAAA,EAAA;AAAA,OAC5E;AAAA,IACF;AACA,IAAA,MAAM,CAAC,OAAO,CAAA,GAAI,KAAK,MAAA,CAAO,MAAA,CAAO,OAAO,CAAC,CAAA;AAC7C,IAAA,IAAA,CAAK,SAAS,IAAA,CAAK,EAAE,IAAA,EAAM,QAAA,EAAU,OAAO,CAAA;AAC5C,IAAA,IAAA,CAAK,QAAA,CAAS,KAAA,EAAA;AACd,IAAA,OAAO,OAAA;AAAA,EACT;AAAA;AAAA,EAGA,IAAA,CAAK,MAAc,EAAA,EAAkB;AACnC,IAAA,IACE,CAAC,YAAA,CAAa,IAAA,EAAM,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA,IACtC,CAAC,YAAA,CAAa,EAAA,EAAI,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA,EACpC;AACA,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,iDAAiD,IAAI,CAAA,KAAA,EAAQ,EAAE,CAAA,SAAA,EAAY,IAAA,CAAK,OAAO,MAAM,CAAA,EAAA;AAAA,OAC/F;AAAA,IACF;AACA,IAAA,IAAI,SAAS,EAAA,EAAI;AACjB,IAAA,MAAM,CAAC,IAAI,CAAA,GAAI,KAAK,MAAA,CAAO,MAAA,CAAO,MAAM,CAAC,CAAA;AACzC,IAAA,IAAA,CAAK,MAAA,CAAO,MAAA,CAAO,EAAA,EAAI,CAAA,EAAG,IAAI,CAAA;AAC9B,IAAA,IAAA,CAAK,SAAS,IAAA,CAAK,EAAE,MAAM,MAAA,EAAQ,IAAA,EAAM,IAAI,CAAA;AAC7C,IAAA,IAAA,CAAK,QAAA,CAAS,KAAA,EAAA;AAAA,EAChB;AAAA;AAAA,EAGA,QAAQ,KAAA,EAA2B;AACjC,IAAA,IAAA,CAAK,MAAA,GAAS,CAAC,GAAG,KAAK,CAAA;AACvB,IAAA,IAAA,CAAK,QAAA,CAAS,KAAK,EAAE,IAAA,EAAM,WAAW,KAAA,EAAO,IAAA,CAAK,QAAQ,CAAA;AAC1D,IAAA,IAAA,CAAK,QAAA,CAAS,KAAA,EAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,eAAA,GAAmC;AACjC,IAAA,MAAM,MAAM,IAAA,CAAK,QAAA;AACjB,IAAA,IAAA,CAAK,WAAW,EAAC;AACjB,IAAA,OAAO,GAAA;AAAA,EACT;AACF;AAGO,SAAS,WAAA,CAAe,OAAA,GAAwB,EAAC,EAAmB;AACzE,EAAA,OAAO,IAAI,YAAY,OAAO,CAAA;AAChC","file":"chunk-ELXVRKY2.js","sourcesContent":["/**\n * `arraySignal(initial)` — granular collection signal.\n *\n * A keyed-list-friendly variant of `signal()` that emits typed patch events\n * for every mutation (update / insert / remove / move / replace). When such\n * a signal is bound to `each(...)` inside a `mount()`, the keyed list\n * reconciler applies just the patches against the live DOM — no per-item\n * iteration, no `classifyItems` Map build, no LIS pass over unchanged rows.\n *\n * const rows = arraySignal<Row>([]);\n *\n * rows.update(42, (r) => ({ ...r, label: 'changed' })); // 1 update event\n * rows.insert(0, { id: 'x', ... }); // 1 insert event\n * rows.remove(7); // 1 remove event\n * rows.move(3, 0); // 1 move event\n * rows.replace([...]); // falls back to snapshot reconcile\n *\n * Read-side semantics match a regular signal: `arraySig.value` is a\n * snapshot, and reads inside `effect()` / `computed()` register as\n * dependencies, so derived values keep working.\n */\n\nimport { bumpItemVersion } from './item-version.js';\nimport type { Signal } from './reactive.js';\nimport { signal } from './reactive.js';\n\n/** A single granular mutation event. */\nexport type ArrayPatch<T> =\n | { type: 'update'; index: number; item: T }\n | { type: 'insert'; index: number; item: T }\n | { type: 'remove'; index: number }\n | { type: 'move'; from: number; to: number }\n | { type: 'replace'; items: readonly T[] };\n\n/**\n * Cross-bundle brand for `ArraySignal` instances. `each()` and the\n * granular reconciler check for this brand instead of `instanceof\n * ArraySignal`, so the main `kerfjs` barrel can detect arraySignal\n * inputs without importing the class at runtime — the class lives\n * only in the `kerfjs/array-signal` subpath, so apps that don't need\n * granular collections shed ~1 KB.\n *\n * Same `Symbol.for(...)`-based pattern as `SafeHtml` (KF-14): cross-\n * bundle-safe, zero-cost runtime check.\n */\nexport const ARRAY_SIGNAL_BRAND = Symbol.for('kerfjs.ArraySignal');\n\nfunction isValidIndex(\n index: number,\n length: number,\n allowEnd = false,\n): boolean {\n return (\n Number.isInteger(index) &&\n index >= 0 &&\n (allowEnd ? index <= length : index < length)\n );\n}\n\nexport class ArraySignal<T> {\n private _items: T[];\n private _version: Signal<number>;\n private _patches: ArrayPatch<T>[];\n // Branded so `isArraySignal()` recognizes instances from any copy of this module.\n readonly [ARRAY_SIGNAL_BRAND] = true as const;\n\n constructor(initial: readonly T[] = []) {\n this._items = [...initial];\n this._version = signal(0);\n this._patches = [];\n }\n\n /** Read-only snapshot. Reads inside an effect/computed register a dependency. */\n get value(): readonly T[] {\n // Touch the version signal so signals-core treats reads as tracked.\n void this._version.value;\n return this._items;\n }\n\n /**\n * Replace the item at `index` with `fn(currentItem)`. Emits one `update`\n * patch. Both styles work: returning a fresh object (idiomatic) invalidates\n * the row by identity, and mutating `item` in place and returning it works\n * too — a per-item content version (KF-418) makes the same-ref change visible\n * to every consumer's row memo.\n */\n update(index: number, fn: (item: T) => T): void {\n if (!isValidIndex(index, this._items.length)) {\n throw new Error(\n `arraySignal.update: index ${index} out of bounds [0, ${this._items.length}).`,\n );\n }\n const next = fn(this._items[index]);\n this._items[index] = next;\n this._patches.push({ type: 'update', index, item: next });\n // KF-418: a same-ref update (fn mutates and returns the same object) is\n // invisible to the row memo, which is keyed on object identity. Bump the\n // item's content version so every consumer — this list, another list over\n // this signal, a second mount, a plain-array filter() view — re-renders it.\n // Non-object items (an arraySignal<number> used as a plain signal) are\n // skipped by bumpItemVersion — they can't be each() rows (KF-419).\n bumpItemVersion(next);\n this._version.value++;\n }\n\n /** Insert `item` at `index`. Existing items at index..N shift right. Emits one `insert` patch. */\n insert(index: number, item: T): void {\n if (!isValidIndex(index, this._items.length, true)) {\n throw new Error(\n `arraySignal.insert: index ${index} out of bounds [0, ${this._items.length}].`,\n );\n }\n this._items.splice(index, 0, item);\n this._patches.push({ type: 'insert', index, item });\n this._version.value++;\n }\n\n /** Append `item` at the end. Sugar for `insert(items.length, item)`. */\n push(item: T): void {\n this.insert(this._items.length, item);\n }\n\n /** Remove and return the item at `index`. Emits one `remove` patch. */\n remove(index: number): T {\n if (!isValidIndex(index, this._items.length)) {\n throw new Error(\n `arraySignal.remove: index ${index} out of bounds [0, ${this._items.length}).`,\n );\n }\n const [removed] = this._items.splice(index, 1);\n this._patches.push({ type: 'remove', index });\n this._version.value++;\n return removed;\n }\n\n /** Move the item at `from` to position `to`. Emits one `move` patch (no-op when from === to). */\n move(from: number, to: number): void {\n if (\n !isValidIndex(from, this._items.length) ||\n !isValidIndex(to, this._items.length)\n ) {\n throw new Error(\n `arraySignal.move: indices out of bounds (from=${from}, to=${to}, length=${this._items.length}).`,\n );\n }\n if (from === to) return;\n const [item] = this._items.splice(from, 1);\n this._items.splice(to, 0, item);\n this._patches.push({ type: 'move', from, to });\n this._version.value++;\n }\n\n /** Replace every item. Emits one `replace` patch — the granular reconciler falls back to a full keyed diff for this case. */\n replace(items: readonly T[]): void {\n this._items = [...items];\n this._patches.push({ type: 'replace', items: this._items });\n this._version.value++;\n }\n\n /**\n * @internal Used by `each()` when binding this signal to a list. Returns\n * the queue of granular patches issued since the previous call, then\n * clears the queue. Best paired with a single binding — a second consumer\n * in the same render gets an empty array (which forces the snapshot\n * fall-back path, which is correct but slower).\n */\n _consumePatches(): ArrayPatch<T>[] {\n const out = this._patches;\n this._patches = [];\n return out;\n }\n}\n\n/** Construct an array signal seeded with `initial`. */\nexport function arraySignal<T>(initial: readonly T[] = []): ArraySignal<T> {\n return new ArraySignal(initial);\n}\n"]}
1
+ {"version":3,"sources":["../src/array-signal.ts"],"names":[],"mappings":"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","file":"chunk-H7WTRM6F.js","sourcesContent":["/**\n * `arraySignal(initial)` — granular collection signal.\n *\n * A keyed-list-friendly variant of `signal()` that emits typed patch events\n * for every mutation (update / insert / remove / move / replace). When such\n * a signal is bound to `each(...)` inside a `mount()`, the keyed list\n * reconciler applies just the patches against the live DOM — no per-item\n * iteration, no `classifyItems` Map build, no LIS pass over unchanged rows.\n *\n * const rows = arraySignal<Row>([]);\n *\n * rows.update(42, (r) => ({ ...r, label: 'changed' })); // 1 update event\n * rows.insert(0, { id: 'x', ... }); // 1 insert event\n * rows.remove(7); // 1 remove event\n * rows.move(3, 0); // 1 move event\n * rows.replace([...]); // falls back to snapshot reconcile\n *\n * Read-side semantics match a regular signal: `arraySig.value` is a\n * snapshot, and reads inside `effect()` / `computed()` register as\n * dependencies, so derived values keep working.\n */\n\nimport { bumpItemVersion } from './item-version.js';\nimport type { Signal } from './reactive.js';\nimport { signal } from './reactive.js';\n\n/** A single granular mutation event. */\nexport type ArrayPatch<T> =\n | { type: 'update'; index: number; item: T }\n | { type: 'insert'; index: number; item: T }\n | { type: 'remove'; index: number }\n | { type: 'move'; from: number; to: number }\n | { type: 'replace'; items: readonly T[] };\n\n/**\n * Cross-bundle brand for `ArraySignal` instances. `each()` and the\n * granular reconciler check for this brand instead of `instanceof\n * ArraySignal`, so the main `kerfjs` barrel can detect arraySignal\n * inputs without importing the class at runtime — the class lives\n * only in the `kerfjs/array-signal` subpath, so apps that don't need\n * granular collections shed ~1 KB.\n *\n * Same `Symbol.for(...)`-based pattern as `SafeHtml` (KF-14): cross-\n * bundle-safe, zero-cost runtime check.\n */\nexport const ARRAY_SIGNAL_BRAND = Symbol.for('kerfjs.ArraySignal');\n\nfunction isValidIndex(\n index: number,\n length: number,\n allowEnd = false,\n): boolean {\n return (\n Number.isInteger(index) &&\n index >= 0 &&\n (allowEnd ? index <= length : index < length)\n );\n}\n\nexport class ArraySignal<T> {\n private _items: T[];\n private _version: Signal<number>;\n private _patches: ArrayPatch<T>[];\n // Branded so `isArraySignal()` recognizes instances from any copy of this module.\n readonly [ARRAY_SIGNAL_BRAND] = true as const;\n\n constructor(initial: readonly T[] = []) {\n this._items = [...initial];\n this._version = signal(0);\n this._patches = [];\n }\n\n /** Read-only snapshot. Reads inside an effect/computed register a dependency. */\n get value(): readonly T[] {\n // Touch the version signal so signals-core treats reads as tracked.\n void this._version.value;\n return this._items;\n }\n\n /**\n * Replace the item at `index` with `fn(currentItem)`. Emits one `update`\n * patch. Both styles work: returning a fresh object (idiomatic) invalidates\n * the row by identity, and mutating `item` in place and returning it works\n * too — a per-item content version (KF-418) makes the same-ref change visible\n * to every consumer's row memo.\n */\n update(index: number, fn: (item: T) => T): void {\n if (!isValidIndex(index, this._items.length)) {\n throw new Error(\n `arraySignal.update: index ${index} out of bounds [0, ${this._items.length}).`,\n );\n }\n const next = fn(this._items[index]);\n this._items[index] = next;\n this._patches.push({ type: 'update', index, item: next });\n // KF-418: a same-ref update (fn mutates and returns the same object) is\n // invisible to the row memo, which is keyed on object identity. Bump the\n // item's content version so every consumer — this list, another list over\n // this signal, a second mount, a plain-array filter() view — re-renders it.\n // Non-object items (an arraySignal<number> used as a plain signal) are\n // skipped by bumpItemVersion — they can't be each() rows (KF-419).\n bumpItemVersion(next);\n this._version.value++;\n }\n\n /** Insert `item` at `index`. Existing items at index..N shift right. Emits one `insert` patch. */\n insert(index: number, item: T): void {\n if (!isValidIndex(index, this._items.length, true)) {\n throw new Error(\n `arraySignal.insert: index ${index} out of bounds [0, ${this._items.length}].`,\n );\n }\n this._items.splice(index, 0, item);\n this._patches.push({ type: 'insert', index, item });\n this._version.value++;\n }\n\n /** Append `item` at the end. Sugar for `insert(items.length, item)`. */\n push(item: T): void {\n this.insert(this._items.length, item);\n }\n\n /** Remove and return the item at `index`. Emits one `remove` patch. */\n remove(index: number): T {\n if (!isValidIndex(index, this._items.length)) {\n throw new Error(\n `arraySignal.remove: index ${index} out of bounds [0, ${this._items.length}).`,\n );\n }\n const [removed] = this._items.splice(index, 1);\n this._patches.push({ type: 'remove', index });\n this._version.value++;\n return removed;\n }\n\n /** Move the item at `from` to position `to`. Emits one `move` patch (no-op when from === to). */\n move(from: number, to: number): void {\n if (\n !isValidIndex(from, this._items.length) ||\n !isValidIndex(to, this._items.length)\n ) {\n throw new Error(\n `arraySignal.move: indices out of bounds (from=${from}, to=${to}, length=${this._items.length}).`,\n );\n }\n if (from === to) return;\n const [item] = this._items.splice(from, 1);\n this._items.splice(to, 0, item);\n this._patches.push({ type: 'move', from, to });\n this._version.value++;\n }\n\n /** Replace every item. Emits one `replace` patch — the granular reconciler falls back to a full keyed diff for this case. */\n replace(items: readonly T[]): void {\n this._items = [...items];\n this._patches.push({ type: 'replace', items: this._items });\n this._version.value++;\n }\n\n /**\n * @internal Used by `each()` when binding this signal to a list. Returns\n * the queue of granular patches issued since the previous call, then\n * clears the queue. Best paired with a single binding — a second consumer\n * in the same render gets an empty array (which forces the snapshot\n * fall-back path, which is correct but slower).\n */\n _consumePatches(): ArrayPatch<T>[] {\n const out = this._patches;\n this._patches = [];\n return out;\n }\n}\n\n/** Construct an array signal seeded with `initial`. */\nexport function arraySignal<T>(initial: readonly T[] = []): ArraySignal<T> {\n return new ArraySignal(initial);\n}\n"]}
@@ -1,9 +1,9 @@
1
1
  import { itemVersion } from './chunk-5WRGJZV6.js';
2
2
  import { parseRowTemplate, rowContractError, parseSingleRow, collectTemplateChildren } from './chunk-WKIPLNVO.js';
3
- import { captureRowBindings, listSafeHtml, boundTextNodeOf, syncFormProp, newBindingContext, wireBindings, disposeRowBindings, isSafeHtml, _setBindingContext, TEXT_MARKER_PREFIX, ROW_TEXT_PREFIX, wireRowBindings, carryOrRewireRowBindings, granularListSafeHtml } from './chunk-3GSWKMBZ.js';
4
- import { effect } from './chunk-Y2FOYPBV.js';
3
+ import { captureRowBindings, listSafeHtml, boundTextNodeOf, syncFormProp, newBindingContext, wireBindings, disposeRowBindings, isSafeHtml, _setBindingContext, TEXT_MARKER_PREFIX, ROW_TEXT_PREFIX, wireRowBindings, carryOrRewireRowBindings, granularListSafeHtml } from './chunk-DU35YGKW.js';
4
+ import { effect } from './chunk-IHMDBBUJ.js';
5
5
  import { LIST_MARKER_PREFIX, flattenWithoutListItems, collectLists, flatten } from './chunk-BDX3R4OM.js';
6
- import { devHooks } from './chunk-CEQMZYLR.js';
6
+ import { devHooks } from './chunk-3C6F6NM4.js';
7
7
 
8
8
  // src/list-render-state.ts
9
9
  function deriveListRenderState(bindingCount) {
@@ -1186,6 +1186,110 @@ function reconcileList(binding, listSeg) {
1186
1186
  reconcileSnapshot(binding, listSeg);
1187
1187
  }
1188
1188
 
1189
+ // src/mount-list-bindings.ts
1190
+ function anyRebuiltListIsGranular(segment, rebuilt) {
1191
+ if (!rebuilt.size) return false;
1192
+ const lists = collectLists(segment);
1193
+ for (const id of rebuilt) {
1194
+ if (lists.get(id)?.patches) return true;
1195
+ }
1196
+ return false;
1197
+ }
1198
+ function bindListsFromMarkers(rootEl, segment, bindings, inlinedItems, rebuiltLists) {
1199
+ const lists = collectLists(segment);
1200
+ const found = [];
1201
+ collectComments(rootEl, found);
1202
+ for (const marker of found) {
1203
+ if (!marker.data.startsWith(LIST_MARKER_PREFIX)) continue;
1204
+ const id = marker.data.slice(LIST_MARKER_PREFIX.length);
1205
+ const existing = bindings.get(id);
1206
+ if (existing) {
1207
+ if (existing.marker === marker && rootEl.contains(existing.marker))
1208
+ continue;
1209
+ for (const item of existing.items) {
1210
+ disposeRowBindings(item.bindingDisposers);
1211
+ if (rootEl.contains(item.node)) {
1212
+ item.node.parentElement?.removeChild(item.node);
1213
+ }
1214
+ }
1215
+ bindings.delete(id);
1216
+ rebuiltLists?.add(id);
1217
+ devHooks.listRebind?.(id, marker.parentElement);
1218
+ }
1219
+ const listSeg = lists.get(id);
1220
+ const liveParent = marker.parentElement;
1221
+ const items = [];
1222
+ if (inlinedItems) {
1223
+ let next = marker.nextElementSibling;
1224
+ for (let i = 0; i < listSeg.items.length && next !== null; i++) {
1225
+ validateInlinedRowMatch(listSeg.items[i].html, i, next, liveParent);
1226
+ items.push({
1227
+ ref: listSeg.items[i].ref,
1228
+ cacheKey: listSeg.items[i].cacheKey,
1229
+ html: listSeg.items[i].html,
1230
+ node: next,
1231
+ bindings: listSeg.items[i].bindings
1232
+ });
1233
+ next = next.nextElementSibling;
1234
+ }
1235
+ }
1236
+ const binding = { liveParent, items, marker };
1237
+ if (items.length > 0) {
1238
+ devHooks.missingRowKey?.(items[0].node, items[0].html, binding);
1239
+ }
1240
+ devHooks.eachInMorphSkip?.(id, liveParent, rootEl);
1241
+ bindings.set(id, binding);
1242
+ for (const bound of items) {
1243
+ if (bound.bindings?.length) {
1244
+ bound.bindingDisposers = wireRowBindings(bound.node, bound.bindings);
1245
+ }
1246
+ }
1247
+ }
1248
+ }
1249
+ function validateInlinedRowMatch(expectedHtml, index, boundEl, liveParent) {
1250
+ if (boundEl.outerHTML === expectedHtml) return;
1251
+ const { content, count } = parseRowTemplate(expectedHtml, liveParent);
1252
+ if (count !== 1) throw rowContractError(index, expectedHtml, liveParent);
1253
+ const expectedTag = content.firstElementChild.tagName;
1254
+ if (boundEl.tagName !== expectedTag)
1255
+ throw rowStructureError(index, boundEl.tagName, expectedTag);
1256
+ }
1257
+ function rowStructureError(index, gotTag, wantTag) {
1258
+ const got = gotTag.toLowerCase();
1259
+ const want = wantTag.toLowerCase();
1260
+ return new Error(
1261
+ `each(): row ${index} renders <${want}>, but the HTML parser wrapped the rows in <${got}> \u2014 so kerf cannot bind one row per element. This happens when an each() of <${want}> sits directly inside a table: the parser inserts <${got}> around the whole run. Put the each() inside an explicit <${got}> (e.g. <table><${got}>{each(...)}</${got}></table>) so the rows are the direct children kerf binds.`
1262
+ );
1263
+ }
1264
+ function collectOwnedItems(bindings) {
1265
+ const owned = /* @__PURE__ */ new Set();
1266
+ for (const b of bindings.values()) {
1267
+ for (const item of b.items) owned.add(item.node);
1268
+ }
1269
+ return owned;
1270
+ }
1271
+ function cleanupOrphanBindings(segment, bindings, renderCtx) {
1272
+ const liveIds = collectLists(segment);
1273
+ for (const [id, binding] of bindings) {
1274
+ if (liveIds.has(id)) continue;
1275
+ for (const item of binding.items) {
1276
+ disposeRowBindings(item.bindingDisposers);
1277
+ item.node.remove();
1278
+ }
1279
+ binding.marker.remove();
1280
+ bindings.delete(id);
1281
+ renderCtx.bindingCounts.delete(id);
1282
+ renderCtx.bindingSources.delete(id);
1283
+ renderCtx.caches.delete(id);
1284
+ }
1285
+ }
1286
+ function collectComments(node, out) {
1287
+ for (let c = node.firstChild; c; c = c.nextSibling) {
1288
+ if (c.nodeType === Node.COMMENT_NODE) out.push(c);
1289
+ else if (c.nodeType === Node.ELEMENT_NODE) collectComments(c, out);
1290
+ }
1291
+ }
1292
+
1189
1293
  // src/mount.ts
1190
1294
  var MOUNTED_MARKER = /* @__PURE__ */ Symbol.for("kerfjs.mounted");
1191
1295
  var NESTED_MOUNT_MSG = "mount: rootEl is already inside (or contains) a mounted tree. kerf supports one mount per tree \u2014 compose with plain functions that return JSX instead of nesting mounts.";
@@ -1402,109 +1506,7 @@ function coerceRenderResult(result) {
1402
1506
  function resultToSegment(result) {
1403
1507
  return isSafeHtml(result) ? result.__segment ?? { kind: "static", html: result.__html } : { kind: "static", html: coerceRenderResult(result) };
1404
1508
  }
1405
- function anyRebuiltListIsGranular(segment, rebuilt) {
1406
- if (!rebuilt.size) return false;
1407
- const lists = collectLists(segment);
1408
- for (const id of rebuilt) {
1409
- if (lists.get(id)?.patches) return true;
1410
- }
1411
- return false;
1412
- }
1413
- function bindListsFromMarkers(rootEl, segment, bindings, inlinedItems, rebuiltLists) {
1414
- const lists = collectLists(segment);
1415
- const found = [];
1416
- collectComments(rootEl, found);
1417
- for (const marker of found) {
1418
- if (!marker.data.startsWith(LIST_MARKER_PREFIX)) continue;
1419
- const id = marker.data.slice(LIST_MARKER_PREFIX.length);
1420
- const existing = bindings.get(id);
1421
- if (existing) {
1422
- if (existing.marker === marker && rootEl.contains(existing.marker))
1423
- continue;
1424
- for (const item of existing.items) {
1425
- disposeRowBindings(item.bindingDisposers);
1426
- if (rootEl.contains(item.node)) {
1427
- item.node.parentElement?.removeChild(item.node);
1428
- }
1429
- }
1430
- bindings.delete(id);
1431
- rebuiltLists?.add(id);
1432
- devHooks.listRebind?.(id, marker.parentElement);
1433
- }
1434
- const listSeg = lists.get(id);
1435
- const liveParent = marker.parentElement;
1436
- const items = [];
1437
- if (inlinedItems) {
1438
- let next = marker.nextElementSibling;
1439
- for (let i = 0; i < listSeg.items.length && next !== null; i++) {
1440
- validateInlinedRowMatch(listSeg.items[i].html, i, next, liveParent);
1441
- items.push({
1442
- ref: listSeg.items[i].ref,
1443
- cacheKey: listSeg.items[i].cacheKey,
1444
- html: listSeg.items[i].html,
1445
- node: next,
1446
- bindings: listSeg.items[i].bindings
1447
- });
1448
- next = next.nextElementSibling;
1449
- }
1450
- }
1451
- const binding = { liveParent, items, marker };
1452
- if (items.length > 0) {
1453
- devHooks.missingRowKey?.(items[0].node, items[0].html, binding);
1454
- }
1455
- devHooks.eachInMorphSkip?.(id, liveParent, rootEl);
1456
- bindings.set(id, binding);
1457
- for (const bound of items) {
1458
- if (bound.bindings?.length) {
1459
- bound.bindingDisposers = wireRowBindings(bound.node, bound.bindings);
1460
- }
1461
- }
1462
- }
1463
- }
1464
- function validateInlinedRowMatch(expectedHtml, index, boundEl, liveParent) {
1465
- if (boundEl.outerHTML === expectedHtml) return;
1466
- const { content, count } = parseRowTemplate(expectedHtml, liveParent);
1467
- if (count !== 1) throw rowContractError(index, expectedHtml, liveParent);
1468
- const expectedTag = content.firstElementChild.tagName;
1469
- if (boundEl.tagName !== expectedTag)
1470
- throw rowStructureError(index, boundEl.tagName, expectedTag);
1471
- }
1472
- function rowStructureError(index, gotTag, wantTag) {
1473
- const got = gotTag.toLowerCase();
1474
- const want = wantTag.toLowerCase();
1475
- return new Error(
1476
- `each(): row ${index} renders <${want}>, but the HTML parser wrapped the rows in <${got}> \u2014 so kerf cannot bind one row per element. This happens when an each() of <${want}> sits directly inside a table: the parser inserts <${got}> around the whole run. Put the each() inside an explicit <${got}> (e.g. <table><${got}>{each(...)}</${got}></table>) so the rows are the direct children kerf binds.`
1477
- );
1478
- }
1479
- function collectOwnedItems(bindings) {
1480
- const owned = /* @__PURE__ */ new Set();
1481
- for (const b of bindings.values()) {
1482
- for (const item of b.items) owned.add(item.node);
1483
- }
1484
- return owned;
1485
- }
1486
- function cleanupOrphanBindings(segment, bindings, renderCtx) {
1487
- const liveIds = collectLists(segment);
1488
- for (const [id, binding] of bindings) {
1489
- if (liveIds.has(id)) continue;
1490
- for (const item of binding.items) {
1491
- disposeRowBindings(item.bindingDisposers);
1492
- item.node.remove();
1493
- }
1494
- binding.marker.remove();
1495
- bindings.delete(id);
1496
- renderCtx.bindingCounts.delete(id);
1497
- renderCtx.bindingSources.delete(id);
1498
- renderCtx.caches.delete(id);
1499
- }
1500
- }
1501
- function collectComments(node, out) {
1502
- for (let c = node.firstChild; c; c = c.nextSibling) {
1503
- if (c.nodeType === Node.COMMENT_NODE) out.push(c);
1504
- else if (c.nodeType === Node.ELEMENT_NODE) collectComments(c, out);
1505
- }
1506
- }
1507
1509
 
1508
1510
  export { captureFocus, each, morph, mount, moveNode, restoreFocus };
1509
1511
 
1510
- //# sourceMappingURL=chunk-H3TTM7PQ.js.map
1512
+ //# sourceMappingURL=chunk-HTXQR2KL.js.map