@barefootjs/client 0.4.0 → 0.5.1

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.
@@ -43,7 +43,7 @@ export interface CreateComponentSlotInfo {
43
43
  /** Slot id in the host (this child's `bf-m` value). */
44
44
  mount: string;
45
45
  }
46
- export declare function createComponent(nameOrDef: string | ComponentDef, props: Record<string, unknown>, key?: string | number, slot?: CreateComponentSlotInfo): HTMLElement;
46
+ export declare function createComponent(nameOrDef: string | ComponentDef, props?: Record<string, unknown>, key?: string | number, slot?: CreateComponentSlotInfo): HTMLElement;
47
47
  /**
48
48
  * Get the props stored for a component element.
49
49
  * Used by reconcileList to pass props to an existing element.
@@ -1 +1 @@
1
- {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/runtime/component.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAS3C,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAExD;AAWD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;;;GAGG;AACH;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAA;IACd,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,GAAG,YAAY,EAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EACrB,IAAI,CAAC,EAAE,uBAAuB,GAC7B,WAAW,CAoIb;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAE3F;AAuBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAE7G;AAGD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CAiDR;AA+DD;;;;;GAKG;AACH;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,gBAAgB,CAcjF"}
1
+ {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/runtime/component.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAS3C,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAExD;AAWD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;;;GAGG;AACH;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAA;IACd,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,GAAG,YAAY,EAChC,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACnC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EACrB,IAAI,CAAC,EAAE,uBAAuB,GAC7B,WAAW,CAyIb;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAE3F;AAuBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAE7G;AAGD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CAiDR;AA+DD;;;;;GAKG;AACH;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,gBAAgB,CAcjF"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Dynamic text/JSX slot updater (#1663).
3
+ *
4
+ * The compiler wraps reactive child expressions (`<div>{expr}</div>`) in a
5
+ * `createEffect` that writes the value into the text node sitting between
6
+ * the slot's `<!--bf:sX-->` / `<!--/-->` comment markers. That was a pure
7
+ * `nodeValue = String(value)` assignment, which is correct for primitives
8
+ * but destroys a live `Node` — e.g. when `expr` is a JSX-returning call such
9
+ * as `{themeLogo(id)}` / `{LOGOS[id]()}` whose value is the `HTMLElement`
10
+ * returned by `createComponent`. Stringifying it produced
11
+ * `"[object HTMLElement]"` (and clobbered the server-rendered subtree).
12
+ *
13
+ * `__bfText` mirrors `__bfSlot` (the branch-template equivalent): when the
14
+ * value is a `Node`, it replaces the slot region with that node by identity;
15
+ * otherwise it behaves exactly like the previous text assignment. It returns
16
+ * the node that now occupies the slot so the caller can track it across
17
+ * reactive re-runs (the previous node is detached once replaced).
18
+ */
19
+ export declare function __bfText(current: Node | null, value: unknown): Node | null;
20
+ //# sourceMappingURL=dynamic-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamic-text.d.ts","sourceRoot":"","sources":["../../src/runtime/dynamic-text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA4BH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI,CA0C1E"}
@@ -8,7 +8,7 @@ export { createPortal, isSSRPortal, findSiblingSlot, cleanupPortalPlaceholder, t
8
8
  export { reconcileList, type RenderItemFn } from './list';
9
9
  export { reconcileElements, getLoopChildren, getLoopNodes } from './reconcile-elements';
10
10
  export { qsaItem, upsertChildItem } from './qsa-item';
11
- export { mapArray } from './map-array';
11
+ export { mapArray, mapArrayAnchored } from './map-array';
12
12
  export { registerTemplate, getTemplate, hasTemplate, type TemplateFn } from './template';
13
13
  export { createComponent, renderChild, getPropsUpdateFn, getComponentProps, parseHTML, } from './component';
14
14
  export { applyRestAttrs } from './apply-rest-attrs';
@@ -19,6 +19,7 @@ export { hydrate, rehydrateAll, flushHydration, getRegisteredDef } from './hydra
19
19
  export { registerComponent, getComponentInit, initChild, upsertChild } from './registry';
20
20
  export { insert, type BranchConfig, type BranchTemplateResult } from './insert';
21
21
  export { __bfSlot } from './branch-slot';
22
+ export { __bfText } from './dynamic-text';
22
23
  export { updateClientMarker } from './client-marker';
23
24
  export { hydratedScopes } from './hydration-state';
24
25
  export { render } from './render';
@@ -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,UAAU,EACV,SAAS,EACT,OAAO,EACP,OAAO,EACP,KAAK,EACL,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,IAAI,EACT,KAAK,SAAS,EACd,KAAK,QAAQ,GACd,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,SAAS,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAIlC,OAAO,EACL,aAAa,EACb,UAAU,EACV,cAAc,EACd,eAAe,EACf,KAAK,OAAO,GACb,MAAM,WAAW,CAAA;AAGlB,OAAO,EACL,YAAY,EACZ,WAAW,EACX,eAAe,EACf,wBAAwB,EACxB,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,cAAc,GACpB,MAAM,UAAU,CAAA;AAGjB,OAAO,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,QAAQ,CAAA;AACzD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACvF,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAGtC,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAA;AAGxF,OAAO,EACL,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,GACV,MAAM,aAAa,CAAA;AAGpB,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAGpC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,aAAa,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACnG,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AACnF,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxF,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,KAAK,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAC/E,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAGpD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAGlD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAGjC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGvD,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA"}
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,UAAU,EACV,SAAS,EACT,OAAO,EACP,OAAO,EACP,KAAK,EACL,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,IAAI,EACT,KAAK,SAAS,EACd,KAAK,QAAQ,GACd,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,SAAS,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAIlC,OAAO,EACL,aAAa,EACb,UAAU,EACV,cAAc,EACd,eAAe,EACf,KAAK,OAAO,GACb,MAAM,WAAW,CAAA;AAGlB,OAAO,EACL,YAAY,EACZ,WAAW,EACX,eAAe,EACf,wBAAwB,EACxB,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,cAAc,GACpB,MAAM,UAAU,CAAA;AAGjB,OAAO,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,QAAQ,CAAA;AACzD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACvF,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAGxD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAA;AAGxF,OAAO,EACL,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,GACV,MAAM,aAAa,CAAA;AAGpB,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAGpC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,aAAa,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACnG,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AACnF,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxF,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,KAAK,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAC/E,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAGpD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAGlD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAGjC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGvD,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA"}
@@ -113,6 +113,9 @@ var BF_SCOPE_COMMENT_PREFIX = "bf-scope:";
113
113
  var BF_LOOP_START = "bf-loop";
114
114
  var BF_LOOP_END = "bf-/loop";
115
115
  var BF_LOOP_ITEM = "bf-loop-i";
116
+ function loopItemMarker(key) {
117
+ return `${BF_LOOP_ITEM}:${key}`;
118
+ }
116
119
  function loopStartMarker(markerId) {
117
120
  return `${BF_LOOP_START}:${markerId}`;
118
121
  }
@@ -361,10 +364,18 @@ function getPortalScopeId(element) {
361
364
  return info?.scopeId ?? null;
362
365
  }
363
366
  function getCommentScopeBoundary(commentNode) {
367
+ const isLoopItem = commentNode.nodeValue?.startsWith(`${BF_LOOP_ITEM}:`) ?? false;
364
368
  let node = commentNode.nextSibling;
365
369
  while (node) {
366
- if (node.nodeType === Node.COMMENT_NODE && node.nodeValue?.startsWith(BF_SCOPE_COMMENT_PREFIX)) {
367
- return node;
370
+ if (node.nodeType === Node.COMMENT_NODE) {
371
+ const value = node.nodeValue ?? "";
372
+ if (isLoopItem) {
373
+ if (value.startsWith(`${BF_LOOP_ITEM}:`) || value.startsWith(`${BF_LOOP_END}:`)) {
374
+ return node;
375
+ }
376
+ } else if (value.startsWith(BF_SCOPE_COMMENT_PREFIX)) {
377
+ return node;
378
+ }
368
379
  }
369
380
  node = node.nextSibling;
370
381
  }
@@ -941,7 +952,9 @@ function setParentScopeId(id) {
941
952
  }
942
953
  var propsUpdateMap = new WeakMap;
943
954
  var propsMap = new WeakMap;
944
- function createComponent(nameOrDef, props, key, slot) {
955
+ function createComponent(nameOrDef, props = {}, key, slot) {
956
+ if (props == null)
957
+ props = {};
945
958
  if (typeof nameOrDef !== "string") {
946
959
  return createComponentFromDef(nameOrDef, props, key);
947
960
  }
@@ -1726,6 +1739,150 @@ function mapArray(accessor, container, getKey, renderItem, markerId) {
1726
1739
  }
1727
1740
  });
1728
1741
  }
1742
+ var ITEM_PREFIX = `${BF_LOOP_ITEM}:`;
1743
+ function isItemAnchor(node) {
1744
+ return node.nodeType === Node.COMMENT_NODE && (node.nodeValue ?? "").startsWith(ITEM_PREFIX);
1745
+ }
1746
+ function collectAnchorRange(anchor, end) {
1747
+ const nodes = [anchor];
1748
+ let node = anchor.nextSibling;
1749
+ while (node && node !== end) {
1750
+ if (isItemAnchor(node))
1751
+ break;
1752
+ nodes.push(node);
1753
+ node = node.nextSibling;
1754
+ }
1755
+ return nodes;
1756
+ }
1757
+ function findItemAnchors(start, end) {
1758
+ const anchors = [];
1759
+ let node = start.nextSibling;
1760
+ while (node && node !== end) {
1761
+ if (isItemAnchor(node))
1762
+ anchors.push(node);
1763
+ node = node.nextSibling;
1764
+ }
1765
+ return anchors;
1766
+ }
1767
+ function placeAnchorScope(scope, container, before, end) {
1768
+ if (scope.pending) {
1769
+ container.insertBefore(scope.pending, before);
1770
+ scope.pending = null;
1771
+ return;
1772
+ }
1773
+ for (const node of collectAnchorRange(scope.anchor, end)) {
1774
+ container.insertBefore(node, before);
1775
+ }
1776
+ }
1777
+ function removeAnchorScope(scope, end) {
1778
+ for (const node of collectAnchorRange(scope.anchor, end)) {
1779
+ node.parentNode?.removeChild(node);
1780
+ }
1781
+ }
1782
+ function createAnchorScope(item, index, key, renderItem, existingAnchor) {
1783
+ let dispose;
1784
+ let setItem;
1785
+ let returned;
1786
+ createRoot((d) => {
1787
+ dispose = d;
1788
+ const [itemAccessor, itemSetter] = createSignal(item);
1789
+ setItem = itemSetter;
1790
+ returned = renderItem(itemAccessor, index, existingAnchor);
1791
+ return;
1792
+ });
1793
+ if (existingAnchor) {
1794
+ return { anchor: existingAnchor, pending: null, dispose, setItem };
1795
+ }
1796
+ const frag = returned;
1797
+ const anchor = frag.firstChild;
1798
+ if (anchor && !anchor.nodeValue?.startsWith(ITEM_PREFIX)) {
1799
+ anchor.nodeValue = loopItemMarker(key);
1800
+ }
1801
+ return { anchor, pending: frag, dispose, setItem };
1802
+ }
1803
+ function mapArrayAnchored(accessor, container, getKey, renderItem, markerId) {
1804
+ if (!container)
1805
+ return;
1806
+ const scopes = new Map;
1807
+ let hydrated = false;
1808
+ createEffect(() => {
1809
+ const items = accessor();
1810
+ if (!items)
1811
+ return;
1812
+ const { start, end } = findLoopMarkers2(container, markerId);
1813
+ if (!start || !end)
1814
+ return;
1815
+ if (!hydrated) {
1816
+ hydrated = true;
1817
+ const existing = findItemAnchors(start, end);
1818
+ if (existing.length > 0 && scopes.size === 0) {
1819
+ for (let i = 0;i < existing.length && i < items.length; i++) {
1820
+ const item = items[i];
1821
+ const key = getKey ? getKey(item, i) : String(i);
1822
+ scopes.set(key, createAnchorScope(item, i, key, renderItem, existing[i]));
1823
+ }
1824
+ for (let i = existing.length;i < items.length; i++) {
1825
+ const item = items[i];
1826
+ const key = getKey ? getKey(item, i) : String(i);
1827
+ const scope = createAnchorScope(item, i, key, renderItem);
1828
+ scopes.set(key, scope);
1829
+ placeAnchorScope(scope, container, end, end);
1830
+ }
1831
+ for (let i = items.length;i < existing.length; i++) {
1832
+ for (const node of collectAnchorRange(existing[i], end)) {
1833
+ node.parentNode?.removeChild(node);
1834
+ }
1835
+ }
1836
+ return;
1837
+ }
1838
+ }
1839
+ const newKeys = new Set;
1840
+ const warnedKeys = new Set;
1841
+ const desiredOrder = [];
1842
+ for (let i = 0;i < items.length; i++) {
1843
+ const item = items[i];
1844
+ const key = getKey ? getKey(item, i) : String(i);
1845
+ if (newKeys.has(key) && !warnedKeys.has(key)) {
1846
+ warnedKeys.add(key);
1847
+ console.warn(`[BarefootJS] mapArrayAnchored: duplicate key "${key}" — items with this key collapse to a ` + `single DOM scope, so only the last one renders. Use a per-item identifier (e.g. \`key={item.id}\`).`);
1848
+ }
1849
+ newKeys.add(key);
1850
+ const existing = scopes.get(key);
1851
+ if (existing) {
1852
+ existing.setItem(item);
1853
+ desiredOrder.push(existing);
1854
+ } else {
1855
+ const scope = createAnchorScope(item, i, key, renderItem);
1856
+ scopes.set(key, scope);
1857
+ desiredOrder.push(scope);
1858
+ }
1859
+ }
1860
+ for (const [key, scope] of scopes) {
1861
+ if (!newKeys.has(key)) {
1862
+ scope.dispose();
1863
+ removeAnchorScope(scope, end);
1864
+ scopes.delete(key);
1865
+ }
1866
+ }
1867
+ let inOrder = true;
1868
+ const domAnchors = findItemAnchors(start, end);
1869
+ if (domAnchors.length !== desiredOrder.length) {
1870
+ inOrder = false;
1871
+ } else {
1872
+ for (let i = 0;i < desiredOrder.length; i++) {
1873
+ if (domAnchors[i] !== desiredOrder[i].anchor) {
1874
+ inOrder = false;
1875
+ break;
1876
+ }
1877
+ }
1878
+ }
1879
+ if (!inOrder) {
1880
+ for (const scope of desiredOrder) {
1881
+ placeAnchorScope(scope, container, end, end);
1882
+ }
1883
+ }
1884
+ });
1885
+ }
1729
1886
  // src/runtime/apply-rest-attrs.ts
1730
1887
  import { createEffect as createEffect2 } from "@barefootjs/client/reactive";
1731
1888
 
@@ -1834,6 +1991,55 @@ function spreadAttrs(obj) {
1834
1991
  }
1835
1992
  // src/runtime/insert.ts
1836
1993
  import { createEffect as createEffect3, untrack as untrack2 } from "@barefootjs/client/reactive";
1994
+ function makeRegion(scope) {
1995
+ if (scope.nodeType === Node.COMMENT_NODE) {
1996
+ const anchor = scope;
1997
+ const parentEl = anchor.parentElement;
1998
+ const componentScope = parentEl?.closest(`[${BF_SCOPE}]`) ?? null;
1999
+ const parentScopeId = componentScope?.getAttribute(BF_SCOPE) ?? null;
2000
+ const proxyEl = document.createElement("bf-loop-item");
2001
+ commentScopeRegistry.set(proxyEl, { commentNode: anchor, scopeId: parentScopeId ?? "" });
2002
+ return { anchor, bindScope: proxyEl, parentScopeId };
2003
+ }
2004
+ const el = scope;
2005
+ return { anchor: null, bindScope: el, parentScopeId: el.getAttribute(BF_SCOPE) };
2006
+ }
2007
+ function findCondElInRange(anchor, id) {
2008
+ const sel = `[${BF_COND}="${id}"]`;
2009
+ const boundary = getCommentScopeBoundary(anchor);
2010
+ let node = anchor.nextSibling;
2011
+ while (node && node !== boundary) {
2012
+ if (node.nodeType === Node.ELEMENT_NODE) {
2013
+ const el = node;
2014
+ if (el.matches?.(sel))
2015
+ return el;
2016
+ const inner = el.querySelector(sel);
2017
+ if (inner)
2018
+ return inner;
2019
+ }
2020
+ node = node.nextSibling;
2021
+ }
2022
+ return null;
2023
+ }
2024
+ function findCondStartInRange(anchor, id) {
2025
+ const want = `bf-cond-start:${id}`;
2026
+ const boundary = getCommentScopeBoundary(anchor);
2027
+ let node = anchor.nextSibling;
2028
+ while (node && node !== boundary) {
2029
+ if (node.nodeType === Node.COMMENT_NODE && node.nodeValue === want) {
2030
+ return node;
2031
+ }
2032
+ if (node.nodeType === Node.ELEMENT_NODE) {
2033
+ const w = document.createTreeWalker(node, NodeFilter.SHOW_COMMENT);
2034
+ while (w.nextNode()) {
2035
+ if (w.currentNode.nodeValue === want)
2036
+ return w.currentNode;
2037
+ }
2038
+ }
2039
+ node = node.nextSibling;
2040
+ }
2041
+ return null;
2042
+ }
1837
2043
  var EMPTY_SLOTS = [];
1838
2044
  function normalizeTemplate(value) {
1839
2045
  return typeof value === "string" ? { html: value, slots: EMPTY_SLOTS } : value;
@@ -1844,7 +2050,8 @@ function evalBranchTemplate(branch) {
1844
2050
  function insert(scope, id, conditionFn, whenTrue, whenFalse) {
1845
2051
  if (!scope)
1846
2052
  return;
1847
- const parentScopeId = scope.getAttribute(BF_SCOPE);
2053
+ const region = makeRegion(scope);
2054
+ const parentScopeId = region.parentScopeId;
1848
2055
  let isFragmentCond = false;
1849
2056
  try {
1850
2057
  const sampleTrue = evalBranchTemplate(whenTrue);
@@ -1887,23 +2094,23 @@ function insert(scope, id, conditionFn, whenTrue, whenFalse) {
1887
2094
  } finally {
1888
2095
  setParentScopeId(null);
1889
2096
  }
1890
- const existingEl = find(scope, `[${BF_COND}="${id}"]`);
2097
+ const existingEl = region.anchor ? findCondElInRange(region.anchor, id) : find(region.bindScope, `[${BF_COND}="${id}"]`);
1891
2098
  if (existingEl) {
1892
2099
  const expectedSig = getTemplateRootSignature(result2.html);
1893
2100
  const existingSig = existingEl.outerHTML.match(/^<[^>]+>/)?.[0] ?? null;
1894
2101
  if (isFragmentCond && (!expectedSig || existingSig !== expectedSig)) {
1895
- updateFragmentConditional(scope, id, result2);
2102
+ updateFragmentConditional(region, id, result2);
1896
2103
  } else if (!isFragmentCond && expectedSig && existingSig && expectedSig !== existingSig) {
1897
- updateElementConditional(scope, id, result2);
2104
+ updateElementConditional(region, id, result2);
1898
2105
  } else if (result2.slots.length > 0) {
1899
- updateElementConditional(scope, id, result2);
2106
+ updateElementConditional(region, id, result2);
1900
2107
  }
1901
2108
  } else if (isFragmentCond) {
1902
- updateFragmentConditional(scope, id, result2);
2109
+ updateFragmentConditional(region, id, result2);
1903
2110
  }
1904
- const cleanup2 = branch.bindEvents(scope, { isFirstRun: true });
2111
+ const cleanup2 = branch.bindEvents(region.bindScope, { isFirstRun: true });
1905
2112
  branchCleanup = typeof cleanup2 === "function" ? cleanup2 : null;
1906
- autoFocusConditionalElement(scope, id);
2113
+ autoFocusConditionalElement(region, id);
1907
2114
  return;
1908
2115
  }
1909
2116
  if (currCond === prevVal) {
@@ -1921,18 +2128,18 @@ function insert(scope, id, conditionFn, whenTrue, whenFalse) {
1921
2128
  setParentScopeId(null);
1922
2129
  }
1923
2130
  if (isFragmentCond) {
1924
- updateFragmentConditional(scope, id, result);
2131
+ updateFragmentConditional(region, id, result);
1925
2132
  } else {
1926
- updateElementConditional(scope, id, result);
2133
+ updateElementConditional(region, id, result);
1927
2134
  }
1928
- const cleanup = branch.bindEvents(scope, { isFirstRun: false });
2135
+ const cleanup = branch.bindEvents(region.bindScope, { isFirstRun: false });
1929
2136
  branchCleanup = typeof cleanup === "function" ? cleanup : null;
1930
- autoFocusConditionalElement(scope, id);
2137
+ autoFocusConditionalElement(region, id);
1931
2138
  });
1932
2139
  }
1933
- function autoFocusConditionalElement(scope, id) {
2140
+ function autoFocusConditionalElement(region, id) {
1934
2141
  requestAnimationFrame(() => {
1935
- const condEl = scope.querySelector(`[${BF_COND}="${id}"]`);
2142
+ const condEl = region.anchor ? findCondElInRange(region.anchor, id) : region.bindScope.querySelector(`[${BF_COND}="${id}"]`);
1936
2143
  if (condEl) {
1937
2144
  const autofocusEl = condEl.matches("[autofocus]") ? condEl : condEl.querySelector("[autofocus]");
1938
2145
  if (autofocusEl && typeof autofocusEl.focus === "function") {
@@ -1965,18 +2172,23 @@ function spliceSlots(fragment, slots) {
1965
2172
  }
1966
2173
  return fragment;
1967
2174
  }
1968
- function updateFragmentConditional(scope, id, result) {
2175
+ function updateFragmentConditional(region, id, result) {
1969
2176
  const { html, slots } = result;
2177
+ const scope = region.bindScope;
1970
2178
  const startMarker = `bf-cond-start:${id}`;
1971
2179
  let startComment = null;
1972
- const walker = document.createTreeWalker(scope, NodeFilter.SHOW_COMMENT);
1973
- while (walker.nextNode()) {
1974
- if (walker.currentNode.nodeValue === startMarker) {
1975
- startComment = walker.currentNode;
1976
- break;
2180
+ if (region.anchor) {
2181
+ startComment = findCondStartInRange(region.anchor, id);
2182
+ } else {
2183
+ const walker = document.createTreeWalker(scope, NodeFilter.SHOW_COMMENT);
2184
+ while (walker.nextNode()) {
2185
+ if (walker.currentNode.nodeValue === startMarker) {
2186
+ startComment = walker.currentNode;
2187
+ break;
2188
+ }
1977
2189
  }
1978
2190
  }
1979
- const condEl = scope.querySelector(`[${BF_COND}="${id}"]`);
2191
+ const condEl = region.anchor ? findCondElInRange(region.anchor, id) : scope.querySelector(`[${BF_COND}="${id}"]`);
1980
2192
  const endMarker = `bf-cond-end:${id}`;
1981
2193
  if (startComment) {
1982
2194
  const nodesToRemove = [];
@@ -2015,8 +2227,8 @@ function updateFragmentConditional(scope, id, result) {
2015
2227
  }
2016
2228
  }
2017
2229
  }
2018
- function updateElementConditional(scope, id, result) {
2019
- const condEl = scope.querySelector(`[${BF_COND}="${id}"]`);
2230
+ function updateElementConditional(region, id, result) {
2231
+ const condEl = region.anchor ? findCondElInRange(region.anchor, id) : region.bindScope.querySelector(`[${BF_COND}="${id}"]`);
2020
2232
  if (!condEl)
2021
2233
  return;
2022
2234
  const { html, slots } = result;
@@ -2041,6 +2253,58 @@ function __bfSlot(value, slots) {
2041
2253
  }
2042
2254
  return String(value);
2043
2255
  }
2256
+ // src/runtime/dynamic-text.ts
2257
+ var END_MARKER = "/";
2258
+ function clearSlotRegion(start, keep) {
2259
+ let n = start.nextSibling;
2260
+ while (n && !(n.nodeType === Node.COMMENT_NODE && n.nodeValue === END_MARKER)) {
2261
+ const next = n.nextSibling;
2262
+ if (n !== keep)
2263
+ n.parentNode?.removeChild(n);
2264
+ n = next;
2265
+ }
2266
+ }
2267
+ function slotStart(node) {
2268
+ let n = node.previousSibling;
2269
+ while (n && n.nodeType !== Node.COMMENT_NODE)
2270
+ n = n.previousSibling;
2271
+ return n;
2272
+ }
2273
+ function __bfText(current, value) {
2274
+ if (!current)
2275
+ return current;
2276
+ if (value != null && value.__isSlot)
2277
+ return current;
2278
+ if (typeof Node !== "undefined" && value instanceof Node) {
2279
+ if (value === current)
2280
+ return current;
2281
+ const start2 = current.previousSibling;
2282
+ if (start2 && start2.nodeType === Node.COMMENT_NODE) {
2283
+ clearSlotRegion(start2);
2284
+ start2.parentNode?.insertBefore(value, start2.nextSibling);
2285
+ return value;
2286
+ }
2287
+ current.parentNode?.replaceChild(value, current);
2288
+ return value;
2289
+ }
2290
+ const text = String(value ?? "");
2291
+ if (current.nodeType === Node.TEXT_NODE) {
2292
+ current.nodeValue = text;
2293
+ const start2 = slotStart(current);
2294
+ if (start2 && start2.nodeType === Node.COMMENT_NODE)
2295
+ clearSlotRegion(start2, current);
2296
+ return current;
2297
+ }
2298
+ const start = current.previousSibling;
2299
+ const textNode = (current.ownerDocument ?? document).createTextNode(text);
2300
+ if (start && start.nodeType === Node.COMMENT_NODE) {
2301
+ clearSlotRegion(start);
2302
+ start.parentNode?.insertBefore(textNode, start.nextSibling);
2303
+ } else {
2304
+ current.parentNode?.replaceChild(textNode, current);
2305
+ }
2306
+ return textNode;
2307
+ }
2044
2308
  // src/runtime/client-marker.ts
2045
2309
  function updateClientMarker(scope, id, value) {
2046
2310
  if (!scope)
@@ -2161,6 +2425,7 @@ export {
2161
2425
  parseHTML,
2162
2426
  onMount,
2163
2427
  onCleanup,
2428
+ mapArrayAnchored,
2164
2429
  mapArray,
2165
2430
  isSSRPortal,
2166
2431
  insert,
@@ -2194,6 +2459,7 @@ export {
2194
2459
  applyRestAttrs,
2195
2460
  __slot,
2196
2461
  __bf_swap,
2462
+ __bfText,
2197
2463
  __bfSlot,
2198
2464
  $t,
2199
2465
  $c,
@@ -71,5 +71,5 @@ export interface BranchConfig {
71
71
  * @param whenTrue - Branch config for when condition is true
72
72
  * @param whenFalse - Branch config for when condition is false
73
73
  */
74
- export declare function insert(scope: Element | null, id: string, conditionFn: () => boolean, whenTrue: BranchConfig, whenFalse: BranchConfig): void;
74
+ export declare function insert(scope: Element | Comment | null, id: string, conditionFn: () => boolean, whenTrue: BranchConfig, whenFalse: BranchConfig): void;
75
75
  //# sourceMappingURL=insert.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"insert.d.ts","sourceRoot":"","sources":["../../src/runtime/insert.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,IAAI,EAAE,CAAA;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,QAAQ,EAAE,MAAM,MAAM,GAAG,oBAAoB,CAAA;IAE7C;;;;;;OAMG;IACH,UAAU,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;CACrF;AAyBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CACpB,KAAK,EAAE,OAAO,GAAG,IAAI,EACrB,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,MAAM,OAAO,EAC1B,QAAQ,EAAE,YAAY,EACtB,SAAS,EAAE,YAAY,GACtB,IAAI,CAsIN"}
1
+ {"version":3,"file":"insert.d.ts","sourceRoot":"","sources":["../../src/runtime/insert.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAmFH;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,IAAI,EAAE,CAAA;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,QAAQ,EAAE,MAAM,MAAM,GAAG,oBAAoB,CAAA;IAE7C;;;;;;OAMG;IACH,UAAU,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;CACrF;AAyBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CACpB,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,EAC/B,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,MAAM,OAAO,EAC1B,QAAQ,EAAE,YAAY,EACtB,SAAS,EAAE,YAAY,GACtB,IAAI,CA+IN"}
@@ -29,4 +29,13 @@
29
29
  * When `existing` is undefined, creates a new element and returns it.
30
30
  */
31
31
  export declare function mapArray<T>(accessor: () => T[], container: HTMLElement | null, getKey: ((item: T, index: number) => string) | null, renderItem: (item: () => T, index: number, existing?: HTMLElement) => HTMLElement, markerId?: string): void;
32
+ /**
33
+ * Per-item scoped list rendering for whole-item conditionals (#1665).
34
+ *
35
+ * Same call shape as `mapArray`, but `renderItem` returns a `DocumentFragment`
36
+ * (CSR, first child = `bf-loop-i:KEY` anchor) or the existing anchor Comment
37
+ * (hydration). Items may render zero elements; the anchor is the stable
38
+ * identity and position.
39
+ */
40
+ export declare function mapArrayAnchored<T>(accessor: () => T[], container: HTMLElement | null, getKey: ((item: T, index: number) => string) | null, renderItem: (item: () => T, index: number, existing?: Comment) => DocumentFragment | Comment, markerId?: string): void;
32
41
  //# sourceMappingURL=map-array.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"map-array.d.ts","sourceRoot":"","sources":["../../src/runtime/map-array.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AA4LH;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EACxB,QAAQ,EAAE,MAAM,CAAC,EAAE,EACnB,SAAS,EAAE,WAAW,GAAG,IAAI,EAC7B,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,IAAI,EACnD,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,WAAW,KAAK,WAAW,EACjF,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAwKN"}
1
+ {"version":3,"file":"map-array.d.ts","sourceRoot":"","sources":["../../src/runtime/map-array.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AA6LH;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EACxB,QAAQ,EAAE,MAAM,CAAC,EAAE,EACnB,SAAS,EAAE,WAAW,GAAG,IAAI,EAC7B,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,IAAI,EACnD,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,WAAW,KAAK,WAAW,EACjF,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAwKN;AAqHD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,QAAQ,EAAE,MAAM,CAAC,EAAE,EACnB,SAAS,EAAE,WAAW,GAAG,IAAI,EAC7B,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,IAAI,EACnD,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,gBAAgB,GAAG,OAAO,EAC5F,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CA+FN"}
@@ -22,7 +22,13 @@ export declare const commentScopeRegistry: WeakMap<Element, CommentScopeInfo>;
22
22
  export declare function getPortalScopeId(element: Element): string | null;
23
23
  /**
24
24
  * Find the end boundary for a comment-based scope.
25
- * The boundary is the next bf-scope: comment or the end of the parent's children.
25
+ *
26
+ * The boundary depends on the anchor's kind:
27
+ * - `bf-scope:` anchor (fragment-root component): boundary is the next
28
+ * `bf-scope:` comment or the end of the parent's children (unchanged).
29
+ * - `bf-loop-i:<key>` anchor (loop item, #1665): boundary is the next
30
+ * loop-item anchor (`bf-loop-i:*`) or the loop end marker (`bf-/loop:*`),
31
+ * so one item's range never bleeds into the next item or past the loop.
26
32
  */
27
33
  export declare function getCommentScopeBoundary(commentNode: Comment): Node | null;
28
34
  //# sourceMappingURL=scope.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/runtime/scope.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,OAAO,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,eAAO,MAAM,oBAAoB,oCAA2C,CAAA;AAE5E;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAGhE;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI,CAUzE"}
1
+ {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/runtime/scope.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,OAAO,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,eAAO,MAAM,oBAAoB,oCAA2C,CAAA;AAE5E;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAGhE;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI,CAiBzE"}