@barefootjs/client 0.26.4 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -77,13 +77,17 @@ export {
77
77
  type PortalChildren,
78
78
  } from './portal.ts'
79
79
 
80
- // List reconciliation
81
- export { reconcileList, type RenderItemFn } from './list.ts'
82
- export { reconcileElements, getLoopChildren, getLoopNodes } from './reconcile-elements.ts'
80
+ // Loop boundary marker lookup (used by mapArray/mapArrayAnchored consumers
81
+ // and compiler-generated clearing code see ./loop-markers.ts docstring)
82
+ export { getLoopChildren, getLoopNodes } from './loop-markers.ts'
83
83
  export { qsaItem, upsertChildItem } from './qsa-item.ts'
84
84
  export { mapArray, mapArrayAnchored } from './map-array.ts'
85
85
  export { patchLeaf } from './patch-leaf.ts'
86
- export { patchSlotRange } from './patch-slot-range.ts'
86
+
87
+ // Claim-plan interpreter (slot unification A2/A3, spec/slot-unification.md)
88
+ // — the ONE content-slot update mechanism, wired up by the compiler in A3.
89
+ // Supersedes (deleted) `patchSlotRange` and `updateClientMarker`.
90
+ export { claimSlots, lazySlots, type SlotSpec, type ClaimPlan, type ClaimedSlots, type SlotWriter } from './claim-slots.ts'
87
91
 
88
92
  // Template registry
89
93
  export { registerTemplate, getTemplate, hasTemplate, type TemplateFn } from './template.ts'
@@ -92,11 +96,10 @@ export { registerTemplate, getTemplate, hasTemplate, type TemplateFn } from './t
92
96
  export {
93
97
  createComponent,
94
98
  renderChild,
95
- getPropsUpdateFn,
96
- getComponentProps,
97
99
  parseHTML,
98
100
  escapeAttr,
99
101
  escapeText,
102
+ escapeTextOrNode,
100
103
  } from './component.ts'
101
104
 
102
105
  // Spread props helpers
@@ -111,8 +114,14 @@ export { hydrate, rehydrateAll, rehydrateScope, disposeScope, flushHydration, ge
111
114
  export { registerComponent, getComponentInit, initChild, upsertChild } from './registry.ts'
112
115
  export { insert, type BranchConfig, type BranchTemplateResult } from './insert.ts'
113
116
  export { __bfSlot } from './branch-slot.ts'
117
+ // `__bfText` (dynamic-text.ts) and `$t` (query.ts) are kept: one narrow
118
+ // emission site — a `@client`-nested-inside-a-top-level-conditional dynamic
119
+ // text/JSX expression (`emitDynamicTextUpdates`'s `conditionalElems` path in
120
+ // `ir-to-client-js/emit-reactive.ts`) still uses them, deferred from slot
121
+ // unification A3 (see that function's docstring for why the claim-plan
122
+ // model doesn't fit that one case cleanly). `updateClientMarker` had no
123
+ // such holdout and is fully deleted.
114
124
  export { __bfText } from './dynamic-text.ts'
115
- export { updateClientMarker } from './client-marker.ts'
116
125
 
117
126
  // Hydration state
118
127
  export { hydratedScopes } from './hydration-state.ts'
@@ -338,7 +338,7 @@ export function insert(
338
338
  function autoFocusConditionalElement(region: CondRegion, id: string): void {
339
339
  // Use requestAnimationFrame to defer focus until after DOM updates.
340
340
  // This is necessary because createComponent() may call insert() before
341
- // the element is added to the document by reconcileList().
341
+ // the element is added to the document by mapArray()/mapArrayAnchored().
342
342
  requestAnimationFrame(() => {
343
343
  const condEl = region.anchor
344
344
  ? findCondElInRange(region.anchor, id)
@@ -0,0 +1,100 @@
1
+ /**
2
+ * BarefootJS - Loop Boundary Marker Lookup
3
+ *
4
+ * `<!--bf-loop:<id>-->` / `<!--bf-/loop:<id>-->` comment markers delimit a
5
+ * loop's rendered range inside its container so `mapArray`/`mapArrayAnchored`
6
+ * (`./map-array.ts`) can reconcile only that range without disturbing
7
+ * non-loop siblings. These lookups used to also back the now-removed
8
+ * `reconcileElements`/`reconcileList` element-reconciler (slot unification
9
+ * A4); they remain as the compiler-facing marker API.
10
+ */
11
+
12
+ import { BF_LOOP_START, BF_LOOP_END, loopStartMarker, loopEndMarker } from '@barefootjs/shared'
13
+
14
+ /**
15
+ * Find loop boundary comment markers in a container.
16
+ *
17
+ * `markerId` scopes the lookup to `<!--bf-loop:<id>-->` / `<!--bf-/loop:<id>-->`
18
+ * so sibling loops under the same parent disambiguate (#1087). Without an id,
19
+ * accepts the legacy unscoped form too — used by tests that build containers
20
+ * without compiler-emitted markers.
21
+ */
22
+ function findLoopMarkers(
23
+ container: HTMLElement,
24
+ markerId?: string,
25
+ ): { startMarker: Comment | null; endMarker: Comment | null } {
26
+ let startMarker: Comment | null = null
27
+ let endMarker: Comment | null = null
28
+ if (markerId) {
29
+ const startVal = loopStartMarker(markerId)
30
+ const endVal = loopEndMarker(markerId)
31
+ for (const node of Array.from(container.childNodes)) {
32
+ if (node.nodeType !== Node.COMMENT_NODE) continue
33
+ const value = (node as Comment).nodeValue
34
+ if (value === startVal) startMarker = node as Comment
35
+ else if (value === endVal) endMarker = node as Comment
36
+ }
37
+ } else {
38
+ const startPrefix = `${BF_LOOP_START}:`
39
+ const endPrefix = `${BF_LOOP_END}:`
40
+ for (const node of Array.from(container.childNodes)) {
41
+ if (node.nodeType !== Node.COMMENT_NODE) continue
42
+ const value = (node as Comment).nodeValue ?? ''
43
+ if (!startMarker && (value === BF_LOOP_START || value.startsWith(startPrefix))) {
44
+ startMarker = node as Comment
45
+ } else if (!endMarker && (value === BF_LOOP_END || value.startsWith(endPrefix))) {
46
+ endMarker = node as Comment
47
+ }
48
+ }
49
+ }
50
+ if (startMarker && endMarker) return { startMarker, endMarker }
51
+ return { startMarker: null, endMarker: null }
52
+ }
53
+
54
+ /** Get all Element nodes between start and end comment markers. */
55
+ function getElementsBetweenMarkers(start: Comment, end: Comment): Element[] {
56
+ const elements: Element[] = []
57
+ let node: Node | null = start.nextSibling
58
+ while (node && node !== end) {
59
+ if (node.nodeType === Node.ELEMENT_NODE) {
60
+ elements.push(node as Element)
61
+ }
62
+ node = node.nextSibling
63
+ }
64
+ return elements
65
+ }
66
+
67
+ /**
68
+ * Get loop children from a container, respecting bf-loop boundary markers.
69
+ * When markers are present, returns only elements between them.
70
+ * When absent, returns all children (backward compatible).
71
+ * Exported for use by compiler-generated hydration code.
72
+ */
73
+ export function getLoopChildren(container: HTMLElement, markerId?: string): HTMLElement[] {
74
+ const { startMarker, endMarker } = findLoopMarkers(container, markerId)
75
+ if (startMarker && endMarker) {
76
+ return getElementsBetweenMarkers(startMarker, endMarker) as HTMLElement[]
77
+ }
78
+ return Array.from(container.children) as HTMLElement[]
79
+ }
80
+
81
+ /**
82
+ * Like {@link getLoopChildren}, but returns every node between the loop
83
+ * boundary markers — Comments (per-item `<!--bf-loop-i-->` markers) and
84
+ * text included. The branch-clearing path needs to remove the per-item
85
+ * marker comments alongside elements; otherwise stale markers would
86
+ * accumulate when a branch swap forces mapArray to start over (#1212).
87
+ */
88
+ export function getLoopNodes(container: HTMLElement, markerId?: string): Node[] {
89
+ const { startMarker, endMarker } = findLoopMarkers(container, markerId)
90
+ const nodes: Node[] = []
91
+ if (startMarker && endMarker) {
92
+ let node: Node | null = startMarker.nextSibling
93
+ while (node && node !== endMarker) {
94
+ nodes.push(node)
95
+ node = node.nextSibling
96
+ }
97
+ return nodes
98
+ }
99
+ return Array.from(container.childNodes)
100
+ }
@@ -1,21 +0,0 @@
1
- /**
2
- * BarefootJS - Client Marker
3
- *
4
- * Update text content for @client directive expressions
5
- * that are evaluated only on the client side.
6
- */
7
- /**
8
- * Update text content for a client marker.
9
- *
10
- * Expects comment marker format: <!--bf-client:sX-->
11
- * Both GoTemplateAdapter and HonoAdapter output this format for @client directives.
12
- *
13
- * A zero-width space (\u200B) is used as a prefix to mark text nodes managed by @client.
14
- * This allows distinguishing managed text nodes from other content.
15
- *
16
- * @param scope - The component scope element to search within
17
- * @param id - The slot ID (e.g., 's5')
18
- * @param value - The value to display (will be converted to string)
19
- */
20
- export declare function updateClientMarker(scope: Element | null, id: string, value: unknown): void;
21
- //# sourceMappingURL=client-marker.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client-marker.d.ts","sourceRoot":"","sources":["../../src/runtime/client-marker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAyB1F"}
@@ -1,21 +0,0 @@
1
- /**
2
- * BarefootJS - List Reconciliation
3
- *
4
- * Key-based DOM reconciliation for efficient list updates.
5
- * Delegates to reconcileElements for element-based rendering.
6
- */
7
- /**
8
- * Render function type for list items.
9
- * Returns an HTMLElement for each item.
10
- */
11
- export type RenderItemFn<T> = (item: T, index: number) => HTMLElement;
12
- /**
13
- * Reconcile a list container with new items using key-based matching.
14
- *
15
- * @param container - The parent element containing list items
16
- * @param items - Array of items to render
17
- * @param getKey - Function to extract a unique key from each item (or null to use index)
18
- * @param renderItem - Function to render an item as HTMLElement
19
- */
20
- export declare function reconcileList<T>(container: HTMLElement | null, items: T[], getKey: ((item: T, index: number) => string) | null, renderItem: RenderItemFn<T>): void;
21
- //# sourceMappingURL=list.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/runtime/list.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;GAGG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,WAAW,CAAA;AAErE;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,SAAS,EAAE,WAAW,GAAG,IAAI,EAC7B,KAAK,EAAE,CAAC,EAAE,EACV,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,IAAI,EACnD,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC,GAC1B,IAAI,CAkBN"}
@@ -1,47 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,44 +0,0 @@
1
- /**
2
- * BarefootJS - Element-based List Reconciliation
3
- *
4
- * Key-based DOM reconciliation for component-based list rendering.
5
- * Used when renderItem returns HTMLElement (via createComponent).
6
- */
7
- /**
8
- * Get loop children from a container, respecting bf-loop boundary markers.
9
- * When markers are present, returns only elements between them.
10
- * When absent, returns all children (backward compatible).
11
- * Exported for use by compiler-generated hydration code.
12
- */
13
- export declare function getLoopChildren(container: HTMLElement, markerId?: string): HTMLElement[];
14
- /**
15
- * Like {@link getLoopChildren}, but returns every node between the loop
16
- * boundary markers — Comments (per-item `<!--bf-loop-i-->` markers) and
17
- * text included. The branch-clearing path needs to remove the per-item
18
- * marker comments alongside elements; otherwise stale markers would
19
- * accumulate when a branch swap forces mapArray to start over (#1212).
20
- */
21
- export declare function getLoopNodes(container: HTMLElement, markerId?: string): Node[];
22
- /**
23
- * Ensure loop boundary markers exist in a container for SSR-rendered content.
24
- * SSR HTML doesn't include markers, so we insert them during hydration.
25
- * Uses itemCount to identify the last N children as loop items (rest are siblings).
26
- */
27
- export declare function ensureLoopMarkers(container: HTMLElement, itemCount: number, markerId?: string): void;
28
- /**
29
- * Reconcile a list container using HTMLElement mode (for createComponent).
30
- * Reuses existing elements by key, creates new elements as needed.
31
- *
32
- * @param container - The parent element containing list items
33
- * @param items - Array of items to render
34
- * @param getKey - Function to extract a unique key from each item (or null to use index)
35
- * @param renderItem - Function that returns an HTMLElement for each item
36
- * @param firstElement - Pre-created element for first item (avoids duplicate creation when caller already rendered item 0)
37
- */
38
- export declare function reconcileElements<T>(container: HTMLElement | null, items: T[], getKey: ((item: T, index: number) => string) | null, renderItem: (item: T, index: number) => HTMLElement, firstElement?: HTMLElement, markerId?: string): void;
39
- /**
40
- * Sync reactive DOM state from a source element to a target element.
41
- * Copies class names, replaces conditional elements, and syncs text content.
42
- */
43
- export declare function syncElementState(target: HTMLElement, source: HTMLElement): void;
44
- //# sourceMappingURL=reconcile-elements.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"reconcile-elements.d.ts","sourceRoot":"","sources":["../../src/runtime/reconcile-elements.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoEH;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,EAAE,CAMxF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,CAY9E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAgBpG;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,SAAS,EAAE,WAAW,GAAG,IAAI,EAC7B,KAAK,EAAE,CAAC,EAAE,EACV,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,IAAI,EACnD,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,WAAW,EACnD,YAAY,CAAC,EAAE,WAAW,EAC1B,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CA2JN;AAkCD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAiD/E"}
@@ -1,46 +0,0 @@
1
- /**
2
- * BarefootJS - Client Marker
3
- *
4
- * Update text content for @client directive expressions
5
- * that are evaluated only on the client side.
6
- */
7
-
8
- /**
9
- * Update text content for a client marker.
10
- *
11
- * Expects comment marker format: <!--bf-client:sX-->
12
- * Both GoTemplateAdapter and HonoAdapter output this format for @client directives.
13
- *
14
- * A zero-width space (\u200B) is used as a prefix to mark text nodes managed by @client.
15
- * This allows distinguishing managed text nodes from other content.
16
- *
17
- * @param scope - The component scope element to search within
18
- * @param id - The slot ID (e.g., 's5')
19
- * @param value - The value to display (will be converted to string)
20
- */
21
- export function updateClientMarker(scope: Element | null, id: string, value: unknown): void {
22
- if (!scope) return
23
-
24
- const marker = `bf-client:${id}`
25
- const walker = document.createTreeWalker(scope, NodeFilter.SHOW_COMMENT)
26
-
27
- while (walker.nextNode()) {
28
- if (walker.currentNode.nodeValue === marker) {
29
- const comment = walker.currentNode
30
- let textNode = comment.nextSibling
31
-
32
- // Check if next sibling is our managed text node (prefixed with zero-width space)
33
- if (textNode?.nodeType !== Node.TEXT_NODE ||
34
- !textNode.nodeValue?.startsWith('\u200B')) {
35
- // Create new text node with zero-width space marker
36
- textNode = document.createTextNode('\u200B' + String(value ?? ''))
37
- // Insert after the comment node
38
- comment.parentNode?.insertBefore(textNode, comment.nextSibling)
39
- } else {
40
- // Update existing managed text node
41
- textNode.nodeValue = '\u200B' + String(value ?? '')
42
- }
43
- return
44
- }
45
- }
46
- }
@@ -1,47 +0,0 @@
1
- /**
2
- * BarefootJS - List Reconciliation
3
- *
4
- * Key-based DOM reconciliation for efficient list updates.
5
- * Delegates to reconcileElements for element-based rendering.
6
- */
7
-
8
- import { reconcileElements } from './reconcile-elements.ts'
9
-
10
- /**
11
- * Render function type for list items.
12
- * Returns an HTMLElement for each item.
13
- */
14
- export type RenderItemFn<T> = (item: T, index: number) => HTMLElement
15
-
16
- /**
17
- * Reconcile a list container with new items using key-based matching.
18
- *
19
- * @param container - The parent element containing list items
20
- * @param items - Array of items to render
21
- * @param getKey - Function to extract a unique key from each item (or null to use index)
22
- * @param renderItem - Function to render an item as HTMLElement
23
- */
24
- export function reconcileList<T>(
25
- container: HTMLElement | null,
26
- items: T[],
27
- getKey: ((item: T, index: number) => string) | null,
28
- renderItem: RenderItemFn<T>
29
- ): void {
30
- if (!container || !items) return
31
-
32
- if (items.length === 0) {
33
- container.innerHTML = ''
34
- return
35
- }
36
-
37
- // Pre-create first element to avoid duplicate creation inside reconcileElements
38
- const firstElement = renderItem(items[0], 0)
39
-
40
- reconcileElements(
41
- container,
42
- items,
43
- getKey,
44
- renderItem,
45
- firstElement
46
- )
47
- }
@@ -1,105 +0,0 @@
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
- }