@barefootjs/client 0.26.2 → 0.26.4

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.
@@ -12,6 +12,8 @@ export { reconcileList, type RenderItemFn } from './list.ts';
12
12
  export { reconcileElements, getLoopChildren, getLoopNodes } from './reconcile-elements.ts';
13
13
  export { qsaItem, upsertChildItem } from './qsa-item.ts';
14
14
  export { mapArray, mapArrayAnchored } from './map-array.ts';
15
+ export { patchLeaf } from './patch-leaf.ts';
16
+ export { patchSlotRange } from './patch-slot-range.ts';
15
17
  export { registerTemplate, getTemplate, hasTemplate, type TemplateFn } from './template.ts';
16
18
  export { createComponent, renderChild, getPropsUpdateFn, getComponentProps, parseHTML, escapeAttr, escapeText, } from './component.ts';
17
19
  export { applyRestAttrs } from './apply-rest-attrs.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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"}
@@ -2387,6 +2387,87 @@ function mapArrayAnchored(accessor, container, getKey, renderItem, markerId, bfI
2387
2387
  }
2388
2388
  }, bfId);
2389
2389
  }
2390
+ // src/runtime/patch-leaf.ts
2391
+ function patchLeaf(el, html) {
2392
+ const tpl = document.createElement("template");
2393
+ tpl.innerHTML = html;
2394
+ const next = tpl.content.firstElementChild;
2395
+ if (!next)
2396
+ return;
2397
+ if (next.tagName !== el.tagName) {
2398
+ console.warn("[barefootjs] flatMap leaf root tag changed under a stable key " + `(<${el.tagName.toLowerCase()}> -> <${next.tagName.toLowerCase()}>); ` + "give each branch its own key so the node is replaced instead of patched.");
2399
+ }
2400
+ for (const name of el.getAttributeNames()) {
2401
+ if (name === "data-key")
2402
+ continue;
2403
+ if (!next.hasAttribute(name))
2404
+ el.removeAttribute(name);
2405
+ }
2406
+ for (const name of next.getAttributeNames()) {
2407
+ if (name === "data-key")
2408
+ continue;
2409
+ const value = next.getAttribute(name);
2410
+ if (value !== null && el.getAttribute(name) !== value)
2411
+ el.setAttribute(name, value);
2412
+ }
2413
+ el.replaceChildren(...Array.from(next.childNodes));
2414
+ }
2415
+ // src/runtime/patch-slot-range.ts
2416
+ function patchSlotRange(scope, id, html) {
2417
+ const marker = `bf:${id}`;
2418
+ let start = null;
2419
+ const walker = document.createTreeWalker(scope, NodeFilter.SHOW_COMMENT);
2420
+ while (walker.nextNode()) {
2421
+ const comment = walker.currentNode;
2422
+ if (comment.nodeValue !== marker)
2423
+ continue;
2424
+ let owned = true;
2425
+ for (let el = comment.parentElement;el && el !== scope; el = el.parentElement) {
2426
+ if (el.hasAttribute(BF_SCOPE)) {
2427
+ owned = false;
2428
+ break;
2429
+ }
2430
+ }
2431
+ if (owned) {
2432
+ start = comment;
2433
+ break;
2434
+ }
2435
+ }
2436
+ const parent = start?.parentNode;
2437
+ if (!start || !parent) {
2438
+ console.warn(`[barefootjs] preamble region marker bf:${id} not found in row; skipping patch`);
2439
+ return;
2440
+ }
2441
+ let depth = 0;
2442
+ let end = null;
2443
+ const toRemove = [];
2444
+ let node = start.nextSibling;
2445
+ while (node) {
2446
+ if (node.nodeType === Node.COMMENT_NODE) {
2447
+ const value = node.nodeValue ?? "";
2448
+ if (value.startsWith("bf:")) {
2449
+ depth++;
2450
+ } else if (value === "/") {
2451
+ if (depth === 0) {
2452
+ end = node;
2453
+ break;
2454
+ }
2455
+ depth--;
2456
+ }
2457
+ }
2458
+ toRemove.push(node);
2459
+ node = node.nextSibling;
2460
+ }
2461
+ if (!end) {
2462
+ console.warn(`[barefootjs] preamble region bf:${id} has no end marker; skipping patch`);
2463
+ return;
2464
+ }
2465
+ for (const n of toRemove)
2466
+ parent.removeChild(n);
2467
+ const tpl = document.createElement("template");
2468
+ tpl.innerHTML = html;
2469
+ parent.insertBefore(tpl.content, end);
2470
+ }
2390
2471
  // src/runtime/apply-rest-attrs.ts
2391
2472
  import { createEffect as createEffect2 } from "@barefootjs/client/reactive";
2392
2473
 
@@ -2957,6 +3038,8 @@ export {
2957
3038
  qsaChildScope,
2958
3039
  qsa,
2959
3040
  provideContext,
3041
+ patchSlotRange,
3042
+ patchLeaf,
2960
3043
  parseHTML,
2961
3044
  onMount,
2962
3045
  onCleanup,
@@ -0,0 +1,17 @@
1
+ /**
2
+ * In-place patch for flatMap leaf elements (the descriptor-based
3
+ * `mapArray` path — see `stringifyPlainLoop`'s flatMap branch in
4
+ * `@barefootjs/jsx`).
5
+ *
6
+ * flatMap leaves carry no per-slot reactive wiring (the compiler refuses
7
+ * leaves that would need it), so a leaf whose rendered HTML changed under a
8
+ * stable key is updated wholesale: attributes are synced and children are
9
+ * replaced from the freshly rendered string. The element's identity is
10
+ * preserved — `mapArray` holds the node in its keyed scope map, so the
11
+ * patch must never swap the node itself.
12
+ *
13
+ * `data-key` is excluded from attribute sync: reconciliation identity is
14
+ * owned by `mapArray` (stamped via `setAttribute`), never by leaf content.
15
+ */
16
+ export declare function patchLeaf(el: Element, html: string): void;
17
+ //# sourceMappingURL=patch-leaf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch-leaf.d.ts","sourceRoot":"","sources":["../../src/runtime/patch-leaf.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAyBzD"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * In-place patch for a `.map()` preamble-patched loop region — the
3
+ * `<!--bf:sN-->...<!--/-->` marker pair around a loop-body expression child
4
+ * whose free identifiers read a preamble-declared local (`{cells}` in
5
+ * `arr.map(t => { const cells = []; ...; return <tr>{cells}<td>{t.name}</td></tr> })`,
6
+ * #2389). `mapArray` reuses the same row element on a same-key item update
7
+ * via per-item `setItem`, re-running only the row's wired text/attr-slot
8
+ * effects — a preamble-derived region has neither, so without this it
9
+ * freezes at its mount-time content forever. The compiled per-item
10
+ * `createEffect` (see `emitPreambleRegionEffects` in `@barefootjs/jsx`)
11
+ * calls this whenever the re-computed region HTML differs from the
12
+ * last-patched value.
13
+ *
14
+ * Sibling of `patchLeaf` (same "wholesale replace, preserve identity"
15
+ * contract), but scoped to a marker-delimited RANGE inside an element
16
+ * rather than the element itself — a region has no element of its own to
17
+ * hold an identity, only the comment pair.
18
+ *
19
+ * The start comment is located here, per call, rather than by a separate
20
+ * mount-time lookup: the region effect's first run only records the
21
+ * mount-time value, so a row that never changes pays ZERO lookup cost —
22
+ * the scan runs only on an actual content change, over a single (small)
23
+ * row element.
24
+ *
25
+ * Ownership: slot ids are per-component, so a marker under a nested `bf-s`
26
+ * scope (a child component's own `bf:sN`) is never a candidate. Today
27
+ * that's defense in depth — regions are only emitted for the plain
28
+ * loop-plan shape, and `decideLoopRendering` routes any row containing
29
+ * nested components or inner loops to the composite/component shapes,
30
+ * which don't consume `preambleRegions` — but the guard keeps that safety
31
+ * local instead of coupled to routing.
32
+ *
33
+ * A missing start or end marker means the DOM diverged from the compiled
34
+ * template — warn and do nothing (sound-or-loud: never guess a boundary).
35
+ *
36
+ * Range contract: the matching end is the nearest following sibling
37
+ * `<!--/-->` comment at the SAME nesting depth — any `bf:`-prefixed
38
+ * comment along the way opens a FURTHER nested region (a leaf rendered
39
+ * inside this one could carry its own ordinary text-slot markers) and
40
+ * increments a depth counter so that region's own `/` doesn't prematurely
41
+ * close the outer range. Every node strictly between the two markers is
42
+ * removed, then `html` is parsed via a `<template>` and inserted before
43
+ * the end marker. The two markers themselves are never removed — they
44
+ * stay as the region's permanent boundary for the next patch.
45
+ */
46
+ export declare function patchSlotRange(scope: Element, id: string, html: string): void;
47
+ //# sourceMappingURL=patch-slot-range.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch-slot-range.d.ts","sourceRoot":"","sources":["../../src/runtime/patch-slot-range.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAyD7E"}
@@ -2792,6 +2792,87 @@ function mapArrayAnchored(accessor, container, getKey, renderItem, markerId, bfI
2792
2792
  }
2793
2793
  }, bfId);
2794
2794
  }
2795
+ // src/runtime/patch-leaf.ts
2796
+ function patchLeaf(el, html) {
2797
+ const tpl = document.createElement("template");
2798
+ tpl.innerHTML = html;
2799
+ const next = tpl.content.firstElementChild;
2800
+ if (!next)
2801
+ return;
2802
+ if (next.tagName !== el.tagName) {
2803
+ console.warn("[barefootjs] flatMap leaf root tag changed under a stable key " + `(<${el.tagName.toLowerCase()}> -> <${next.tagName.toLowerCase()}>); ` + "give each branch its own key so the node is replaced instead of patched.");
2804
+ }
2805
+ for (const name of el.getAttributeNames()) {
2806
+ if (name === "data-key")
2807
+ continue;
2808
+ if (!next.hasAttribute(name))
2809
+ el.removeAttribute(name);
2810
+ }
2811
+ for (const name of next.getAttributeNames()) {
2812
+ if (name === "data-key")
2813
+ continue;
2814
+ const value = next.getAttribute(name);
2815
+ if (value !== null && el.getAttribute(name) !== value)
2816
+ el.setAttribute(name, value);
2817
+ }
2818
+ el.replaceChildren(...Array.from(next.childNodes));
2819
+ }
2820
+ // src/runtime/patch-slot-range.ts
2821
+ function patchSlotRange(scope, id, html) {
2822
+ const marker = `bf:${id}`;
2823
+ let start = null;
2824
+ const walker = document.createTreeWalker(scope, NodeFilter.SHOW_COMMENT);
2825
+ while (walker.nextNode()) {
2826
+ const comment = walker.currentNode;
2827
+ if (comment.nodeValue !== marker)
2828
+ continue;
2829
+ let owned = true;
2830
+ for (let el = comment.parentElement;el && el !== scope; el = el.parentElement) {
2831
+ if (el.hasAttribute(BF_SCOPE)) {
2832
+ owned = false;
2833
+ break;
2834
+ }
2835
+ }
2836
+ if (owned) {
2837
+ start = comment;
2838
+ break;
2839
+ }
2840
+ }
2841
+ const parent = start?.parentNode;
2842
+ if (!start || !parent) {
2843
+ console.warn(`[barefootjs] preamble region marker bf:${id} not found in row; skipping patch`);
2844
+ return;
2845
+ }
2846
+ let depth = 0;
2847
+ let end = null;
2848
+ const toRemove = [];
2849
+ let node = start.nextSibling;
2850
+ while (node) {
2851
+ if (node.nodeType === Node.COMMENT_NODE) {
2852
+ const value = node.nodeValue ?? "";
2853
+ if (value.startsWith("bf:")) {
2854
+ depth++;
2855
+ } else if (value === "/") {
2856
+ if (depth === 0) {
2857
+ end = node;
2858
+ break;
2859
+ }
2860
+ depth--;
2861
+ }
2862
+ }
2863
+ toRemove.push(node);
2864
+ node = node.nextSibling;
2865
+ }
2866
+ if (!end) {
2867
+ console.warn(`[barefootjs] preamble region bf:${id} has no end marker; skipping patch`);
2868
+ return;
2869
+ }
2870
+ for (const n of toRemove)
2871
+ parent.removeChild(n);
2872
+ const tpl = document.createElement("template");
2873
+ tpl.innerHTML = html;
2874
+ parent.insertBefore(tpl.content, end);
2875
+ }
2795
2876
  // src/runtime/style.ts
2796
2877
  function styleToCss(value) {
2797
2878
  if (value == null)
@@ -3357,6 +3438,8 @@ export {
3357
3438
  qsaChildScope,
3358
3439
  qsa,
3359
3440
  provideContext,
3441
+ patchSlotRange,
3442
+ patchLeaf,
3360
3443
  parseHTML,
3361
3444
  onMount,
3362
3445
  onCleanup,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/client",
3
- "version": "0.26.2",
3
+ "version": "0.26.4",
4
4
  "description": "BarefootJS client package: reactive primitives (SSR-safe) plus browser runtime under the `/runtime` subpath (compiler target)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -55,7 +55,7 @@
55
55
  "directory": "packages/client"
56
56
  },
57
57
  "dependencies": {
58
- "@barefootjs/shared": "0.26.2"
58
+ "@barefootjs/shared": "0.26.4"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@barefootjs/jsx": ">=0.2.0"
@@ -82,6 +82,8 @@ export { reconcileList, type RenderItemFn } from './list.ts'
82
82
  export { reconcileElements, getLoopChildren, getLoopNodes } from './reconcile-elements.ts'
83
83
  export { qsaItem, upsertChildItem } from './qsa-item.ts'
84
84
  export { mapArray, mapArrayAnchored } from './map-array.ts'
85
+ export { patchLeaf } from './patch-leaf.ts'
86
+ export { patchSlotRange } from './patch-slot-range.ts'
85
87
 
86
88
  // Template registry
87
89
  export { registerTemplate, getTemplate, hasTemplate, type TemplateFn } from './template.ts'
@@ -0,0 +1,41 @@
1
+ /**
2
+ * In-place patch for flatMap leaf elements (the descriptor-based
3
+ * `mapArray` path — see `stringifyPlainLoop`'s flatMap branch in
4
+ * `@barefootjs/jsx`).
5
+ *
6
+ * flatMap leaves carry no per-slot reactive wiring (the compiler refuses
7
+ * leaves that would need it), so a leaf whose rendered HTML changed under a
8
+ * stable key is updated wholesale: attributes are synced and children are
9
+ * replaced from the freshly rendered string. The element's identity is
10
+ * preserved — `mapArray` holds the node in its keyed scope map, so the
11
+ * patch must never swap the node itself.
12
+ *
13
+ * `data-key` is excluded from attribute sync: reconciliation identity is
14
+ * owned by `mapArray` (stamped via `setAttribute`), never by leaf content.
15
+ */
16
+ export function patchLeaf(el: Element, html: string): void {
17
+ const tpl = document.createElement('template')
18
+ tpl.innerHTML = html
19
+ const next = tpl.content.firstElementChild
20
+ if (!next) return
21
+ if (next.tagName !== el.tagName) {
22
+ // A root-tag change under a stable key cannot swap the node without
23
+ // desyncing mapArray's keyed scope. Attributes/children still patch
24
+ // onto the existing tag; the honest fix is a distinct key per branch.
25
+ console.warn(
26
+ '[barefootjs] flatMap leaf root tag changed under a stable key ' +
27
+ `(<${el.tagName.toLowerCase()}> -> <${next.tagName.toLowerCase()}>); ` +
28
+ 'give each branch its own key so the node is replaced instead of patched.',
29
+ )
30
+ }
31
+ for (const name of el.getAttributeNames()) {
32
+ if (name === 'data-key') continue
33
+ if (!next.hasAttribute(name)) el.removeAttribute(name)
34
+ }
35
+ for (const name of next.getAttributeNames()) {
36
+ if (name === 'data-key') continue
37
+ const value = next.getAttribute(name)
38
+ if (value !== null && el.getAttribute(name) !== value) el.setAttribute(name, value)
39
+ }
40
+ el.replaceChildren(...Array.from(next.childNodes))
41
+ }
@@ -0,0 +1,105 @@
1
+ import { BF_SCOPE } from '@barefootjs/shared'
2
+
3
+ /**
4
+ * In-place patch for a `.map()` preamble-patched loop region — the
5
+ * `<!--bf:sN-->...<!--/-->` marker pair around a loop-body expression child
6
+ * whose free identifiers read a preamble-declared local (`{cells}` in
7
+ * `arr.map(t => { const cells = []; ...; return <tr>{cells}<td>{t.name}</td></tr> })`,
8
+ * #2389). `mapArray` reuses the same row element on a same-key item update
9
+ * via per-item `setItem`, re-running only the row's wired text/attr-slot
10
+ * effects — a preamble-derived region has neither, so without this it
11
+ * freezes at its mount-time content forever. The compiled per-item
12
+ * `createEffect` (see `emitPreambleRegionEffects` in `@barefootjs/jsx`)
13
+ * calls this whenever the re-computed region HTML differs from the
14
+ * last-patched value.
15
+ *
16
+ * Sibling of `patchLeaf` (same "wholesale replace, preserve identity"
17
+ * contract), but scoped to a marker-delimited RANGE inside an element
18
+ * rather than the element itself — a region has no element of its own to
19
+ * hold an identity, only the comment pair.
20
+ *
21
+ * The start comment is located here, per call, rather than by a separate
22
+ * mount-time lookup: the region effect's first run only records the
23
+ * mount-time value, so a row that never changes pays ZERO lookup cost —
24
+ * the scan runs only on an actual content change, over a single (small)
25
+ * row element.
26
+ *
27
+ * Ownership: slot ids are per-component, so a marker under a nested `bf-s`
28
+ * scope (a child component's own `bf:sN`) is never a candidate. Today
29
+ * that's defense in depth — regions are only emitted for the plain
30
+ * loop-plan shape, and `decideLoopRendering` routes any row containing
31
+ * nested components or inner loops to the composite/component shapes,
32
+ * which don't consume `preambleRegions` — but the guard keeps that safety
33
+ * local instead of coupled to routing.
34
+ *
35
+ * A missing start or end marker means the DOM diverged from the compiled
36
+ * template — warn and do nothing (sound-or-loud: never guess a boundary).
37
+ *
38
+ * Range contract: the matching end is the nearest following sibling
39
+ * `<!--/-->` comment at the SAME nesting depth — any `bf:`-prefixed
40
+ * comment along the way opens a FURTHER nested region (a leaf rendered
41
+ * inside this one could carry its own ordinary text-slot markers) and
42
+ * increments a depth counter so that region's own `/` doesn't prematurely
43
+ * close the outer range. Every node strictly between the two markers is
44
+ * removed, then `html` is parsed via a `<template>` and inserted before
45
+ * the end marker. The two markers themselves are never removed — they
46
+ * stay as the region's permanent boundary for the next patch.
47
+ */
48
+ export function patchSlotRange(scope: Element, id: string, html: string): void {
49
+ const marker = `bf:${id}`
50
+ let start: Comment | null = null
51
+ const walker = document.createTreeWalker(scope, NodeFilter.SHOW_COMMENT)
52
+ while (walker.nextNode()) {
53
+ const comment = walker.currentNode as Comment
54
+ if (comment.nodeValue !== marker) continue
55
+ let owned = true
56
+ for (let el = comment.parentElement; el && el !== scope; el = el.parentElement) {
57
+ if (el.hasAttribute(BF_SCOPE)) {
58
+ owned = false
59
+ break
60
+ }
61
+ }
62
+ if (owned) {
63
+ start = comment
64
+ break
65
+ }
66
+ }
67
+ const parent = start?.parentNode
68
+ if (!start || !parent) {
69
+ console.warn(`[barefootjs] preamble region marker bf:${id} not found in row; skipping patch`)
70
+ return
71
+ }
72
+
73
+ let depth = 0
74
+ let end: Comment | null = null
75
+ const toRemove: Node[] = []
76
+ let node: Node | null = start.nextSibling
77
+ while (node) {
78
+ if (node.nodeType === Node.COMMENT_NODE) {
79
+ const value = (node as Comment).nodeValue ?? ''
80
+ if (value.startsWith('bf:')) {
81
+ depth++
82
+ } else if (value === '/') {
83
+ if (depth === 0) {
84
+ end = node as Comment
85
+ break
86
+ }
87
+ depth--
88
+ }
89
+ }
90
+ toRemove.push(node)
91
+ node = node.nextSibling
92
+ }
93
+ if (!end) {
94
+ // Malformed/unexpected DOM shape — never risk deleting past the
95
+ // region's intended boundary.
96
+ console.warn(`[barefootjs] preamble region bf:${id} has no end marker; skipping patch`)
97
+ return
98
+ }
99
+
100
+ for (const n of toRemove) parent.removeChild(n)
101
+
102
+ const tpl = document.createElement('template')
103
+ tpl.innerHTML = html
104
+ parent.insertBefore(tpl.content, end)
105
+ }