@barefootjs/client 0.26.3 → 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,11 +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
+ export { patchLeaf } from './patch-leaf.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'
85
91
 
86
92
  // Template registry
87
93
  export { registerTemplate, getTemplate, hasTemplate, type TemplateFn } from './template.ts'
@@ -90,11 +96,10 @@ export { registerTemplate, getTemplate, hasTemplate, type TemplateFn } from './t
90
96
  export {
91
97
  createComponent,
92
98
  renderChild,
93
- getPropsUpdateFn,
94
- getComponentProps,
95
99
  parseHTML,
96
100
  escapeAttr,
97
101
  escapeText,
102
+ escapeTextOrNode,
98
103
  } from './component.ts'
99
104
 
100
105
  // Spread props helpers
@@ -109,8 +114,14 @@ export { hydrate, rehydrateAll, rehydrateScope, disposeScope, flushHydration, ge
109
114
  export { registerComponent, getComponentInit, initChild, upsertChild } from './registry.ts'
110
115
  export { insert, type BranchConfig, type BranchTemplateResult } from './insert.ts'
111
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.
112
124
  export { __bfText } from './dynamic-text.ts'
113
- export { updateClientMarker } from './client-marker.ts'
114
125
 
115
126
  // Hydration state
116
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
+ }
@@ -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
+ }
@@ -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,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
- }