@barefootjs/client 0.33.2 → 0.33.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.
@@ -168754,7 +168754,7 @@ Additional information: BADCLIENT: Bad error code, ${badCode} not found in range
168754
168754
  });
168755
168755
 
168756
168756
  // ../jsx/src/compiler.ts
168757
- var import_typescript24 = __toESM(require_typescript(), 1);
168757
+ var import_typescript25 = __toESM(require_typescript(), 1);
168758
168758
 
168759
168759
  // ../jsx/src/analyzer.ts
168760
168760
  var import_typescript9 = __toESM(require_typescript(), 1);
@@ -169998,11 +169998,14 @@ var KEYWORDS_AND_GLOBALS = new Set([
169998
169998
  // ../jsx/src/ir-to-client-js/walk-prop-accesses.ts
169999
169999
  var import_typescript14 = __toESM(require_typescript(), 1);
170000
170000
 
170001
+ // ../jsx/src/ir-to-client-js/imports.ts
170002
+ var import_typescript16 = __toESM(require_typescript(), 1);
170003
+
170001
170004
  // ../jsx/src/value-references.ts
170002
170005
  var import_typescript15 = __toESM(require_typescript(), 1);
170003
170006
 
170004
170007
  // ../jsx/src/relocate.ts
170005
- var import_typescript16 = __toESM(require_typescript(), 1);
170008
+ var import_typescript17 = __toESM(require_typescript(), 1);
170006
170009
 
170007
170010
  // ../jsx/src/lowering-registry.ts
170008
170011
  var plugins = [];
@@ -170181,10 +170184,10 @@ function formatDateLocalNames(metadata) {
170181
170184
  }
170182
170185
 
170183
170186
  // ../jsx/src/ir-to-client-js/prune-unused-prop-extractions.ts
170184
- var import_typescript17 = __toESM(require_typescript(), 1);
170187
+ var import_typescript18 = __toESM(require_typescript(), 1);
170185
170188
 
170186
170189
  // ../jsx/src/ir-to-client-js/control-flow/plan/lazy-preamble.ts
170187
- var import_typescript18 = __toESM(require_typescript(), 1);
170190
+ var import_typescript19 = __toESM(require_typescript(), 1);
170188
170191
  var NO_PREAMBLE = {
170189
170192
  lazySafe: true,
170190
170193
  facts: { declaredNames: new Set, freeNames: new Set }
@@ -170234,7 +170237,7 @@ var INERT_BINDING_GLOBALS = new Set([
170234
170237
  ]);
170235
170238
 
170236
170239
  // ../jsx/src/ir-to-client-js/emit-reactive.ts
170237
- var import_typescript19 = __toESM(require_typescript(), 1);
170240
+ var import_typescript20 = __toESM(require_typescript(), 1);
170238
170241
 
170239
170242
  // ../jsx/src/ir-to-client-js/control-flow/stringify/event-delegation.ts
170240
170243
  var NON_BUBBLING_EVENTS = new Set([
@@ -170249,7 +170252,7 @@ var NON_BUBBLING_EVENTS = new Set([
170249
170252
  ]);
170250
170253
 
170251
170254
  // ../jsx/src/ir-to-client-js/rewrite-props-object.ts
170252
- var import_typescript20 = __toESM(require_typescript(), 1);
170255
+ var import_typescript21 = __toESM(require_typescript(), 1);
170253
170256
 
170254
170257
  // ../jsx/src/ir-to-client-js/source-map.ts
170255
170258
  var BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -170340,20 +170343,20 @@ class SourceMapGenerator {
170340
170343
  }
170341
170344
 
170342
170345
  // ../jsx/src/preprocess-inline-jsx-callbacks.ts
170343
- var import_typescript21 = __toESM(require_typescript(), 1);
170346
+ var import_typescript22 = __toESM(require_typescript(), 1);
170344
170347
 
170345
170348
  // ../jsx/src/ssr-defaults.ts
170346
- var import_typescript22 = __toESM(require_typescript(), 1);
170349
+ var import_typescript23 = __toESM(require_typescript(), 1);
170347
170350
  var UNRESOLVED = Symbol("unresolved");
170348
170351
  var NO_RETURN = Symbol("no-return");
170349
170352
 
170350
170353
  // ../jsx/src/augment-inherited-props.ts
170351
- var import_typescript23 = __toESM(require_typescript(), 1);
170354
+ var import_typescript24 = __toESM(require_typescript(), 1);
170352
170355
 
170353
170356
  // ../jsx/src/rich-type-refusal.ts
170354
170357
  var EMPTY_BINDINGS2 = new Map;
170355
170358
  // ../jsx/src/shared-program.ts
170356
- var import_typescript25 = __toESM(require_typescript(), 1);
170359
+ var import_typescript26 = __toESM(require_typescript(), 1);
170357
170360
  // ../jsx/src/adapters/interface.ts
170358
170361
  class BaseAdapter {
170359
170362
  renderChildren(children) {
@@ -170635,7 +170638,7 @@ class JsxAdapter extends BaseAdapter {
170635
170638
  }
170636
170639
 
170637
170640
  // ../jsx/src/adapters/template-imports.ts
170638
- var import_typescript26 = __toESM(require_typescript(), 1);
170641
+ var import_typescript27 = __toESM(require_typescript(), 1);
170639
170642
  var CLIENT_PACKAGE_SOURCES = new Set([
170640
170643
  "@barefootjs/client",
170641
170644
  "@barefootjs/client/runtime"
@@ -171028,11 +171031,11 @@ function registerBuiltinLoweringPlugins() {
171028
171031
  registerLoweringPlugin(plugin);
171029
171032
  }
171030
171033
  // ../jsx/src/combine-client-js.ts
171031
- var import_typescript27 = __toESM(require_typescript(), 1);
171032
- // ../jsx/src/debug.ts
171033
171034
  var import_typescript28 = __toESM(require_typescript(), 1);
171034
- // ../jsx/src/profiler.ts
171035
+ // ../jsx/src/debug.ts
171035
171036
  var import_typescript29 = __toESM(require_typescript(), 1);
171037
+ // ../jsx/src/profiler.ts
171038
+ var import_typescript30 = __toESM(require_typescript(), 1);
171036
171039
 
171037
171040
  // ../jsx/src/index.ts
171038
171041
  registerBuiltinLoweringPlugins();
@@ -105,8 +105,8 @@ export interface CreateComponentSlotInfo {
105
105
  * `upsertChildItem` (qsa-item.ts) assert nothing — the narrowing is the
106
106
  * signature's job, not theirs.
107
107
  */
108
- export declare function createComponent(nameOrDef: string | ComponentDef, props: Record<string, unknown>, key: string | number | undefined, slot: CreateComponentSlotInfo | undefined, mountAt: Element): HTMLElement;
109
- export declare function createComponent(nameOrDef: string | ComponentDef, props?: Record<string, unknown>, key?: string | number, slot?: CreateComponentSlotInfo, mountAt?: Element | null): HTMLElement | DocumentFragment;
108
+ export declare function createComponent(nameOrDef: string | ComponentDef, props: Record<string, unknown>, key: string | number | undefined, slot: CreateComponentSlotInfo | undefined, mountAt: Element, keyAttrName?: string): HTMLElement;
109
+ export declare function createComponent(nameOrDef: string | ComponentDef, props?: Record<string, unknown>, key?: string | number, slot?: CreateComponentSlotInfo, mountAt?: Element | null, keyAttrName?: string): HTMLElement | DocumentFragment;
110
110
  /**
111
111
  * Render a child component's template to an HTML string.
112
112
  * Used by compiler-generated template functions when a stateless component
@@ -221,6 +221,31 @@ export declare function isBfMarkup(value: unknown): value is BfMarkup;
221
221
  * is actually branded.
222
222
  */
223
223
  export declare function escapeTextOrMarkup(value: unknown): string;
224
+ /**
225
+ * Nullish guard for a bare `${children}` passthrough splice
226
+ * (`ir-to-client-js/html-template.ts`'s no-`slotId` `'expression'` branches
227
+ * — the "bare `${...}` interpolations" the docstring above `escapeTextSlotExpr`
228
+ * describes) — #2775. The value here is ALREADY-STRINGIFIED MARKUP, not an
229
+ * arbitrary prop: `materializeComponent` (this file, "Template functions
230
+ * expect children as an HTML string, not an array") joins a component's
231
+ * `children` into an HTML string before the template lambda ever runs, so by
232
+ * the time this function sees the value it is either that HTML string or,
233
+ * when the caller passed none, simply `undefined` (the key is absent from
234
+ * the props object entirely). This function's ENTIRE job is that one
235
+ * nullish case: turn the absent/`null` value into `''`, same as SSR's
236
+ * `{props.children}` and the DSL adapters' `bf.string(children)` render it.
237
+ *
238
+ * Deliberately NOT `escapeTextOrMarkup`: that function falls back to
239
+ * `escapeText` for anything not `bfMarkup()`-branded, and a `children`
240
+ * string is never branded (it is a plain string built by
241
+ * `materializeComponent`, not compiler-escaped piece-by-piece the way a
242
+ * `bfMarkup()` value is) — routing `children` through it would HTML-escape
243
+ * real markup into visible `&lt;span&gt;`-style text on every call that
244
+ * actually has children, trading the no-children bug for a strictly worse
245
+ * with-children one. So: nullish becomes `''`; every other value is
246
+ * returned completely unescaped, exactly as it arrived.
247
+ */
248
+ export declare function markupOrEmpty(value: unknown): string;
224
249
  /**
225
250
  * `escapeText`'s counterpart for a claimed 'markup' slot's REACTIVE write
226
251
  * (slot unification A3 follow-up), where the value is a plain-JS expression
@@ -1 +1 @@
1
- {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/runtime/component.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/runtime/component.ts"],"names":[],"mappings":"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"}
@@ -15,7 +15,7 @@ export { mapArrayLazy, type LazyRowEntry, type LazyRowPlan } from './map-array-l
15
15
  export { patchLeaf } from './patch-leaf.ts';
16
16
  export { claimSlots, lazySlots, lazyClaimSlots, textOrNode, type SlotSpec, type ClaimPlan, type ClaimedSlots, type ClaimedSlotsRW, type SlotWriter } from './claim-slots.ts';
17
17
  export { registerTemplate, getTemplate, hasTemplate, type TemplateFn } from './template.ts';
18
- export { createComponent, mountRowRoot, renderChild, parseHTML, escapeAttr, escapeText, escapeTextOrNode, bfMarkup, isBfMarkup, escapeTextOrMarkup, type BfMarkup, } from './component.ts';
18
+ export { createComponent, mountRowRoot, renderChild, parseHTML, escapeAttr, escapeText, escapeTextOrNode, bfMarkup, isBfMarkup, escapeTextOrMarkup, markupOrEmpty, type BfMarkup, } from './component.ts';
19
19
  export { applyRestAttrs } from './apply-rest-attrs.ts';
20
20
  export { spreadAttrs } from './spread-attrs.ts';
21
21
  export { styleToCss } from './style.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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"}
@@ -1368,25 +1368,25 @@ function mountRowRoot(el) {
1368
1368
  }
1369
1369
  return el;
1370
1370
  }
1371
- function createComponent(nameOrDef, props = {}, key, slot, mountAt) {
1372
- const element = materializeComponent(nameOrDef, props, key, slot, mountAt);
1371
+ function createComponent(nameOrDef, props = {}, key, slot, mountAt, keyAttrName2 = BF_KEY) {
1372
+ const element = materializeComponent(nameOrDef, props, key, slot, mountAt, keyAttrName2);
1373
1373
  if (mountAt && mountAt.parentNode && element !== mountAt) {
1374
1374
  mountAt.replaceWith(element);
1375
1375
  }
1376
1376
  return element;
1377
1377
  }
1378
- function materializeComponent(nameOrDef, props = {}, key, slot, mountAt) {
1378
+ function materializeComponent(nameOrDef, props = {}, key, slot, mountAt, keyAttrName2 = BF_KEY) {
1379
1379
  if (props == null)
1380
1380
  props = {};
1381
1381
  const rowMount = mountAt ? null : takeRowMountPoint();
1382
1382
  if (typeof nameOrDef !== "string") {
1383
- return createComponentFromDef(nameOrDef, props, key, mountAt, rowMount);
1383
+ return createComponentFromDef(nameOrDef, props, key, mountAt, rowMount, keyAttrName2);
1384
1384
  }
1385
1385
  const name = nameOrDef;
1386
1386
  const templateFn = getTemplate(name);
1387
1387
  if (!templateFn) {
1388
1388
  console.warn(`[BarefootJS] Template not found for component: ${name}`);
1389
- return createPlaceholder(name, key);
1389
+ return createPlaceholder(name, key, keyAttrName2);
1390
1390
  }
1391
1391
  const childrenDescriptor = Object.getOwnPropertyDescriptor(props, "children");
1392
1392
  const childrenIsGetter = childrenDescriptor != null && typeof childrenDescriptor.get === "function";
@@ -1419,6 +1419,8 @@ function materializeComponent(nameOrDef, props = {}, key, slot, mountAt) {
1419
1419
  _parentScopeId = scopeId;
1420
1420
  } else if (slot?.parent) {
1421
1421
  _parentScopeId = slot.parent;
1422
+ } else if (!_parentScopeId) {
1423
+ _parentScopeId = `${def?.name ?? name}_${generateId()}`;
1422
1424
  }
1423
1425
  let html;
1424
1426
  try {
@@ -1431,11 +1433,11 @@ function materializeComponent(nameOrDef, props = {}, key, slot, mountAt) {
1431
1433
  const element = isFragmentRoot ? roots.find((node) => node.nodeType === Node.ELEMENT_NODE) : roots[0];
1432
1434
  if (isFragmentRoot && roots.length > 0 && !element) {
1433
1435
  console.warn(`[BarefootJS] Fragment-root component ${name} rendered no element root; ` + "a scope needs at least one element to attach to. Wrap the content in an element.");
1434
- return createPlaceholder(name, key);
1436
+ return createPlaceholder(name, key, keyAttrName2);
1435
1437
  }
1436
1438
  if (!element) {
1437
1439
  console.warn(`[BarefootJS] Template returned empty HTML for component: ${name}`);
1438
- return createPlaceholder(name, key);
1440
+ return createPlaceholder(name, key, keyAttrName2);
1439
1441
  }
1440
1442
  if (scopeId && !isFragmentRoot) {
1441
1443
  element.setAttribute(BF_SCOPE, scopeId);
@@ -1446,7 +1448,7 @@ function materializeComponent(nameOrDef, props = {}, key, slot, mountAt) {
1446
1448
  element.setAttribute(BF_AT, slot.mount);
1447
1449
  }
1448
1450
  if (key !== undefined) {
1449
- element.setAttribute(BF_KEY, String(key));
1451
+ element.setAttribute(keyAttrName2, String(key));
1450
1452
  }
1451
1453
  const fragmentComments = isFragmentRoot && scopeId ? {
1452
1454
  start: document.createComment(`${BF_SCOPE_COMMENT_PREFIX}${scopeId}`),
@@ -1562,11 +1564,11 @@ var ROOT_HAS_BFS_PATTERN = /^<\w+[^>]*\sbf-s="/;
1562
1564
  function generateId() {
1563
1565
  return Math.random().toString(36).slice(2, 8);
1564
1566
  }
1565
- function createPlaceholder(name, key) {
1567
+ function createPlaceholder(name, key, keyAttrName2 = BF_KEY) {
1566
1568
  const el = document.createElement("div");
1567
1569
  el.setAttribute(BF_SCOPE, `${name}_placeholder`);
1568
1570
  if (key !== undefined) {
1569
- el.setAttribute(BF_KEY, String(key));
1571
+ el.setAttribute(keyAttrName2, String(key));
1570
1572
  }
1571
1573
  el.textContent = `[${name}]`;
1572
1574
  el.style.cssText = "color: red; border: 1px dashed red; padding: 4px;";
@@ -1615,6 +1617,9 @@ function escapeTextOrMarkup(value) {
1615
1617
  return value[BF_MARKUP_BRAND];
1616
1618
  return escapeText(value);
1617
1619
  }
1620
+ function markupOrEmpty(value) {
1621
+ return value == null ? "" : value;
1622
+ }
1618
1623
  function escapeTextOrNode(value) {
1619
1624
  if (isBfMarkup(value))
1620
1625
  return value[BF_MARKUP_BRAND];
@@ -1676,7 +1681,7 @@ function insertGetterChildren(element, children) {
1676
1681
  element.appendChild(document.createTextNode(String(children)));
1677
1682
  }
1678
1683
  }
1679
- function createComponentFromDef(def, props, key, mountAt, rowMount) {
1684
+ function createComponentFromDef(def, props, key, mountAt, rowMount, keyAttrName2 = BF_KEY) {
1680
1685
  if (!def.template) {
1681
1686
  throw new Error("[BarefootJS] createComponent with ComponentDef requires a template function");
1682
1687
  }
@@ -1693,7 +1698,7 @@ function createComponentFromDef(def, props, key, mountAt, rowMount) {
1693
1698
  const scopeId = `${name}_${generateId()}`;
1694
1699
  element.setAttribute(BF_SCOPE, scopeId);
1695
1700
  if (key !== undefined) {
1696
- element.setAttribute(BF_KEY, String(key));
1701
+ element.setAttribute(keyAttrName2, String(key));
1697
1702
  }
1698
1703
  if (mountAt) {
1699
1704
  mountAt.replaceWith(element);
@@ -2075,7 +2080,7 @@ function createItemScope(item, index, renderItem, existingPrimary, existingExtra
2075
2080
  }
2076
2081
  return { startMarker, primaryEl, extras, scopeComments, dispose, setItem };
2077
2082
  }
2078
- function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
2083
+ function mapArray(accessor, container, getKey, renderItem, markerId, bfId, keyAttrName2 = BF_KEY) {
2079
2084
  if (!container)
2080
2085
  return;
2081
2086
  const scopes = new Map;
@@ -2100,13 +2105,15 @@ function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
2100
2105
  if (!hydrated) {
2101
2106
  hydrated = true;
2102
2107
  const existingRanges = startMarker ? findItemRanges(startMarker, endMarker) : Array.from(container.children).map((el) => ({ startMarker: null, primaryEl: el, extras: [], scopeComments: null }));
2103
- const needsHydration = existingRanges.length > 0 && (!existingRanges[0]?.primaryEl.hasAttribute("data-key") || scopes.size === 0);
2108
+ const needsHydration = existingRanges.length > 0;
2104
2109
  if (needsHydration) {
2105
2110
  for (let i2 = 0;i2 < existingRanges.length && i2 < items.length; i2++) {
2106
2111
  const range = existingRanges[i2];
2107
2112
  const item = items[i2];
2108
2113
  const key = getKey ? getKey(item, i2) : String(i2);
2109
- range.primaryEl.setAttribute(BF_KEY, key);
2114
+ if (getKey && !range.primaryEl.getAttribute(keyAttrName2)) {
2115
+ range.primaryEl.setAttribute(keyAttrName2, key);
2116
+ }
2110
2117
  const scope = createItemScope(item, i2, renderItem, range.primaryEl, range.extras, range.startMarker, range.scopeComments);
2111
2118
  scopes.set(key, scope);
2112
2119
  hydratedScopes.add(range.primaryEl);
@@ -2115,8 +2122,8 @@ function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
2115
2122
  const item = items[i2];
2116
2123
  const key = getKey ? getKey(item, i2) : String(i2);
2117
2124
  const scope = createItemScope(item, i2, renderItem, undefined, undefined, undefined, undefined, { container, anchor });
2118
- if (!scope.primaryEl.dataset.key)
2119
- scope.primaryEl.setAttribute(BF_KEY, key);
2125
+ if (getKey && !scope.primaryEl.getAttribute(keyAttrName2))
2126
+ scope.primaryEl.setAttribute(keyAttrName2, key);
2120
2127
  scopes.set(key, scope);
2121
2128
  insertScope(scope, container, anchor);
2122
2129
  }
@@ -2141,7 +2148,7 @@ function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
2141
2148
  if (scopes.size === 0) {
2142
2149
  const loopRanges = startMarker ? findItemRanges(startMarker, endMarker) : Array.from(container.children).map((el) => ({ startMarker: null, primaryEl: el, extras: [], scopeComments: null }));
2143
2150
  for (const range of loopRanges) {
2144
- const existingKey = range.primaryEl.dataset?.key;
2151
+ const existingKey = range.primaryEl.getAttribute(keyAttrName2);
2145
2152
  if (existingKey && !scopes.has(existingKey)) {
2146
2153
  scopes.set(existingKey, {
2147
2154
  startMarker: range.startMarker,
@@ -2199,8 +2206,8 @@ function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
2199
2206
  desiredOrder.push(existing);
2200
2207
  } else {
2201
2208
  const scope = createItemScope(item, i2, renderItem, undefined, undefined, undefined, undefined, { container, anchor });
2202
- if (!scope.primaryEl.dataset.key)
2203
- scope.primaryEl.setAttribute(BF_KEY, key);
2209
+ if (getKey && !scope.primaryEl.getAttribute(keyAttrName2))
2210
+ scope.primaryEl.setAttribute(keyAttrName2, key);
2204
2211
  scopes.set(key, scope);
2205
2212
  desiredOrder.push(scope);
2206
2213
  }
@@ -2396,7 +2403,7 @@ function mapArrayAnchored(accessor, container, getKey, renderItem, markerId, bfI
2396
2403
  }
2397
2404
  // src/runtime/map-array-lazy.ts
2398
2405
  import { createEffect as createEffect2, createSignal as createSignal2, untrack as untrack2 } from "@barefootjs/client/reactive";
2399
- function mapArrayLazy(accessor, container, getKey, plan, markerId, bfId) {
2406
+ function mapArrayLazy(accessor, container, getKey, plan, markerId, bfId, keyAttrName2 = BF_KEY) {
2400
2407
  if (!container)
2401
2408
  return;
2402
2409
  const entries = new Map;
@@ -2435,8 +2442,8 @@ function mapArrayLazy(accessor, container, getKey, plan, markerId, bfId) {
2435
2442
  last: null
2436
2443
  };
2437
2444
  entry.primaryEl = untrack2(() => plan.createRow(entry, index));
2438
- if (!entry.primaryEl.dataset.key)
2439
- entry.primaryEl.setAttribute(BF_KEY, key);
2445
+ if (getKey && !entry.primaryEl.getAttribute(keyAttrName2))
2446
+ entry.primaryEl.setAttribute(keyAttrName2, key);
2440
2447
  markStranded();
2441
2448
  return entry;
2442
2449
  };
@@ -2458,7 +2465,7 @@ function mapArrayLazy(accessor, container, getKey, plan, markerId, bfId) {
2458
2465
  const shared = Math.min(doms.length, items.length);
2459
2466
  for (let i2 = 0;i2 < shared; i2++) {
2460
2467
  const el = doms[i2];
2461
- const ssrKey = el.getAttribute(BF_KEY);
2468
+ const ssrKey = el.getAttribute(keyAttrName2);
2462
2469
  const key = ssrKey !== null ? ssrKey : getKey ? getKey(items[i2], i2) : String(i2);
2463
2470
  const entry = { key, primaryEl: el, item: items[i2], refs: null, last: null };
2464
2471
  entries.set(key, entry);
@@ -3405,6 +3412,7 @@ export {
3405
3412
  onMount,
3406
3413
  onCleanup,
3407
3414
  mountRowRoot,
3415
+ markupOrEmpty,
3408
3416
  mapArrayLazy,
3409
3417
  mapArrayAnchored,
3410
3418
  mapArray,
@@ -159,6 +159,11 @@ export interface LazyRowPlan<T> {
159
159
  * @param plan - Compiler-emitted row plan (see {@link LazyRowPlan})
160
160
  * @param markerId - Scoped loop marker id (`<!--bf-loop:<id>-->`), see #1087
161
161
  * @param bfId - Profiler attribution id for the reconciler effect
162
+ * @param keyAttrName - The row-key attribute NAME this loop resolved at
163
+ * compile time (`IRElement.keyAttr`/`keyAttrName(loop.depth)`,
164
+ * jsx-to-ir.ts). Defaults to `BF_KEY` (plain `'data-key'`),
165
+ * correct at the outermost loop; only a nested loop's
166
+ * compiled call passes a depth-suffixed name (#2753).
162
167
  */
163
- export declare function mapArrayLazy<T>(accessor: () => T[], container: HTMLElement | null, getKey: ((item: T, index: number) => string) | null, plan: LazyRowPlan<T>, markerId?: string, bfId?: string): void;
168
+ export declare function mapArrayLazy<T>(accessor: () => T[], container: HTMLElement | null, getKey: ((item: T, index: number) => string) | null, plan: LazyRowPlan<T>, markerId?: string, bfId?: string, keyAttrName?: string): void;
164
169
  //# sourceMappingURL=map-array-lazy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"map-array-lazy.d.ts","sourceRoot":"","sources":["../../src/runtime/map-array-lazy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkHG;AAMH;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,WAAW,CAAA;IACtB,IAAI,EAAE,CAAC,CAAA;IACP,iFAAiF;IACjF,IAAI,EAAE,OAAO,GAAG,IAAI,CAAA;IACpB,qDAAqD;IACrD,IAAI,EAAE,OAAO,GAAG,IAAI,CAAA;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B;gFAC4E;IAC5E,SAAS,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAAA;IAC7D;;;4EAGwE;IACxE,SAAS,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAA;IACpD;;;;;;;qEAOiE;IACjE,UAAU,CAAC,CAAC,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAAA;CAC1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAC5B,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,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,EACpB,QAAQ,CAAC,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,MAAM,GACZ,IAAI,CAqSN"}
1
+ {"version":3,"file":"map-array-lazy.d.ts","sourceRoot":"","sources":["../../src/runtime/map-array-lazy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkHG;AAMH;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,WAAW,CAAA;IACtB,IAAI,EAAE,CAAC,CAAA;IACP,iFAAiF;IACjF,IAAI,EAAE,OAAO,GAAG,IAAI,CAAA;IACpB,qDAAqD;IACrD,IAAI,EAAE,OAAO,GAAG,IAAI,CAAA;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B;gFAC4E;IAC5E,SAAS,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAAA;IAC7D;;;4EAGwE;IACxE,SAAS,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAA;IACpD;;;;;;;qEAOiE;IACjE,UAAU,CAAC,CAAC,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAAA;CAC1E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAC5B,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,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,EACpB,QAAQ,CAAC,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,MAAM,EACb,WAAW,GAAE,MAAe,GAC3B,IAAI,CA0SN"}
@@ -62,8 +62,18 @@ export declare function longestIncreasingSubsequenceIndices(arr: number[]): numb
62
62
  * Receives item as signal accessor: item() returns current value.
63
63
  * When `existing` is passed, initializes the SSR-rendered element and returns it.
64
64
  * When `existing` is undefined, creates a new element and returns it.
65
+ * @param keyAttrName - The row-key attribute NAME this loop resolved at
66
+ * compile time (`IRElement.keyAttr`/`keyAttrName(loop.depth)`,
67
+ * jsx-to-ir.ts) — `'data-key'` at the outermost loop,
68
+ * `'data-key-N'` N levels deep. Defaults to `BF_KEY`
69
+ * (plain `'data-key'`), correct for every depth-0 call
70
+ * site; only a nested loop's compiled call passes a
71
+ * depth-suffixed name (#2753 Shape B — see the "Why not
72
+ * stamp unconditionally" note below for why this
73
+ * replaced a hardcoded constant instead of just fixing
74
+ * the value).
65
75
  */
66
- 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, bfId?: string): void;
76
+ 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, bfId?: string, keyAttrName?: string): void;
67
77
  /**
68
78
  * Per-item scoped list rendering for whole-item conditionals (#1665).
69
79
  *
@@ -1 +1 @@
1
- {"version":3,"file":"map-array.d.ts","sourceRoot":"","sources":["../../src/runtime/map-array.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAkEH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,WAAW,EACtB,QAAQ,CAAC,EAAE,MAAM,GAChB;IAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,CA+BhD;AAyHD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mCAAmC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CA2B3E;AA4GD;;;;;;;;;;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,EACjB,IAAI,CAAC,EAAE,MAAM,GACZ,IAAI,CAuSN;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,EACjB,IAAI,CAAC,EAAE,MAAM,GACZ,IAAI,CA+FN"}
1
+ {"version":3,"file":"map-array.d.ts","sourceRoot":"","sources":["../../src/runtime/map-array.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAkEH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,WAAW,EACtB,QAAQ,CAAC,EAAE,MAAM,GAChB;IAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,CA+BhD;AAyHD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mCAAmC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CA2B3E;AA4GD;;;;;;;;;;;;;;;;;;;;GAoBG;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,EACjB,IAAI,CAAC,EAAE,MAAM,EACb,WAAW,GAAE,MAAe,GAC3B,IAAI,CAsUN;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,EACjB,IAAI,CAAC,EAAE,MAAM,GACZ,IAAI,CA+FN"}
@@ -1774,25 +1774,25 @@ function mountRowRoot(el) {
1774
1774
  }
1775
1775
  return el;
1776
1776
  }
1777
- function createComponent(nameOrDef, props = {}, key, slot, mountAt) {
1778
- const element = materializeComponent(nameOrDef, props, key, slot, mountAt);
1777
+ function createComponent(nameOrDef, props = {}, key, slot, mountAt, keyAttrName2 = BF_KEY) {
1778
+ const element = materializeComponent(nameOrDef, props, key, slot, mountAt, keyAttrName2);
1779
1779
  if (mountAt && mountAt.parentNode && element !== mountAt) {
1780
1780
  mountAt.replaceWith(element);
1781
1781
  }
1782
1782
  return element;
1783
1783
  }
1784
- function materializeComponent(nameOrDef, props = {}, key, slot, mountAt) {
1784
+ function materializeComponent(nameOrDef, props = {}, key, slot, mountAt, keyAttrName2 = BF_KEY) {
1785
1785
  if (props == null)
1786
1786
  props = {};
1787
1787
  const rowMount = mountAt ? null : takeRowMountPoint();
1788
1788
  if (typeof nameOrDef !== "string") {
1789
- return createComponentFromDef(nameOrDef, props, key, mountAt, rowMount);
1789
+ return createComponentFromDef(nameOrDef, props, key, mountAt, rowMount, keyAttrName2);
1790
1790
  }
1791
1791
  const name = nameOrDef;
1792
1792
  const templateFn = getTemplate(name);
1793
1793
  if (!templateFn) {
1794
1794
  console.warn(`[BarefootJS] Template not found for component: ${name}`);
1795
- return createPlaceholder(name, key);
1795
+ return createPlaceholder(name, key, keyAttrName2);
1796
1796
  }
1797
1797
  const childrenDescriptor = Object.getOwnPropertyDescriptor(props, "children");
1798
1798
  const childrenIsGetter = childrenDescriptor != null && typeof childrenDescriptor.get === "function";
@@ -1825,6 +1825,8 @@ function materializeComponent(nameOrDef, props = {}, key, slot, mountAt) {
1825
1825
  _parentScopeId = scopeId;
1826
1826
  } else if (slot?.parent) {
1827
1827
  _parentScopeId = slot.parent;
1828
+ } else if (!_parentScopeId) {
1829
+ _parentScopeId = `${def?.name ?? name}_${generateId()}`;
1828
1830
  }
1829
1831
  let html;
1830
1832
  try {
@@ -1837,11 +1839,11 @@ function materializeComponent(nameOrDef, props = {}, key, slot, mountAt) {
1837
1839
  const element = isFragmentRoot ? roots.find((node) => node.nodeType === Node.ELEMENT_NODE) : roots[0];
1838
1840
  if (isFragmentRoot && roots.length > 0 && !element) {
1839
1841
  console.warn(`[BarefootJS] Fragment-root component ${name} rendered no element root; ` + "a scope needs at least one element to attach to. Wrap the content in an element.");
1840
- return createPlaceholder(name, key);
1842
+ return createPlaceholder(name, key, keyAttrName2);
1841
1843
  }
1842
1844
  if (!element) {
1843
1845
  console.warn(`[BarefootJS] Template returned empty HTML for component: ${name}`);
1844
- return createPlaceholder(name, key);
1846
+ return createPlaceholder(name, key, keyAttrName2);
1845
1847
  }
1846
1848
  if (scopeId && !isFragmentRoot) {
1847
1849
  element.setAttribute(BF_SCOPE, scopeId);
@@ -1852,7 +1854,7 @@ function materializeComponent(nameOrDef, props = {}, key, slot, mountAt) {
1852
1854
  element.setAttribute(BF_AT, slot.mount);
1853
1855
  }
1854
1856
  if (key !== undefined) {
1855
- element.setAttribute(BF_KEY, String(key));
1857
+ element.setAttribute(keyAttrName2, String(key));
1856
1858
  }
1857
1859
  const fragmentComments = isFragmentRoot && scopeId ? {
1858
1860
  start: document.createComment(`${BF_SCOPE_COMMENT_PREFIX}${scopeId}`),
@@ -1968,11 +1970,11 @@ var ROOT_HAS_BFS_PATTERN = /^<\w+[^>]*\sbf-s="/;
1968
1970
  function generateId() {
1969
1971
  return Math.random().toString(36).slice(2, 8);
1970
1972
  }
1971
- function createPlaceholder(name, key) {
1973
+ function createPlaceholder(name, key, keyAttrName2 = BF_KEY) {
1972
1974
  const el = document.createElement("div");
1973
1975
  el.setAttribute(BF_SCOPE, `${name}_placeholder`);
1974
1976
  if (key !== undefined) {
1975
- el.setAttribute(BF_KEY, String(key));
1977
+ el.setAttribute(keyAttrName2, String(key));
1976
1978
  }
1977
1979
  el.textContent = `[${name}]`;
1978
1980
  el.style.cssText = "color: red; border: 1px dashed red; padding: 4px;";
@@ -2021,6 +2023,9 @@ function escapeTextOrMarkup(value) {
2021
2023
  return value[BF_MARKUP_BRAND];
2022
2024
  return escapeText(value);
2023
2025
  }
2026
+ function markupOrEmpty(value) {
2027
+ return value == null ? "" : value;
2028
+ }
2024
2029
  function escapeTextOrNode(value) {
2025
2030
  if (isBfMarkup(value))
2026
2031
  return value[BF_MARKUP_BRAND];
@@ -2082,7 +2087,7 @@ function insertGetterChildren(element, children) {
2082
2087
  element.appendChild(document.createTextNode(String(children)));
2083
2088
  }
2084
2089
  }
2085
- function createComponentFromDef(def, props, key, mountAt, rowMount) {
2090
+ function createComponentFromDef(def, props, key, mountAt, rowMount, keyAttrName2 = BF_KEY) {
2086
2091
  if (!def.template) {
2087
2092
  throw new Error("[BarefootJS] createComponent with ComponentDef requires a template function");
2088
2093
  }
@@ -2099,7 +2104,7 @@ function createComponentFromDef(def, props, key, mountAt, rowMount) {
2099
2104
  const scopeId = `${name}_${generateId()}`;
2100
2105
  element.setAttribute(BF_SCOPE, scopeId);
2101
2106
  if (key !== undefined) {
2102
- element.setAttribute(BF_KEY, String(key));
2107
+ element.setAttribute(keyAttrName2, String(key));
2103
2108
  }
2104
2109
  if (mountAt) {
2105
2110
  mountAt.replaceWith(element);
@@ -2480,7 +2485,7 @@ function createItemScope(item, index, renderItem, existingPrimary, existingExtra
2480
2485
  }
2481
2486
  return { startMarker, primaryEl, extras, scopeComments, dispose, setItem };
2482
2487
  }
2483
- function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
2488
+ function mapArray(accessor, container, getKey, renderItem, markerId, bfId, keyAttrName2 = BF_KEY) {
2484
2489
  if (!container)
2485
2490
  return;
2486
2491
  const scopes = new Map;
@@ -2505,13 +2510,15 @@ function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
2505
2510
  if (!hydrated) {
2506
2511
  hydrated = true;
2507
2512
  const existingRanges = startMarker ? findItemRanges(startMarker, endMarker) : Array.from(container.children).map((el) => ({ startMarker: null, primaryEl: el, extras: [], scopeComments: null }));
2508
- const needsHydration = existingRanges.length > 0 && (!existingRanges[0]?.primaryEl.hasAttribute("data-key") || scopes.size === 0);
2513
+ const needsHydration = existingRanges.length > 0;
2509
2514
  if (needsHydration) {
2510
2515
  for (let i2 = 0;i2 < existingRanges.length && i2 < items.length; i2++) {
2511
2516
  const range = existingRanges[i2];
2512
2517
  const item = items[i2];
2513
2518
  const key = getKey ? getKey(item, i2) : String(i2);
2514
- range.primaryEl.setAttribute(BF_KEY, key);
2519
+ if (getKey && !range.primaryEl.getAttribute(keyAttrName2)) {
2520
+ range.primaryEl.setAttribute(keyAttrName2, key);
2521
+ }
2515
2522
  const scope = createItemScope(item, i2, renderItem, range.primaryEl, range.extras, range.startMarker, range.scopeComments);
2516
2523
  scopes.set(key, scope);
2517
2524
  hydratedScopes.add(range.primaryEl);
@@ -2520,8 +2527,8 @@ function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
2520
2527
  const item = items[i2];
2521
2528
  const key = getKey ? getKey(item, i2) : String(i2);
2522
2529
  const scope = createItemScope(item, i2, renderItem, undefined, undefined, undefined, undefined, { container, anchor });
2523
- if (!scope.primaryEl.dataset.key)
2524
- scope.primaryEl.setAttribute(BF_KEY, key);
2530
+ if (getKey && !scope.primaryEl.getAttribute(keyAttrName2))
2531
+ scope.primaryEl.setAttribute(keyAttrName2, key);
2525
2532
  scopes.set(key, scope);
2526
2533
  insertScope(scope, container, anchor);
2527
2534
  }
@@ -2546,7 +2553,7 @@ function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
2546
2553
  if (scopes.size === 0) {
2547
2554
  const loopRanges = startMarker ? findItemRanges(startMarker, endMarker) : Array.from(container.children).map((el) => ({ startMarker: null, primaryEl: el, extras: [], scopeComments: null }));
2548
2555
  for (const range of loopRanges) {
2549
- const existingKey = range.primaryEl.dataset?.key;
2556
+ const existingKey = range.primaryEl.getAttribute(keyAttrName2);
2550
2557
  if (existingKey && !scopes.has(existingKey)) {
2551
2558
  scopes.set(existingKey, {
2552
2559
  startMarker: range.startMarker,
@@ -2604,8 +2611,8 @@ function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
2604
2611
  desiredOrder.push(existing);
2605
2612
  } else {
2606
2613
  const scope = createItemScope(item, i2, renderItem, undefined, undefined, undefined, undefined, { container, anchor });
2607
- if (!scope.primaryEl.dataset.key)
2608
- scope.primaryEl.setAttribute(BF_KEY, key);
2614
+ if (getKey && !scope.primaryEl.getAttribute(keyAttrName2))
2615
+ scope.primaryEl.setAttribute(keyAttrName2, key);
2609
2616
  scopes.set(key, scope);
2610
2617
  desiredOrder.push(scope);
2611
2618
  }
@@ -2800,7 +2807,7 @@ function mapArrayAnchored(accessor, container, getKey, renderItem, markerId, bfI
2800
2807
  }, bfId);
2801
2808
  }
2802
2809
  // src/runtime/map-array-lazy.ts
2803
- function mapArrayLazy(accessor, container, getKey, plan, markerId, bfId) {
2810
+ function mapArrayLazy(accessor, container, getKey, plan, markerId, bfId, keyAttrName2 = BF_KEY) {
2804
2811
  if (!container)
2805
2812
  return;
2806
2813
  const entries = new Map;
@@ -2839,8 +2846,8 @@ function mapArrayLazy(accessor, container, getKey, plan, markerId, bfId) {
2839
2846
  last: null
2840
2847
  };
2841
2848
  entry.primaryEl = untrack(() => plan.createRow(entry, index));
2842
- if (!entry.primaryEl.dataset.key)
2843
- entry.primaryEl.setAttribute(BF_KEY, key);
2849
+ if (getKey && !entry.primaryEl.getAttribute(keyAttrName2))
2850
+ entry.primaryEl.setAttribute(keyAttrName2, key);
2844
2851
  markStranded();
2845
2852
  return entry;
2846
2853
  };
@@ -2862,7 +2869,7 @@ function mapArrayLazy(accessor, container, getKey, plan, markerId, bfId) {
2862
2869
  const shared = Math.min(doms.length, items.length);
2863
2870
  for (let i2 = 0;i2 < shared; i2++) {
2864
2871
  const el = doms[i2];
2865
- const ssrKey = el.getAttribute(BF_KEY);
2872
+ const ssrKey = el.getAttribute(keyAttrName2);
2866
2873
  const key = ssrKey !== null ? ssrKey : getKey ? getKey(items[i2], i2) : String(i2);
2867
2874
  const entry = { key, primaryEl: el, item: items[i2], refs: null, last: null };
2868
2875
  entries.set(key, entry);
@@ -3804,6 +3811,7 @@ export {
3804
3811
  onMount,
3805
3812
  onCleanup,
3806
3813
  mountRowRoot,
3814
+ markupOrEmpty,
3807
3815
  mapArrayLazy,
3808
3816
  mapArrayAnchored,
3809
3817
  mapArray,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/client",
3
- "version": "0.33.2",
3
+ "version": "0.33.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.33.2"
58
+ "@barefootjs/shared": "0.33.4"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@barefootjs/jsx": ">=0.2.0"
@@ -178,6 +178,7 @@ export function createComponent(
178
178
  key: string | number | undefined,
179
179
  slot: CreateComponentSlotInfo | undefined,
180
180
  mountAt: Element,
181
+ keyAttrName?: string,
181
182
  ): HTMLElement
182
183
  export function createComponent(
183
184
  nameOrDef: string | ComponentDef,
@@ -185,6 +186,7 @@ export function createComponent(
185
186
  key?: string | number,
186
187
  slot?: CreateComponentSlotInfo,
187
188
  mountAt?: Element | null,
189
+ keyAttrName?: string,
188
190
  ): HTMLElement | DocumentFragment
189
191
  export function createComponent(
190
192
  nameOrDef: string | ComponentDef,
@@ -192,8 +194,9 @@ export function createComponent(
192
194
  key?: string | number,
193
195
  slot?: CreateComponentSlotInfo,
194
196
  mountAt?: Element | null,
197
+ keyAttrName: string = BF_KEY,
195
198
  ): HTMLElement | DocumentFragment {
196
- const element = materializeComponent(nameOrDef, props, key, slot, mountAt)
199
+ const element = materializeComponent(nameOrDef, props, key, slot, mountAt, keyAttrName)
197
200
  // `mountAt` is an unconditional obligation: callers used to run
198
201
  // `ph.replaceWith(comp)` themselves on every outcome, so every path that
199
202
  // did NOT consume the placeholder still owes the replacement — a missing or
@@ -224,6 +227,7 @@ function materializeComponent(
224
227
  key?: string | number,
225
228
  slot?: CreateComponentSlotInfo,
226
229
  mountAt?: Element | null,
230
+ keyAttrName: string = BF_KEY,
227
231
  ): HTMLElement | DocumentFragment {
228
232
  // A bare callable shim invoked from user code (e.g. an object-literal
229
233
  // value `LOGOS[id]()` whose arrow the compiler hoisted into a component)
@@ -235,7 +239,7 @@ function materializeComponent(
235
239
  const rowMount = mountAt ? null : takeRowMountPoint()
236
240
  // ComponentDef mode: use def directly instead of registry lookup
237
241
  if (typeof nameOrDef !== 'string') {
238
- return createComponentFromDef(nameOrDef, props, key, mountAt, rowMount)
242
+ return createComponentFromDef(nameOrDef, props, key, mountAt, rowMount, keyAttrName)
239
243
  }
240
244
 
241
245
  const name = nameOrDef
@@ -244,7 +248,7 @@ function materializeComponent(
244
248
  const templateFn = getTemplate(name)
245
249
  if (!templateFn) {
246
250
  console.warn(`[BarefootJS] Template not found for component: ${name}`)
247
- return createPlaceholder(name, key)
251
+ return createPlaceholder(name, key, keyAttrName)
248
252
  }
249
253
 
250
254
  // 2. Check for getter children.
@@ -328,11 +332,32 @@ function materializeComponent(
328
332
  // #2444's `grandchild-composition` case). A comment wrapper keeps
329
333
  // `scopeId === null` and falls through to `slot?.parent`, preserving the
330
334
  // hoisted-children placeholder resolution (#1320).
335
+ //
336
+ // Third branch (#2757): a root-is-a-child-call wrapper mounted at the TOP
337
+ // LEVEL has neither. `scopeId` is null by design (step 4 — the parsed
338
+ // firstChild is the child's own already-scoped element, so we must not
339
+ // stamp over it) and a top-level `createComponent(name, {})` is passed no
340
+ // `slot`, so pre-#2757 `_parentScopeId` stayed null for the whole template
341
+ // eval and `renderChild` fell back to naming the child after ITSELF
342
+ // (`PairwiseRow_xyz_s2` where SSR and hydration both produce
343
+ // `PairwiseCase_xyz_s2`, and with no `bf-h`/`bf-m` at all). The wrapper
344
+ // still HAS a scope identity in the SSR convention — it just has no
345
+ // element of its own to carry it — so derive one here for threading only.
346
+ // Same split #2722 made for a genuine fragment root, which keeps a
347
+ // non-null `scopeId` purely so this threading works and skips only the
348
+ // ATTRIBUTE write in step 7.
349
+ //
350
+ // Why not derive unconditionally: guarded on `!_parentScopeId` so a
351
+ // wrapper materialized while an OUTER template eval is in flight keeps
352
+ // inheriting that caller's ambient scope rather than being renamed under a
353
+ // fresh random id. Only the genuinely-rootless mount is affected.
331
354
  const prevParentScopeId = _parentScopeId
332
355
  if (scopeId) {
333
356
  _parentScopeId = scopeId
334
357
  } else if (slot?.parent) {
335
358
  _parentScopeId = slot.parent
359
+ } else if (!_parentScopeId) {
360
+ _parentScopeId = `${def?.name ?? name}_${generateId()}`
336
361
  }
337
362
  let html: string
338
363
  try {
@@ -385,12 +410,12 @@ function materializeComponent(
385
410
  `[BarefootJS] Fragment-root component ${name} rendered no element root; ` +
386
411
  'a scope needs at least one element to attach to. Wrap the content in an element.',
387
412
  )
388
- return createPlaceholder(name, key)
413
+ return createPlaceholder(name, key, keyAttrName)
389
414
  }
390
415
 
391
416
  if (!element) {
392
417
  console.warn(`[BarefootJS] Template returned empty HTML for component: ${name}`)
393
- return createPlaceholder(name, key)
418
+ return createPlaceholder(name, key, keyAttrName)
394
419
  }
395
420
 
396
421
  // 7. Set scope ID and key attributes.
@@ -408,7 +433,7 @@ function materializeComponent(
408
433
  element.setAttribute(BF_AT, slot.mount)
409
434
  }
410
435
  if (key !== undefined) {
411
- element.setAttribute(BF_KEY, String(key))
436
+ element.setAttribute(keyAttrName, String(key))
412
437
  }
413
438
 
414
439
  // 7a. Fragment-root boundary comments + registry (#2722).
@@ -722,8 +747,8 @@ export function renderChild(
722
747
  const hostSuffix = (_parentScopeId && slotSuffix) ? `|h=${_parentScopeId}|m=${slotSuffix}` : ''
723
748
  // #2732: `data-key` for a fragment-root loop row lands on the row's own
724
749
  // first element — the same "first element, not first node" convention
725
- // `IRElement.carriesDataKey` uses on the SSR side (jsx-to-ir.ts) — not
726
- // on the comment above, which carries scope identity only. This keeps
750
+ // `IRElement.keyAttr` uses on the SSR side (jsx-to-ir.ts) — not on the
751
+ // comment above, which carries scope identity only. This keeps
727
752
  // `mapArray`'s existing `primaryEl.dataset.key` read (map-array.ts)
728
753
  // working unchanged for markup this function pre-builds (the pure-CSR
729
754
  // `materializeComponent` template-string path, used when there is no
@@ -769,11 +794,11 @@ function generateId(): string {
769
794
  /**
770
795
  * Create a placeholder element when template is not found
771
796
  */
772
- function createPlaceholder(name: string, key?: string | number): HTMLElement {
797
+ function createPlaceholder(name: string, key?: string | number, keyAttrName: string = BF_KEY): HTMLElement {
773
798
  const el = document.createElement('div')
774
799
  el.setAttribute(BF_SCOPE, `${name}_placeholder`)
775
800
  if (key !== undefined) {
776
- el.setAttribute(BF_KEY, String(key))
801
+ el.setAttribute(keyAttrName, String(key))
777
802
  }
778
803
  el.textContent = `[${name}]`
779
804
  el.style.cssText = 'color: red; border: 1px dashed red; padding: 4px;'
@@ -949,6 +974,34 @@ export function escapeTextOrMarkup(value: unknown): string {
949
974
  return escapeText(value)
950
975
  }
951
976
 
977
+ /**
978
+ * Nullish guard for a bare `${children}` passthrough splice
979
+ * (`ir-to-client-js/html-template.ts`'s no-`slotId` `'expression'` branches
980
+ * — the "bare `${...}` interpolations" the docstring above `escapeTextSlotExpr`
981
+ * describes) — #2775. The value here is ALREADY-STRINGIFIED MARKUP, not an
982
+ * arbitrary prop: `materializeComponent` (this file, "Template functions
983
+ * expect children as an HTML string, not an array") joins a component's
984
+ * `children` into an HTML string before the template lambda ever runs, so by
985
+ * the time this function sees the value it is either that HTML string or,
986
+ * when the caller passed none, simply `undefined` (the key is absent from
987
+ * the props object entirely). This function's ENTIRE job is that one
988
+ * nullish case: turn the absent/`null` value into `''`, same as SSR's
989
+ * `{props.children}` and the DSL adapters' `bf.string(children)` render it.
990
+ *
991
+ * Deliberately NOT `escapeTextOrMarkup`: that function falls back to
992
+ * `escapeText` for anything not `bfMarkup()`-branded, and a `children`
993
+ * string is never branded (it is a plain string built by
994
+ * `materializeComponent`, not compiler-escaped piece-by-piece the way a
995
+ * `bfMarkup()` value is) — routing `children` through it would HTML-escape
996
+ * real markup into visible `&lt;span&gt;`-style text on every call that
997
+ * actually has children, trading the no-children bug for a strictly worse
998
+ * with-children one. So: nullish becomes `''`; every other value is
999
+ * returned completely unescaped, exactly as it arrived.
1000
+ */
1001
+ export function markupOrEmpty(value: unknown): string {
1002
+ return value == null ? '' : (value as string)
1003
+ }
1004
+
952
1005
  /**
953
1006
  * `escapeText`'s counterpart for a claimed 'markup' slot's REACTIVE write
954
1007
  * (slot unification A3 follow-up), where the value is a plain-JS expression
@@ -1073,6 +1126,7 @@ function createComponentFromDef(
1073
1126
  key?: string | number,
1074
1127
  mountAt?: Element | null,
1075
1128
  rowMount?: { container: Node; anchor: Node | null } | null,
1129
+ keyAttrName: string = BF_KEY,
1076
1130
  ): HTMLElement {
1077
1131
  if (!def.template) {
1078
1132
  throw new Error('[BarefootJS] createComponent with ComponentDef requires a template function')
@@ -1097,7 +1151,7 @@ function createComponentFromDef(
1097
1151
  const scopeId = `${name}_${generateId()}`
1098
1152
  element.setAttribute(BF_SCOPE, scopeId)
1099
1153
  if (key !== undefined) {
1100
- element.setAttribute(BF_KEY, String(key))
1154
+ element.setAttribute(keyAttrName, String(key))
1101
1155
  }
1102
1156
 
1103
1157
  // Connect before init, for the same reason the registry path does (see
@@ -112,6 +112,7 @@ export {
112
112
  bfMarkup,
113
113
  isBfMarkup,
114
114
  escapeTextOrMarkup,
115
+ markupOrEmpty,
115
116
  type BfMarkup,
116
117
  } from './component.ts'
117
118
 
@@ -166,6 +166,11 @@ export interface LazyRowPlan<T> {
166
166
  * @param plan - Compiler-emitted row plan (see {@link LazyRowPlan})
167
167
  * @param markerId - Scoped loop marker id (`<!--bf-loop:<id>-->`), see #1087
168
168
  * @param bfId - Profiler attribution id for the reconciler effect
169
+ * @param keyAttrName - The row-key attribute NAME this loop resolved at
170
+ * compile time (`IRElement.keyAttr`/`keyAttrName(loop.depth)`,
171
+ * jsx-to-ir.ts). Defaults to `BF_KEY` (plain `'data-key'`),
172
+ * correct at the outermost loop; only a nested loop's
173
+ * compiled call passes a depth-suffixed name (#2753).
169
174
  */
170
175
  export function mapArrayLazy<T>(
171
176
  accessor: () => T[],
@@ -174,6 +179,7 @@ export function mapArrayLazy<T>(
174
179
  plan: LazyRowPlan<T>,
175
180
  markerId?: string,
176
181
  bfId?: string,
182
+ keyAttrName: string = BF_KEY,
177
183
  ): void {
178
184
  if (!container) return
179
185
 
@@ -249,8 +255,11 @@ export function mapArrayLazy<T>(
249
255
  * so it is wrapped in `untrack()`: it writes outer-involving bindings
250
256
  * with current values (contract), and those signal reads must not
251
257
  * subscribe the reconciler. The returned element is assigned to
252
- * `entry.primaryEl`; `data-key` is stamped if `createRow` didn't
253
- * (mirrors `mapArray`'s create path).
258
+ * `entry.primaryEl`; the row's key attribute is stamped if `createRow`
259
+ * didn't (mirrors `mapArray`'s create path) — only for a KEYED loop
260
+ * (#2753 Shape A: an unkeyed loop's rows carry no key attribute at all),
261
+ * using `keyAttrName` (Shape B: a nested loop's name is depth-suffixed,
262
+ * never the plain default `BF_KEY`).
254
263
  */
255
264
  const createEntry = (item: T, index: number, key: string): LazyRowEntry<T> => {
256
265
  const entry: LazyRowEntry<T> = {
@@ -263,7 +272,7 @@ export function mapArrayLazy<T>(
263
272
  last: null,
264
273
  }
265
274
  entry.primaryEl = untrack(() => plan.createRow(entry, index))
266
- if (!entry.primaryEl.dataset.key) entry.primaryEl.setAttribute(BF_KEY, key)
275
+ if (getKey && !entry.primaryEl.getAttribute(keyAttrName)) entry.primaryEl.setAttribute(keyAttrName, key)
267
276
  markStranded()
268
277
  return entry
269
278
  }
@@ -294,7 +303,9 @@ export function mapArrayLazy<T>(
294
303
  const el = doms[i]
295
304
  // READ the SSR-rendered key (never write it on adopted rows);
296
305
  // positional item pairing is sound by the §9.3(2) eligibility gate.
297
- const ssrKey = el.getAttribute(BF_KEY)
306
+ // `keyAttrName`, not the plain `BF_KEY` default — a nested loop's
307
+ // rows carry a depth-suffixed name (#2753 Shape B).
308
+ const ssrKey = el.getAttribute(keyAttrName)
298
309
  const key = ssrKey !== null ? ssrKey : getKey ? getKey(items[i], i) : String(i)
299
310
  const entry: LazyRowEntry<T> = { key, primaryEl: el, item: items[i], refs: null, last: null }
300
311
  entries.set(key, entry)
@@ -412,6 +412,16 @@ function createItemScope<T>(
412
412
  * Receives item as signal accessor: item() returns current value.
413
413
  * When `existing` is passed, initializes the SSR-rendered element and returns it.
414
414
  * When `existing` is undefined, creates a new element and returns it.
415
+ * @param keyAttrName - The row-key attribute NAME this loop resolved at
416
+ * compile time (`IRElement.keyAttr`/`keyAttrName(loop.depth)`,
417
+ * jsx-to-ir.ts) — `'data-key'` at the outermost loop,
418
+ * `'data-key-N'` N levels deep. Defaults to `BF_KEY`
419
+ * (plain `'data-key'`), correct for every depth-0 call
420
+ * site; only a nested loop's compiled call passes a
421
+ * depth-suffixed name (#2753 Shape B — see the "Why not
422
+ * stamp unconditionally" note below for why this
423
+ * replaced a hardcoded constant instead of just fixing
424
+ * the value).
415
425
  */
416
426
  export function mapArray<T>(
417
427
  accessor: () => T[],
@@ -420,6 +430,7 @@ export function mapArray<T>(
420
430
  renderItem: (item: () => T, index: number, existing?: HTMLElement) => HTMLElement,
421
431
  markerId?: string,
422
432
  bfId?: string,
433
+ keyAttrName: string = BF_KEY,
423
434
  ): void {
424
435
  if (!container) return
425
436
 
@@ -460,18 +471,46 @@ export function mapArray<T>(
460
471
  (el) => ({ startMarker: null, primaryEl: el as HTMLElement, extras: [] as HTMLElement[], scopeComments: null as ScopeCommentPair | null }),
461
472
  )
462
473
 
463
- // SSR elements need initialization when they haven't been adopted into scopes yet.
464
- // Check both: elements without data-key (legacy) OR elements with data-key but no scopes
465
- // (component loops render data-key in SSR template but haven't been hydrated).
474
+ // #2753 why not read the DOM for a hydration signal: this branch
475
+ // runs at most ONCE per `mapArray` call (guarded by the closure-local
476
+ // `hydrated` flag, set true on the line above, before anything else
477
+ // can run), and `scopes` is a closure-local Map that is ALWAYS empty
478
+ // the first time this line runs — nothing sets it earlier. So "has
479
+ // this container already been adopted" is already fully answered by
480
+ // `existingRanges.length > 0` (there is DOM here from SSR/a prior
481
+ // render) with no attribute read at all. The attribute the old check
482
+ // read (`hasAttribute('data-key')`) was never a sound signal anyway:
483
+ // an unkeyed loop's rows legitimately carry no key attribute at all
484
+ // (Shape A), and a nested loop's rows carry a depth-suffixed name
485
+ // (`data-key-N`, Shape B) — so the plain-name probe either
486
+ // misdiagnosed a correctly-unkeyed row as "not yet hydrated", or
487
+ // missed a correctly-keyed nested row entirely. `scopes.size === 0`
488
+ // is not a second, independent signal here — it is ALWAYS true at
489
+ // this line, so the two-part `||` reduced to the first probe being
490
+ // moot: this is a behavior-preserving simplification, not a new rule.
466
491
  const needsHydration = existingRanges.length > 0
467
- && (!existingRanges[0]?.primaryEl.hasAttribute('data-key') || scopes.size === 0)
468
492
  if (needsHydration) {
469
493
  // Hydrate in place: tag keys, create per-item scopes with renderItem(existing)
470
494
  for (let i = 0; i < existingRanges.length && i < items.length; i++) {
471
495
  const range = existingRanges[i]
472
496
  const item = items[i]
473
497
  const key = getKey ? getKey(item, i) : String(i)
474
- range.primaryEl.setAttribute(BF_KEY, key)
498
+ // Only a KEYED loop gets a key attribute at all (Shape A) — SSR
499
+ // (or the static template this row cloned from) already carries
500
+ // the correct, depth-aware name (Shape B) when it applies, so this
501
+ // is a defensive backfill for a row that arrived without one, not
502
+ // the primary writer. A FALSY-value check (`getAttribute`, not
503
+ // `hasAttribute`) — not just presence — because a compiled
504
+ // template can bake the attribute PRESENT but empty for a shape
505
+ // whose key expression isn't ready at clone time (measured on the
506
+ // csr-mount leg of a keyed top-level loop); `hasAttribute` would
507
+ // wrongly treat that empty placeholder as "already correct" and
508
+ // skip the backfill this line exists to make. Reads the SAME name
509
+ // being written (not `dataset.key`, which only ever reads the
510
+ // plain, undepth-suffixed name).
511
+ if (getKey && !range.primaryEl.getAttribute(keyAttrName)) {
512
+ range.primaryEl.setAttribute(keyAttrName, key)
513
+ }
475
514
 
476
515
  const scope = createItemScope(
477
516
  item,
@@ -493,7 +532,7 @@ export function mapArray<T>(
493
532
  // Final position is known here (append before the loop's trailing
494
533
  // anchor), so the row can be mounted at it before its init runs.
495
534
  const scope = createItemScope(item, i, renderItem, undefined, undefined, undefined, undefined, { container, anchor })
496
- if (!scope.primaryEl.dataset.key) scope.primaryEl.setAttribute(BF_KEY, key)
535
+ if (getKey && !scope.primaryEl.getAttribute(keyAttrName)) scope.primaryEl.setAttribute(keyAttrName, key)
497
536
  scopes.set(key, scope)
498
537
  insertScope(scope, container, anchor)
499
538
  }
@@ -522,7 +561,10 @@ export function mapArray<T>(
522
561
  (el) => ({ startMarker: null, primaryEl: el as HTMLElement, extras: [] as HTMLElement[], scopeComments: null as ScopeCommentPair | null }),
523
562
  )
524
563
  for (const range of loopRanges) {
525
- const existingKey = range.primaryEl.dataset?.key
564
+ // `getAttribute(keyAttrName)`, not `dataset.key` — `dataset` only
565
+ // ever reads the plain, undepth-suffixed `data-key`, which misses a
566
+ // nested loop's `data-key-N` rows entirely (#2753 Shape B).
567
+ const existingKey = range.primaryEl.getAttribute(keyAttrName)
526
568
  if (existingKey && !scopes.has(existingKey)) {
527
569
  scopes.set(existingKey, {
528
570
  startMarker: range.startMarker,
@@ -612,7 +654,7 @@ export function mapArray<T>(
612
654
  // to its final position (it participates in the walk like any other
613
655
  // attached scope, so the resulting order is unchanged).
614
656
  const scope = createItemScope(item, i, renderItem, undefined, undefined, undefined, undefined, { container, anchor })
615
- if (!scope.primaryEl.dataset.key) scope.primaryEl.setAttribute(BF_KEY, key)
657
+ if (getKey && !scope.primaryEl.getAttribute(keyAttrName)) scope.primaryEl.setAttribute(keyAttrName, key)
616
658
  scopes.set(key, scope)
617
659
  desiredOrder.push(scope)
618
660
  }