@barefootjs/client 0.26.3 → 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.
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +83 -0
- package/dist/runtime/patch-leaf.d.ts +17 -0
- package/dist/runtime/patch-leaf.d.ts.map +1 -0
- package/dist/runtime/patch-slot-range.d.ts +47 -0
- package/dist/runtime/patch-slot-range.d.ts.map +1 -0
- package/dist/runtime/standalone.js +83 -0
- package/package.json +2 -2
- package/src/runtime/index.ts +2 -0
- package/src/runtime/patch-leaf.ts +41 -0
- package/src/runtime/patch-slot-range.ts +105 -0
package/dist/runtime/index.d.ts
CHANGED
|
@@ -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":"AAUA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,UAAU,EACV,cAAc,EACd,UAAU,EACV,SAAS,EACT,OAAO,EACP,OAAO,EACP,KAAK,EACL,eAAe,EACf,SAAS,EACT,OAAO,EACP,gBAAgB,EAOhB,kBAAkB,EAClB,KAAK,gBAAgB,EACrB,sBAAsB,EACtB,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,IAAI,EACT,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EACL,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,aAAa,GACnB,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACpF,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAI9C,OAAO,EACL,aAAa,EACb,UAAU,EACV,cAAc,EACd,eAAe,EACf,KAAK,OAAO,GACb,MAAM,cAAc,CAAA;AAGrB,OAAO,EACL,YAAY,EACZ,WAAW,EACX,eAAe,EACf,wBAAwB,EACxB,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,cAAc,GACpB,MAAM,aAAa,CAAA;AAGpB,OAAO,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAA;AAC5D,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC1F,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACxD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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"}
|
package/dist/runtime/index.js
CHANGED
|
@@ -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.
|
|
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.
|
|
58
|
+
"@barefootjs/shared": "0.26.4"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@barefootjs/jsx": ">=0.2.0"
|
package/src/runtime/index.ts
CHANGED
|
@@ -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
|
+
}
|