@barefootjs/cli 0.26.3 → 0.26.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -52,6 +52,21 @@ return <div>...</div>
52
52
  ))}
53
53
  ```
54
54
 
55
+ `.flatMap()` expands nested collections into a flat run of keyed elements. A pure projection body — the nested `.map()` as the whole body, expression or single-`return` block — compiles on every adapter; a body with statements before the projection (early returns, `const`s) runs as JS on JS-runtime adapters and needs [`/* @client */`](./client-directive.md) on DSL backends:
56
+
57
+ ```tsx
58
+ // ✅ Projection — works on every adapter
59
+ {todos().flatMap(todo => todo.tags.map(tag => (
60
+ <li key={`${todo.id}:${tag}`}>{tag}</li>
61
+ )))}
62
+
63
+ // ✅ Statement body — JS-runtime adapters; /* @client */ on Go/Mojo etc.
64
+ {todos().flatMap(todo => {
65
+ if (todo.hidden) return []
66
+ return todo.tags.map(tag => <li key={`${todo.id}:${tag}`}>{tag}</li>)
67
+ })}
68
+ ```
69
+
55
70
  `.sort()` and `.toSorted()` can be chained with `.map()` and `.filter()`:
56
71
 
57
72
  ```tsx
@@ -105,7 +120,9 @@ Some JavaScript expressions cannot be translated into marked template syntax. Wh
105
120
  |---|---|---|
106
121
  | `.filter()` with destructured param (`({done}) => done`) | works (runs as JS) | **BF101** |
107
122
  | `.filter()` with `function` keyword callback | works | **BF101** |
108
- | `.reduce()`, `.forEach()`, `.flatMap()` | works | **BF101** |
123
+ | `.reduce()`, `.forEach()`, value-returning `.flatMap()` with an off-catalogue projection | works | **BF101** |
124
+ | JSX-returning `.flatMap()` projection (`items.flatMap(it => it.tags.map(tag => <li key={...}/>))`) | works | works |
125
+ | JSX-returning `.flatMap()` with a statement body (early `return`, `const` before the projection) | works (runs as JS) | **BF021** |
109
126
  | Nested `.filter()` / `.map()` in a filter predicate (`x => x.tags.filter(...).length > 0`) | works | works |
110
127
  | Nested `.some()` / `.find()` / `.reduce()` in a filter predicate | works | **BF101** |
111
128
  | Sort comparator that's a multi-statement block body or `localeCompare(b, locale, opts)` | works (runs as JS) | **BF021** |
package/dist/index.js CHANGED
@@ -3695,7 +3695,7 @@ function templateAttrExpr(attrName, valExpr, presenceOrUndefined) {
3695
3695
  return `\${((v) => v != null ? 'style="' + ${escapeAttrValueExpr("v")} + '"' : '')(styleToCss(${valExpr}))}`;
3696
3696
  }
3697
3697
  if (attrName === "data-key" || attrName.startsWith("data-key-")) {
3698
- return `${attrName}="\${${valExpr}}"`;
3698
+ return `${attrName}="\${${escapeAttrValueExpr(valExpr)}}"`;
3699
3699
  }
3700
3700
  return `\${(${valExpr}) != null ? '${attrName}="' + ${escapeAttrValueExpr(valExpr)} + '"' : ''}`;
3701
3701
  }
@@ -3815,12 +3815,54 @@ function renderPreamble(preamble, opts) {
3815
3815
  if (seg.kind === "js") {
3816
3816
  const text = opts.textVariant === "template" ? seg.templateText ?? seg.text : seg.text;
3817
3817
  out += opts.transformJs ? opts.transformJs(text) : text;
3818
+ } else if (opts.rawLeaf) {
3819
+ out += opts.renderLeaf(escapeLeafTextExpressions(seg.ir));
3818
3820
  } else {
3819
3821
  out += "`" + opts.renderLeaf(escapeLeafTextExpressions(seg.ir)) + "`";
3820
3822
  }
3821
3823
  }
3822
3824
  return out;
3823
3825
  }
3826
+ function flatMapLeafKeyExpr(ir) {
3827
+ if (ir.type !== "element") return null;
3828
+ const keyAttr = ir.attrs.find((a) => a.name === "key");
3829
+ if (!keyAttr) return null;
3830
+ switch (keyAttr.value.kind) {
3831
+ case "expression":
3832
+ return `(${keyAttr.value.expr})`;
3833
+ case "literal":
3834
+ return JSON.stringify(keyAttr.value.value);
3835
+ case "template":
3836
+ return attrValueToString(keyAttr.value);
3837
+ default:
3838
+ return null;
3839
+ }
3840
+ }
3841
+ function stripLeafKeyAttr(ir) {
3842
+ if (ir.type !== "element") return ir;
3843
+ return { ...ir, attrs: ir.attrs.filter((a) => a.name !== "key") };
3844
+ }
3845
+ function renderFlatMapClientBody(cb, restSpreadNames) {
3846
+ return renderPreamble(cb, {
3847
+ textVariant: "client",
3848
+ rawLeaf: true,
3849
+ renderLeaf: (ir) => {
3850
+ const key = flatMapLeafKeyExpr(ir);
3851
+ const html = irToHtmlTemplate(stripLeafKeyAttr(ir), restSpreadNames, 1, void 0, void 0, true);
3852
+ return `({ k: ${key ?? "undefined"}, h: \`${html}\` })`;
3853
+ }
3854
+ });
3855
+ }
3856
+ function flatMapCallbackHasKeyedLeaf(cb) {
3857
+ return cb.segments.some((s) => s.kind === "jsx" && flatMapLeafKeyExpr(s.ir) !== null);
3858
+ }
3859
+ function renderFlatMapProjectionClientBody(inner, restSpreadNames) {
3860
+ const chained = applyLoopChain(inner);
3861
+ const params = inner.index ? `(${inner.param}, ${inner.index})` : `(${inner.param})`;
3862
+ const key = inner.key ? `(${inner.key})` : "undefined";
3863
+ const html = inner.children.map((c) => irToHtmlTemplate(escapeLeafTextExpressions(c), restSpreadNames, 1, void 0, void 0, true)).join("");
3864
+ return `${chained}.map(${params} => ({ k: ${key}, h: \`${html}\` }))`;
3865
+ }
3824
3866
  function escapeLeafTextExpressions(ir) {
3825
3867
  switch (ir.type) {
3826
3868
  case "element":
@@ -3949,7 +3991,7 @@ function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, bran
3949
3991
  let mapExpr;
3950
3992
  if (node.flatMapCallback) {
3951
3993
  const body2 = renderPreamble(node.flatMapCallback, {
3952
- renderLeaf: (ir) => irToHtmlTemplate(ir, restSpreadNames, loopDepth + 1, loopParams, branchSlotsVar, insideLoop)
3994
+ renderLeaf: (ir) => irToHtmlTemplate(stripLeafKeyAttr(ir), restSpreadNames, loopDepth + 1, loopParams, branchSlotsVar, insideLoop)
3953
3995
  });
3954
3996
  mapExpr = `\${${wrappedArray}.flatMap(${node.flatMapCallback.params} => ${body2}).join('')}`;
3955
3997
  } else if (node.preamble) {
@@ -4200,7 +4242,8 @@ function irToPlaceholderTemplate(node, restSpreadNames, loopDepth = 0, loopParam
4200
4242
  let mapExpr;
4201
4243
  if (node.flatMapCallback) {
4202
4244
  const body2 = renderPreamble(node.flatMapCallback, {
4203
- renderLeaf: (ir) => irToPlaceholderTemplate(ir, restSpreadNames, loopDepth + 1, loopParams)
4245
+ // Leaf `key` stripped see the irToHtmlTemplate site above.
4246
+ renderLeaf: (ir) => irToPlaceholderTemplate(stripLeafKeyAttr(ir), restSpreadNames, loopDepth + 1, loopParams)
4204
4247
  });
4205
4248
  mapExpr = `\${${wrappedArray}.flatMap(${node.flatMapCallback.params} => ${body2}).join('')}`;
4206
4249
  } else if (node.preamble) {
@@ -4799,7 +4842,8 @@ function generateCsrTemplateWithOpts(node, opts) {
4799
4842
  const body2 = renderPreamble(node.flatMapCallback, {
4800
4843
  textVariant: "template",
4801
4844
  transformJs: (t) => applyPropsRewrite(t, propsObjectName ?? null),
4802
- renderLeaf: (ir) => recurseInLoopBody(ir)
4845
+ // Leaf `key` stripped — see the irToHtmlTemplate site above.
4846
+ renderLeaf: (ir) => recurseInLoopBody(stripLeafKeyAttr(ir))
4803
4847
  });
4804
4848
  mapExpr = `\${${iterArrayExpr}.flatMap(${node.flatMapCallback.params} => ${body2}).join('')}`;
4805
4849
  } else if (node.preamble) {
@@ -11596,6 +11640,76 @@ function checkLoopKey(callback, ctx2, isNested) {
11596
11640
  return;
11597
11641
  }
11598
11642
  }
11643
+ function flatMapProjectionCall(body2) {
11644
+ let expr;
11645
+ if (ts11.isBlock(body2)) {
11646
+ const real = body2.statements;
11647
+ if (real.length !== 1 || !ts11.isReturnStatement(real[0]) || !real[0].expression) return null;
11648
+ expr = real[0].expression;
11649
+ } else {
11650
+ expr = body2;
11651
+ }
11652
+ while (ts11.isParenthesizedExpression(expr)) expr = expr.expression;
11653
+ if (!ts11.isCallExpression(expr)) return null;
11654
+ if (!getMapLikeMethod(expr)) return null;
11655
+ const cb = expr.arguments[0];
11656
+ if (!cb || !ts11.isArrowFunction(cb) && !ts11.isFunctionExpression(cb)) return null;
11657
+ for (const p of cb.parameters) {
11658
+ if (!ts11.isIdentifier(p.name)) return null;
11659
+ }
11660
+ let innerBody = cb.body;
11661
+ if (ts11.isBlock(innerBody)) {
11662
+ const ret = innerBody.statements.find(
11663
+ (s) => ts11.isReturnStatement(s) && s.expression != null
11664
+ );
11665
+ if (innerBody.statements.length !== 1 || !ret?.expression) return null;
11666
+ innerBody = ret.expression;
11667
+ }
11668
+ while (ts11.isParenthesizedExpression(innerBody)) innerBody = innerBody.expression;
11669
+ const isElementish = (n) => {
11670
+ let m = n;
11671
+ while (ts11.isParenthesizedExpression(m)) m = m.expression;
11672
+ if (ts11.isJsxElement(m) || ts11.isJsxSelfClosingElement(m)) return leafIsWirelessElement(m);
11673
+ if (ts11.isConditionalExpression(m)) return isElementish(m.whenTrue) && isElementish(m.whenFalse);
11674
+ return false;
11675
+ };
11676
+ if (!isElementish(innerBody)) return null;
11677
+ return expr;
11678
+ }
11679
+ function leafIsWirelessElement(el) {
11680
+ let ok = true;
11681
+ const visit3 = (n) => {
11682
+ if (!ok) return;
11683
+ if (ts11.isJsxOpeningElement(n) || ts11.isJsxSelfClosingElement(n)) {
11684
+ const tagNode = n.tagName;
11685
+ const isIntrinsic = ts11.isIdentifier(tagNode) ? !/^[A-Z]/.test(tagNode.text) : ts11.isJsxNamespacedName(tagNode);
11686
+ if (!isIntrinsic) {
11687
+ ok = false;
11688
+ return;
11689
+ }
11690
+ for (const attr of n.attributes.properties) {
11691
+ if (ts11.isJsxSpreadAttribute(attr)) {
11692
+ ok = false;
11693
+ return;
11694
+ }
11695
+ if (ts11.isJsxAttribute(attr)) {
11696
+ const name2 = attr.name.getText();
11697
+ if (/^on[A-Z]/.test(name2)) {
11698
+ ok = false;
11699
+ return;
11700
+ }
11701
+ }
11702
+ }
11703
+ }
11704
+ if (ts11.isCallExpression(n) && getMapLikeMethod(n) && containsJsxInExpression(n)) {
11705
+ ok = false;
11706
+ return;
11707
+ }
11708
+ ts11.forEachChild(n, visit3);
11709
+ };
11710
+ visit3(el);
11711
+ return ok;
11712
+ }
11599
11713
  function loopBodyIsMultiRoot(children2) {
11600
11714
  const real = children2.filter(
11601
11715
  (c) => !(c.type === "text" && typeof c.value === "string" && !c.value.trim())
@@ -11641,6 +11755,7 @@ function extractItemConditionalKey(cond) {
11641
11755
  }
11642
11756
  function transformMapCall(node, ctx2, isClientOnly = false, method2 = "map") {
11643
11757
  const isNested = ctx2.loopParams.size > 0;
11758
+ const diagCountAtEntry = ctx2.analyzer.errors.length;
11644
11759
  const depth = ctx2.loopDepth;
11645
11760
  const propAccess = node.expression;
11646
11761
  const mapSource = propAccess.expression;
@@ -11978,12 +12093,39 @@ function transformMapCall(node, ctx2, isClientOnly = false, method2 = "map") {
11978
12093
  }
11979
12094
  }
11980
12095
  }
11981
- if (method2 === "flatMap" && children2.length === 0) {
12096
+ if (method2 === "flatMap" && children2.length === 0 && !flatMapProjectionCall(body2)) {
11982
12097
  flatMapCallback = buildFlatMapCallback(callback, body2, ctx2);
11983
12098
  }
11984
12099
  } else {
11985
12100
  tryTransformRenderableBody(body2);
11986
12101
  }
12102
+ if (method2 === "flatMap" && children2.length === 0 && !flatMapCallback) {
12103
+ const projection = flatMapProjectionCall(body2);
12104
+ if (projection) {
12105
+ const transformed = transformJsxExpression(projection, ctx2, isClientOnly);
12106
+ if (transformed && transformed.type === "loop") {
12107
+ children2 = [transformed];
12108
+ }
12109
+ }
12110
+ }
12111
+ if (method2 === "flatMap" && children2.length === 0 && !flatMapCallback && !ts11.isBlock(body2)) {
12112
+ flatMapCallback = buildFlatMapCallback(callback, body2, ctx2);
12113
+ }
12114
+ if (flatMapCallback) preamble = void 0;
12115
+ if (flatMapCallback && !isClientOnly && !(ctx2.analyzer.acceptsCallbackBody?.("flatMap") ?? false)) {
12116
+ ctx2.analyzer.errors.push(
12117
+ createError(
12118
+ ErrorCodes.UNSUPPORTED_JSX_PATTERN,
12119
+ getSourceLocation(body2, ctx2.sourceFile, ctx2.filePath),
12120
+ {
12121
+ message: "A .flatMap() callback body with statements or a nested projection cannot be lowered to a template on this backend.",
12122
+ suggestion: {
12123
+ message: "Add /* @client */ to render this loop on the client only"
12124
+ }
12125
+ }
12126
+ )
12127
+ );
12128
+ }
11987
12129
  if (paramBindings) {
11988
12130
  for (const b of paramBindings) ctx2.loopParams.delete(b.name);
11989
12131
  } else {
@@ -11993,6 +12135,22 @@ function transformMapCall(node, ctx2, isClientOnly = false, method2 = "map") {
11993
12135
  ctx2.loopDepth--;
11994
12136
  }
11995
12137
  if (children2.length === 0 && !flatMapCallback) {
12138
+ const cb = node.arguments[0];
12139
+ const cbBody = cb && (ts11.isArrowFunction(cb) || ts11.isFunctionExpression(cb)) ? cb.body : void 0;
12140
+ if (cbBody && containsJsxInExpression(cbBody) && ctx2.analyzer.errors.length === diagCountAtEntry) {
12141
+ ctx2.analyzer.errors.push(
12142
+ createError(
12143
+ ErrorCodes.UNSUPPORTED_JSX_PATTERN,
12144
+ getSourceLocation(cbBody, ctx2.sourceFile, ctx2.filePath),
12145
+ {
12146
+ message: `A .${method2}() callback that builds JSX in this shape cannot be compiled \u2014 the JSX would leak verbatim into the client bundle. Recognized bodies: a JSX element/fragment, a ternary or && / || / ?? expression, an array literal (flatMap), or a block body whose return the compiler can lower.`,
12147
+ suggestion: {
12148
+ message: "Restructure the callback to return the JSX element directly (or via a block body with a plain `return`)."
12149
+ }
12150
+ }
12151
+ )
12152
+ );
12153
+ }
11996
12154
  return null;
11997
12155
  }
11998
12156
  if (ts11.isArrowFunction(node.arguments[0]) && children2.length > 0) {
@@ -12049,6 +12207,7 @@ function transformMapCall(node, ctx2, isClientOnly = false, method2 = "map") {
12049
12207
  const hasCalls = exprHasFunctionCalls(arrayExpr);
12050
12208
  const isDirectPropArray = method2 !== "flatMap" && isArrayExprDirectPropRef(arrayExpr, ctx2);
12051
12209
  const isStaticArray = !isSignalOrMemoArray(array, ctx2) && !isDirectPropArray && !hasCalls && !objectIteration;
12210
+ const preambleRegions = preamble && !isStaticArray ? collectPreambleRegions(children2, new Set(preamble.declaredNames), ctx2) : void 0;
12052
12211
  const nestedComponents = collectNestedComponents(children2).filter((c) => c.name !== childComponent?.name);
12053
12212
  return {
12054
12213
  type: "loop",
@@ -12086,6 +12245,7 @@ function transformMapCall(node, ctx2, isClientOnly = false, method2 = "map") {
12086
12245
  depth,
12087
12246
  clientOnly: isClientOnly || void 0,
12088
12247
  preamble,
12248
+ preambleRegions: preambleRegions && preambleRegions.length > 0 ? preambleRegions : void 0,
12089
12249
  paramType,
12090
12250
  indexType,
12091
12251
  paramBindings,
@@ -12146,6 +12306,40 @@ function buildFlatMapCallback(callback, body2, ctx2) {
12146
12306
  );
12147
12307
  return void 0;
12148
12308
  }
12309
+ for (const leafIr of leafIrs) {
12310
+ if (leafIr.type !== "element") {
12311
+ const loc = "loc" in leafIr && leafIr.loc ? leafIr.loc : getSourceLocation(body2, ctx2.sourceFile, ctx2.filePath);
12312
+ ctx2.analyzer.errors.push(
12313
+ createError(
12314
+ ErrorCodes.UNSUPPORTED_JSX_PATTERN,
12315
+ loc,
12316
+ {
12317
+ message: "A JSX leaf produced by a .flatMap() callback must be a single element \u2014 a fragment or non-element root cannot ride the keyed descriptor path (each leaf hydrates and patches as one element).",
12318
+ suggestion: {
12319
+ message: "Wrap the leaf content in a single keyed element."
12320
+ }
12321
+ }
12322
+ )
12323
+ );
12324
+ return void 0;
12325
+ }
12326
+ if (flatMapLeafNeedsWiring(leafIr)) {
12327
+ const loc = "loc" in leafIr && leafIr.loc ? leafIr.loc : getSourceLocation(body2, ctx2.sourceFile, ctx2.filePath);
12328
+ ctx2.analyzer.errors.push(
12329
+ createError(
12330
+ ErrorCodes.UNSUPPORTED_JSX_PATTERN,
12331
+ loc,
12332
+ {
12333
+ message: "A JSX element produced by a .flatMap() callback cannot carry event handlers, components, nested loops, or spreads \u2014 the leaf renders as a keyed HTML string with no per-element wiring.",
12334
+ suggestion: {
12335
+ message: "Restructure so the interactive element lives in a .map() body \u2014 the descriptor path has no per-element wiring on any backend, so /* @client */ does not lift this."
12336
+ }
12337
+ }
12338
+ )
12339
+ );
12340
+ return void 0;
12341
+ }
12342
+ }
12149
12343
  const pieces = reconstructAsSegments(body2, ctx2.sourceFile, ctx2.analyzer.typeExcludeRanges, leafSpans);
12150
12344
  const segments = pieces.map((piece) => {
12151
12345
  if ("marker" in piece) return { kind: "jsx", ir: leafIrs[piece.marker] };
@@ -12159,6 +12353,23 @@ function buildFlatMapCallback(callback, body2, ctx2) {
12159
12353
  rawBody: tsxSourceText(body2.getText(ctx2.sourceFile))
12160
12354
  };
12161
12355
  }
12356
+ function flatMapLeafNeedsWiring(ir) {
12357
+ switch (ir.type) {
12358
+ case "component":
12359
+ case "loop":
12360
+ return true;
12361
+ case "element":
12362
+ if (ir.events.length > 0) return true;
12363
+ if (ir.attrs.some((a) => a.name.startsWith("..."))) return true;
12364
+ return ir.children.some(flatMapLeafNeedsWiring);
12365
+ case "conditional":
12366
+ return flatMapLeafNeedsWiring(ir.whenTrue) || (ir.whenFalse ? flatMapLeafNeedsWiring(ir.whenFalse) : false);
12367
+ case "fragment":
12368
+ return ir.children.some(flatMapLeafNeedsWiring);
12369
+ default:
12370
+ return false;
12371
+ }
12372
+ }
12162
12373
  function preambleFragmentNeedsWiring(ir) {
12163
12374
  switch (ir.type) {
12164
12375
  case "component":
@@ -12202,6 +12413,39 @@ function flagArrayChildExpressions(nodes, declared) {
12202
12413
  }
12203
12414
  }
12204
12415
  }
12416
+ function collectPreambleRegions(nodes, declared, ctx2) {
12417
+ const regions = [];
12418
+ const visit3 = (list) => {
12419
+ for (const node of list) {
12420
+ switch (node.type) {
12421
+ case "expression": {
12422
+ const refs = extractFreeIdentifiersFromText(node.expr);
12423
+ const usesPreambleLocal = [...refs].some((r2) => declared.has(r2));
12424
+ if (usesPreambleLocal) {
12425
+ if (!node.slotId) node.slotId = generateSlotId(ctx2);
12426
+ node.preambleRegion = true;
12427
+ node.reactive = true;
12428
+ regions.push({
12429
+ slotId: node.slotId,
12430
+ expr: node.expr,
12431
+ joinArrayChild: node.joinArrayChild || void 0
12432
+ });
12433
+ }
12434
+ break;
12435
+ }
12436
+ case "element":
12437
+ case "fragment":
12438
+ visit3(node.children);
12439
+ break;
12440
+ case "conditional":
12441
+ visit3([node.whenTrue, ...node.whenFalse ? [node.whenFalse] : []]);
12442
+ break;
12443
+ }
12444
+ }
12445
+ };
12446
+ visit3(nodes);
12447
+ return regions;
12448
+ }
12205
12449
  function collectBindingNames(name2, out) {
12206
12450
  if (ts11.isIdentifier(name2)) {
12207
12451
  out.add(name2.text);
@@ -13570,6 +13814,7 @@ function collectLoopChildReactiveTexts(node, ctx2, loopParam, loopParamBindings,
13570
13814
  ...stopAt("loop", "async", "ifStatement"),
13571
13815
  expression: ({ node: n, scope: insideConditional }) => {
13572
13816
  if (!n.slotId) return;
13817
+ if (n.preambleRegion) return;
13573
13818
  const originFreeIds = freeIdsFromRefs(n.origin?.freeRefs);
13574
13819
  const expanded = expandConstantForReactivity(n.expr, ctx2, originFreeIds);
13575
13820
  const reactive = classifyReactivity(expanded.expr, ctx2, loopParam, loopParamBindings, expanded.freeIds).kind !== "none" || decideWrapFromAstFlags(n).wrap;
@@ -14102,10 +14347,13 @@ function collectElements(node, ctx2, siblingOffsets, insideConditional = false)
14102
14347
  },
14103
14348
  loop: ({ node: l, scope: inCond }) => {
14104
14349
  if (!l.slotId || inCond) return;
14350
+ const projectionInner = l.method === "flatMap" && l.children.length === 1 && l.children[0].type === "loop" ? l.children[0] : void 0;
14105
14351
  const childHandlers = [];
14106
- const bindings = collectLoopChildBindings(l.children, ctx2, siblingOffsets, l.param, l.paramBindings);
14107
- for (const child of l.children) {
14108
- childHandlers.push(...collectEventHandlersFromIR(child));
14352
+ const bindings = projectionInner ? emptyLoopChildBindings() : collectLoopChildBindings(l.children, ctx2, siblingOffsets, l.param, l.paramBindings);
14353
+ if (!projectionInner) {
14354
+ for (const child of l.children) {
14355
+ childHandlers.push(...collectEventHandlersFromIR(child));
14356
+ }
14109
14357
  }
14110
14358
  if (l.childComponent) {
14111
14359
  for (const prop of l.childComponent.props) {
@@ -14115,7 +14363,7 @@ function collectElements(node, ctx2, siblingOffsets, insideConditional = false)
14115
14363
  }
14116
14364
  }
14117
14365
  }
14118
- const { useElementReconciliation, innerLoops } = decideLoopRendering(l, siblingOffsets, ctx2);
14366
+ const { useElementReconciliation, innerLoops } = projectionInner ? { useElementReconciliation: false, innerLoops: void 0 } : decideLoopRendering(l, siblingOffsets, ctx2);
14119
14367
  let template = "";
14120
14368
  let staticItemTemplate;
14121
14369
  let skeletonTemplate;
@@ -14133,7 +14381,7 @@ function collectElements(node, ctx2, siblingOffsets, insideConditional = false)
14133
14381
  true
14134
14382
  );
14135
14383
  }
14136
- } else if (l.children[0]) {
14384
+ } else if (l.children[0] && !projectionInner) {
14137
14385
  const loopParamSpec = [{ param: l.param, bindings: l.paramBindings }];
14138
14386
  template = useElementReconciliation ? irToPlaceholderTemplate(l.children[0], buildRestSpreadNames(ctx2), 0, loopParamSpec) : irToHtmlTemplate(l.children[0], buildRestSpreadNames(ctx2), 0, loopParamSpec);
14139
14387
  if (l.isStaticArray) {
@@ -14185,7 +14433,17 @@ function collectElements(node, ctx2, siblingOffsets, insideConditional = false)
14185
14433
  raw: l.sortComparator.raw
14186
14434
  } : void 0,
14187
14435
  chainOrder: l.chainOrder,
14188
- preamble: l.preamble
14436
+ preamble: l.preamble,
14437
+ preambleRegions: l.preambleRegions,
14438
+ flatMapClient: projectionInner ? {
14439
+ params: l.index ? `(${l.param}, ${l.index})` : `(${l.param})`,
14440
+ body: renderFlatMapProjectionClientBody(projectionInner, buildRestSpreadNames(ctx2)),
14441
+ keyed: projectionInner.key !== null
14442
+ } : l.flatMapCallback ? {
14443
+ params: l.flatMapCallback.params,
14444
+ body: renderFlatMapClientBody(l.flatMapCallback, buildRestSpreadNames(ctx2)),
14445
+ keyed: flatMapCallbackHasKeyedLeaf(l.flatMapCallback)
14446
+ } : void 0
14189
14447
  });
14190
14448
  },
14191
14449
  component: ({ node: c, descend, descendJsxChildren }) => {
@@ -14340,15 +14598,18 @@ function collectBranchLoops(node, ctx2, siblingOffsets) {
14340
14598
  loop: ({ node: n, scope: parentSlotId }) => {
14341
14599
  const containerSlot = parentSlotId ?? n.slotId;
14342
14600
  if (!containerSlot) return;
14343
- const { useElementReconciliation, innerLoops: innerLoopsCollected } = decideLoopRendering(n, siblingOffsets, void 0);
14601
+ const projectionInner = n.method === "flatMap" && n.children.length === 1 && n.children[0].type === "loop" ? n.children[0] : void 0;
14602
+ const { useElementReconciliation, innerLoops: innerLoopsCollected } = projectionInner ? { useElementReconciliation: false, innerLoops: void 0 } : decideLoopRendering(n, siblingOffsets, void 0);
14344
14603
  let childTemplate;
14345
14604
  const branchLoopParamSpec = [{ param: n.param, bindings: n.paramBindings }];
14346
- if (useElementReconciliation && n.children[0]) {
14605
+ if (projectionInner) {
14606
+ childTemplate = "";
14607
+ } else if (useElementReconciliation && n.children[0]) {
14347
14608
  childTemplate = irToPlaceholderTemplate(n.children[0], restNames, 0, branchLoopParamSpec);
14348
14609
  } else {
14349
14610
  childTemplate = n.children.map((c) => irToHtmlTemplate(c, void 0, 0, branchLoopParamSpec)).join("");
14350
14611
  }
14351
- const branchBindings = ctx2 ? collectLoopChildBindings(n.children, ctx2, siblingOffsets, n.param, n.paramBindings) : emptyLoopChildBindings();
14612
+ const branchBindings = ctx2 && !projectionInner ? collectLoopChildBindings(n.children, ctx2, siblingOffsets, n.param, n.paramBindings) : emptyLoopChildBindings();
14352
14613
  loops.push({
14353
14614
  kind: "branch",
14354
14615
  array: n.array,
@@ -14365,6 +14626,7 @@ function collectBranchLoops(node, ctx2, siblingOffsets) {
14365
14626
  template: childTemplate,
14366
14627
  containerSlotId: containerSlot,
14367
14628
  preamble: n.preamble,
14629
+ preambleRegions: n.preambleRegions,
14368
14630
  nestedComponents: useElementReconciliation ? n.nestedComponents : void 0,
14369
14631
  bindings: branchBindings,
14370
14632
  innerLoops: useElementReconciliation ? innerLoopsCollected : void 0,
@@ -14378,7 +14640,16 @@ function collectBranchLoops(node, ctx2, siblingOffsets) {
14378
14640
  paramB: n.sortComparator.paramB,
14379
14641
  raw: n.sortComparator.raw
14380
14642
  } : void 0,
14381
- chainOrder: n.chainOrder
14643
+ chainOrder: n.chainOrder,
14644
+ flatMapClient: projectionInner ? {
14645
+ params: n.index ? `(${n.param}, ${n.index})` : `(${n.param})`,
14646
+ body: renderFlatMapProjectionClientBody(projectionInner, restNames),
14647
+ keyed: projectionInner.key !== null
14648
+ } : n.flatMapCallback ? {
14649
+ params: n.flatMapCallback.params,
14650
+ body: renderFlatMapClientBody(n.flatMapCallback, restNames),
14651
+ keyed: flatMapCallbackHasKeyedLeaf(n.flatMapCallback)
14652
+ } : void 0
14382
14653
  });
14383
14654
  }
14384
14655
  });
@@ -14774,6 +15045,12 @@ function buildReferencesGraph(ctx2, irRoot) {
14774
15045
  if (l.filterPredicate) addExprEdges(ROOT_SOURCE, l.filterPredicate.raw, "template-closure");
14775
15046
  if (l.sortComparator) addExprEdges(ROOT_SOURCE, l.sortComparator.raw, "template-closure");
14776
15047
  if (l.preamble) addExprEdges(ROOT_SOURCE, preambleAnalysisText(l.preamble), "template-closure");
15048
+ if (l.flatMapCallback) {
15049
+ addExprEdges(ROOT_SOURCE, preambleAnalysisText(l.flatMapCallback), "template-closure");
15050
+ for (const seg of l.flatMapCallback.segments) {
15051
+ if (seg.kind === "jsx") walkIR(seg.ir, null, visitor);
15052
+ }
15053
+ }
14777
15054
  descend();
14778
15055
  if (l.childComponent) walkChildComponent(l.childComponent);
14779
15056
  if (l.nestedComponents) {
@@ -15055,6 +15332,8 @@ var init_imports = __esm({
15055
15332
  "getLoopNodes",
15056
15333
  "mapArray",
15057
15334
  "mapArrayAnchored",
15335
+ "patchLeaf",
15336
+ "patchSlotRange",
15058
15337
  "createDisposableEffect",
15059
15338
  "createComponent",
15060
15339
  "renderChild",
@@ -17006,6 +17285,14 @@ function destructureLoopParam(param, paramBindings) {
17006
17285
  }
17007
17286
  return { head: param, unwrap: "" };
17008
17287
  }
17288
+ function buildPreambleRegionPlans(regions, loopParam, loopParamBindings) {
17289
+ if (!regions || regions.length === 0) return [];
17290
+ return regions.map((r2) => {
17291
+ const wrapped = wrapLoopParamAsAccessor(r2.expr, loopParam, loopParamBindings);
17292
+ const valueExpr = r2.joinArrayChild ? `Array.isArray(${wrapped}) ? ${wrapped}.join('') : (${wrapped} ?? '')` : `escapeText(${wrapped})`;
17293
+ return { slotId: r2.slotId, valueExpr };
17294
+ });
17295
+ }
17009
17296
  function buildComponentPropsExpr2(comp, loopParam, loopParamBindings) {
17010
17297
  const wrap = loopParam ? (expr) => wrapLoopParamAsAccessor(expr, loopParam, loopParamBindings) : (expr) => expr;
17011
17298
  const entries2 = comp.props.map((p) => {
@@ -18104,9 +18391,16 @@ function buildDynamicLoopDelegationPlan(elem, profileComponentName) {
18104
18391
  paramBindings: elem.paramBindings,
18105
18392
  key: elem.key,
18106
18393
  index: elem.index,
18394
+ // No loopParams spec here (unlike the row-render context) — in the
18395
+ // delegated handler `elem.param` is bound to the plain `.find()`/
18396
+ // indexed result, not a signal accessor, so leaf refs must stay in
18397
+ // their literal (`t.name`) form. Passing a loopParams spec here was
18398
+ // BUG-3: it rewrote leaf refs to accessor-call form (`t().name`),
18399
+ // which throws since `t` is a plain object in this scope.
18107
18400
  mapPreamble: elem.preamble ? renderPreamble(elem.preamble, {
18108
- renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, [{ param: elem.param, bindings: elem.paramBindings }], void 0, true)
18109
- }) : null
18401
+ renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, void 0, void 0, true)
18402
+ }) : null,
18403
+ mapPreambleDeclaredNames: elem.preamble?.declaredNames ?? []
18110
18404
  })
18111
18405
  };
18112
18406
  }
@@ -18123,9 +18417,12 @@ function buildBranchLoopDelegationPlan(loop, cv, profileComponentName) {
18123
18417
  paramBindings: loop.paramBindings,
18124
18418
  key: loop.key,
18125
18419
  index: loop.index,
18420
+ // See note in `buildDynamicLoopDelegationPlan` above (BUG-3): no
18421
+ // loopParams spec — leaf refs must stay in plain-object form here.
18126
18422
  mapPreamble: loop.preamble ? renderPreamble(loop.preamble, {
18127
- renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, [{ param: loop.param, bindings: loop.paramBindings }], void 0, true)
18128
- }) : null
18423
+ renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, void 0, void 0, true)
18424
+ }) : null,
18425
+ mapPreambleDeclaredNames: loop.preamble?.declaredNames ?? []
18129
18426
  })
18130
18427
  };
18131
18428
  }
@@ -18142,9 +18439,12 @@ function buildStaticArrayDelegationPlan(elem, profileComponentName) {
18142
18439
  // array too (#1434).
18143
18440
  arrayExpr: buildChainedArrayExpr(elem),
18144
18441
  param: elem.param,
18442
+ // See note in `buildDynamicLoopDelegationPlan` above (BUG-3): no
18443
+ // loopParams spec — leaf refs must stay in plain-object form here.
18145
18444
  mapPreamble: elem.preamble ? renderPreamble(elem.preamble, {
18146
- renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, [{ param: elem.param, bindings: elem.paramBindings }], void 0, true)
18445
+ renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, void 0, void 0, true)
18147
18446
  }) : null,
18447
+ mapPreambleDeclaredNames: elem.preamble?.declaredNames ?? [],
18148
18448
  offset: elem.offset ?? null,
18149
18449
  indexParam: elem.index ?? null
18150
18450
  }
@@ -18161,6 +18461,7 @@ function buildKeyedOrIndexLookup(args2) {
18161
18461
  paramBindings: args2.paramBindings,
18162
18462
  keyWithItem,
18163
18463
  mapPreamble: args2.mapPreamble,
18464
+ mapPreambleDeclaredNames: args2.mapPreambleDeclaredNames,
18164
18465
  hasBindings,
18165
18466
  indexParam: args2.index
18166
18467
  };
@@ -18170,6 +18471,7 @@ function buildKeyedOrIndexLookup(args2) {
18170
18471
  arrayExpr: args2.array,
18171
18472
  param: args2.param,
18172
18473
  mapPreamble: args2.mapPreamble,
18474
+ mapPreambleDeclaredNames: args2.mapPreambleDeclaredNames,
18173
18475
  hasBindings,
18174
18476
  indexParam: args2.index
18175
18477
  };
@@ -18198,14 +18500,16 @@ function buildBranchLoopPlan(loop, profileComponentName) {
18198
18500
  }
18199
18501
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(loop.param, loop.paramBindings);
18200
18502
  const hasReactiveEffects = loop.bindings.reactiveAttrs.length > 0 || loop.bindings.reactiveTexts.length > 0 || loop.bindings.conditionals.length > 0;
18503
+ const fm = loop.flatMapClient;
18201
18504
  const plan = {
18202
18505
  kind: "plain",
18203
18506
  rowConstruction: "string-template",
18204
18507
  containerSlotId,
18205
18508
  containerVar,
18206
18509
  markerId: loop.markerId,
18207
- arrayExpr: buildChainedArrayExpr(loop),
18208
- keyFn: loopKeyFn(loop),
18510
+ flatMapLeafItem: fm ? true : void 0,
18511
+ arrayExpr: fm ? `(${buildChainedArrayExpr(loop)}).flatMap(${fm.params} => ${fm.body})` : buildChainedArrayExpr(loop),
18512
+ keyFn: fm ? fm.keyed ? "(__bfD, __bfI) => String(__bfD.k ?? __bfI)" : "null" : loopKeyFn(loop),
18209
18513
  paramHead,
18210
18514
  paramUnwrap,
18211
18515
  indexParam: loop.index || "__idx",
@@ -18226,6 +18530,7 @@ function buildBranchLoopPlan(loop, profileComponentName) {
18226
18530
  }) : null,
18227
18531
  eventDelegation: buildBranchLoopDelegationPlan(loop, cv, profileComponentName),
18228
18532
  childRefs: buildChildRefBindings(loop.bindings.refs, loop.param, loop.paramBindings),
18533
+ preambleRegions: buildPreambleRegionPlans(loop.preambleRegions, loop.param, loop.paramBindings),
18229
18534
  bodyIsMultiRoot: loop.bodyIsMultiRoot ?? false,
18230
18535
  profileLoopId: profileComponentName ? `${profileComponentName}#binding:${containerSlotId}` : void 0
18231
18536
  };
@@ -18937,6 +19242,20 @@ function emitLoopChildRefs(lines, refs, opts) {
18937
19242
  lines.push(`${indent}if (${varName}) ${emitRefCall(ref.callback, varName)} }`);
18938
19243
  }
18939
19244
  }
19245
+ function emitPreambleRegionEffects(lines, regions, mapPreambleWrapped, opts) {
19246
+ if (regions.length === 0) return;
19247
+ const { indent, elVar } = opts;
19248
+ for (const region of regions) {
19249
+ const v = varSlotId(region.slotId);
19250
+ lines.push(`${indent}{ let __last_${v}`);
19251
+ lines.push(`${indent}createEffect(() => {`);
19252
+ if (mapPreambleWrapped) lines.push(`${indent} ${mapPreambleWrapped}`);
19253
+ lines.push(`${indent} const __html_${v} = ${region.valueExpr}`);
19254
+ lines.push(`${indent} if (__last_${v} === undefined) { __last_${v} = __html_${v}; return }`);
19255
+ lines.push(`${indent} if (__html_${v} !== __last_${v}) { __last_${v} = __html_${v}; patchSlotRange(${elVar}, '${region.slotId}', __html_${v}) }`);
19256
+ lines.push(`${indent}}) }`);
19257
+ }
19258
+ }
18940
19259
  function stringifyLoop(lines, plan) {
18941
19260
  switch (plan.kind) {
18942
19261
  case "static":
@@ -18978,8 +19297,24 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
18978
19297
  childRefs,
18979
19298
  bodyIsMultiRoot,
18980
19299
  anchored,
18981
- anchorKeyExpr
19300
+ anchorKeyExpr,
19301
+ preambleRegions
18982
19302
  } = plan;
19303
+ if (plan.flatMapLeafItem) {
19304
+ const loopBfIdArg = plan.profileLoopId ? `, ${JSON.stringify(plan.profileLoopId)}` : "";
19305
+ lines.push(`${topIndent}mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (__bfD, ${indexParam}, __existing) => {`);
19306
+ lines.push(`${topIndent} let __el = __existing`);
19307
+ lines.push(`${topIndent} if (!__el) { const __tpl = document.createElement('template'); __tpl.innerHTML = __bfD().h; __el = __tpl.content.firstElementChild }`);
19308
+ lines.push(`${topIndent} let __last = __existing ? undefined : __bfD().h`);
19309
+ lines.push(`${topIndent} createEffect(() => {`);
19310
+ lines.push(`${topIndent} const __html = __bfD().h`);
19311
+ lines.push(`${topIndent} if (__last === undefined) { __last = __html; return }`);
19312
+ lines.push(`${topIndent} if (__html !== __last) { __last = __html; patchLeaf(__el, __html) }`);
19313
+ lines.push(`${topIndent} })`);
19314
+ lines.push(`${topIndent} return __el`);
19315
+ lines.push(`${topIndent}}, '${markerId}'${loopBfIdArg})`);
19316
+ return;
19317
+ }
18983
19318
  if (anchored) {
18984
19319
  stringifyAnchoredLoop(lines, plan, topIndent, anchorKeyExpr);
18985
19320
  return;
@@ -18990,7 +19325,7 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
18990
19325
  emitHoistedTemplateDecl(lines, topIndent, tplVar, hoistedTpl);
18991
19326
  }
18992
19327
  const loopBfId = plan.profileLoopId ? `, ${JSON.stringify(plan.profileLoopId)}` : "";
18993
- if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0) {
19328
+ if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0 && preambleRegions.length === 0) {
18994
19329
  const unwrapInline = paramUnwrap ? `${paramUnwrap} ` : "";
18995
19330
  const preamble = mapPreambleWrapped ? `${mapPreambleWrapped}; ` : "";
18996
19331
  const cloneExpr = hoistedTpl ? `return ${hoistedCloneExpr(tplVar, hoistedTpl)}` : emitTemplateCloneInline(template);
@@ -19038,6 +19373,7 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
19038
19373
  });
19039
19374
  }
19040
19375
  emitLoopChildRefs(lines, childRefs, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot, elementIndexBySlot: pathPlan?.elementIndexBySlot });
19376
+ emitPreambleRegionEffects(lines, preambleRegions, mapPreambleWrapped, { indent: bodyIndent, elVar: "__el" });
19041
19377
  lines.push(`${bodyIndent}return __el`);
19042
19378
  lines.push(`${topIndent}}, '${markerId}'${loopBfId})`);
19043
19379
  }
@@ -19370,6 +19706,11 @@ function indexBindingLine(handler, indexParam, indexExpr) {
19370
19706
  if (!extractFreeIdentifiersFromText(handler).has(indexParam)) return null;
19371
19707
  return `const ${indexParam} = ${indexExpr}`;
19372
19708
  }
19709
+ function preambleLineForHandler(mapPreamble, declaredNames, handler) {
19710
+ if (!mapPreamble || declaredNames.length === 0) return null;
19711
+ const free = extractFreeIdentifiersFromText(handler);
19712
+ return declaredNames.some((name2) => free.has(name2)) ? mapPreamble : null;
19713
+ }
19373
19714
  function stringifyEventDelegation(lines, plan) {
19374
19715
  const { containerVar, events, itemLookup, profileComponentName } = plan;
19375
19716
  const eventsByName = /* @__PURE__ */ new Map();
@@ -19414,7 +19755,8 @@ function stringifyEventDelegation(lines, plan) {
19414
19755
  }
19415
19756
  }
19416
19757
  function emitKeyedLookup(ls, ev, handlerCall, lookup) {
19417
- const { arrayExpr, param, keyWithItem, mapPreamble, hasBindings, indexParam } = lookup;
19758
+ const { arrayExpr, param, keyWithItem, mapPreamble, mapPreambleDeclaredNames, hasBindings, indexParam } = lookup;
19759
+ const preambleLine = preambleLineForHandler(mapPreamble, mapPreambleDeclaredNames, ev.handler);
19418
19760
  if (ev.nestedLoops.length === 0) {
19419
19761
  const idxLine2 = indexBindingLine(ev.handler, indexParam, `${arrayExpr}.findIndex(item => String(${keyWithItem}) === key)`);
19420
19762
  ls.push(` const li = ${varSlotId(ev.childSlotId)}El.closest('[${BF_KEY}]')`);
@@ -19424,15 +19766,17 @@ function emitKeyedLookup(ls, ev, handlerCall, lookup) {
19424
19766
  ls.push(` const __bfLoopItem = ${arrayExpr}.find(item => String(${keyWithItem}) === key)`);
19425
19767
  ls.push(` if (__bfLoopItem) {`);
19426
19768
  ls.push(` const ${param} = __bfLoopItem`);
19427
- if (mapPreamble) ls.push(` ${mapPreamble}`);
19769
+ if (preambleLine) ls.push(` ${preambleLine}`);
19428
19770
  if (idxLine2) ls.push(` ${idxLine2}`);
19429
- ls.push(` ${handlerCall}`);
19771
+ ls.push(` ;${handlerCall}`);
19430
19772
  ls.push(` }`);
19431
19773
  } else {
19432
19774
  ls.push(` const ${param} = ${arrayExpr}.find(item => String(${keyWithItem}) === key)`);
19433
- if (mapPreamble) ls.push(` ${mapPreamble}`);
19434
- if (idxLine2) ls.push(` if (${param}) { ${idxLine2}; ${handlerCall} }`);
19435
- else ls.push(` if (${param}) ${handlerCall}`);
19775
+ ls.push(` if (${param}) {`);
19776
+ if (preambleLine) ls.push(` ${preambleLine}`);
19777
+ if (idxLine2) ls.push(` ${idxLine2}`);
19778
+ ls.push(` ;${handlerCall}`);
19779
+ ls.push(` }`);
19436
19780
  }
19437
19781
  ls.push(` }`);
19438
19782
  return;
@@ -19459,13 +19803,16 @@ function emitKeyedLookup(ls, ev, handlerCall, lookup) {
19459
19803
  }
19460
19804
  const outerGuard = hasBindings ? "__bfLoopItem" : param;
19461
19805
  const allParams = [outerGuard, ...ev.nestedLoops.map((n) => n.param)];
19462
- if (mapPreamble) ls.push(` ${mapPreamble}`);
19463
19806
  const idxLine = indexBindingLine(ev.handler, indexParam, `${arrayExpr}.findIndex(item => String(${keyWithItem}) === outerKey)`);
19464
- if (idxLine) ls.push(` if (${allParams.join(" && ")}) { ${idxLine}; ${handlerCall} }`);
19465
- else ls.push(` if (${allParams.join(" && ")}) ${handlerCall}`);
19807
+ ls.push(` if (${allParams.join(" && ")}) {`);
19808
+ if (preambleLine) ls.push(` ${preambleLine}`);
19809
+ if (idxLine) ls.push(` ${idxLine}`);
19810
+ ls.push(` ;${handlerCall}`);
19811
+ ls.push(` }`);
19466
19812
  }
19467
19813
  function emitDynamicIndexLookup(ls, ev, handlerCall, lookup) {
19468
- const { arrayExpr, param, mapPreamble, hasBindings, indexParam } = lookup;
19814
+ const { arrayExpr, param, mapPreamble, mapPreambleDeclaredNames, hasBindings, indexParam } = lookup;
19815
+ const preambleLine = preambleLineForHandler(mapPreamble, mapPreambleDeclaredNames, ev.handler);
19469
19816
  const idxLine = indexBindingLine(ev.handler, indexParam, "idx");
19470
19817
  ls.push(` const li = ${varSlotId(ev.childSlotId)}El.closest('li, [bf-i]')`);
19471
19818
  ls.push(` if (li && li.parentElement) {`);
@@ -19474,20 +19821,23 @@ function emitDynamicIndexLookup(ls, ev, handlerCall, lookup) {
19474
19821
  ls.push(` const __bfLoopItem = ${arrayExpr}[idx]`);
19475
19822
  ls.push(` if (__bfLoopItem) {`);
19476
19823
  ls.push(` const ${param} = __bfLoopItem`);
19477
- if (mapPreamble) ls.push(` ${mapPreamble}`);
19824
+ if (preambleLine) ls.push(` ${preambleLine}`);
19478
19825
  if (idxLine) ls.push(` ${idxLine}`);
19479
- ls.push(` ${handlerCall}`);
19826
+ ls.push(` ;${handlerCall}`);
19480
19827
  ls.push(` }`);
19481
19828
  } else {
19482
19829
  ls.push(` const ${param} = ${arrayExpr}[idx]`);
19483
- if (mapPreamble) ls.push(` ${mapPreamble}`);
19484
- if (idxLine) ls.push(` if (${param}) { ${idxLine}; ${handlerCall} }`);
19485
- else ls.push(` if (${param}) ${handlerCall}`);
19830
+ ls.push(` if (${param}) {`);
19831
+ if (preambleLine) ls.push(` ${preambleLine}`);
19832
+ if (idxLine) ls.push(` ${idxLine}`);
19833
+ ls.push(` ;${handlerCall}`);
19834
+ ls.push(` }`);
19486
19835
  }
19487
19836
  ls.push(` }`);
19488
19837
  }
19489
19838
  function emitStaticIndexLookup(ls, ev, handlerCall, lookup, containerVar) {
19490
- const { arrayExpr, param, mapPreamble, offset: offset2, indexParam } = lookup;
19839
+ const { arrayExpr, param, mapPreamble, mapPreambleDeclaredNames, offset: offset2, indexParam } = lookup;
19840
+ const preambleLine = preambleLineForHandler(mapPreamble, mapPreambleDeclaredNames, ev.handler);
19491
19841
  const idxLine = indexBindingLine(ev.handler, indexParam, "__idx");
19492
19842
  ls.push(` let __el = ${varSlotId(ev.childSlotId)}El`);
19493
19843
  ls.push(` while (__el.parentElement && __el.parentElement !== ${containerVar}) __el = __el.parentElement`);
@@ -19495,9 +19845,11 @@ function emitStaticIndexLookup(ls, ev, handlerCall, lookup, containerVar) {
19495
19845
  const idxOffset = buildLoopChildIndexSubtraction(offset2 ?? void 0);
19496
19846
  ls.push(` const __idx = Array.from(${containerVar}.children).indexOf(__el)${idxOffset}`);
19497
19847
  ls.push(` const ${param} = ${arrayExpr}[__idx]`);
19498
- if (mapPreamble) ls.push(` ${mapPreamble}`);
19499
- if (idxLine) ls.push(` if (${param}) { ${idxLine}; ${handlerCall} }`);
19500
- else ls.push(` if (${param}) ${handlerCall}`);
19848
+ ls.push(` if (${param}) {`);
19849
+ if (preambleLine) ls.push(` ${preambleLine}`);
19850
+ if (idxLine) ls.push(` ${idxLine}`);
19851
+ ls.push(` ;${handlerCall}`);
19852
+ ls.push(` }`);
19501
19853
  ls.push(` }`);
19502
19854
  }
19503
19855
  var NON_BUBBLING_EVENTS;
@@ -19548,12 +19900,29 @@ function emitPlain(lines, plan) {
19548
19900
  eventDelegation,
19549
19901
  childRefs,
19550
19902
  bodyIsMultiRoot,
19551
- profileLoopId
19903
+ profileLoopId,
19904
+ preambleRegions
19552
19905
  } = plan;
19553
19906
  const loopBfId = profileLoopId ? `, ${JSON.stringify(profileLoopId)}` : "";
19554
19907
  const unwrapInline = paramUnwrap ? `${paramUnwrap} ` : "";
19555
19908
  lines.push(` __disposers.push(createDisposableEffect(() => {`);
19556
- if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0) {
19909
+ if (plan.flatMapLeafItem) {
19910
+ lines.push(` if (${containerVar}) mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (__bfD, ${indexParam}, __existing) => {`);
19911
+ lines.push(` let __el = __existing`);
19912
+ lines.push(` if (!__el) { const __tpl = document.createElement('template'); __tpl.innerHTML = __bfD().h; __el = __tpl.content.firstElementChild }`);
19913
+ lines.push(` let __last = __existing ? undefined : __bfD().h`);
19914
+ lines.push(` createEffect(() => {`);
19915
+ lines.push(` const __html = __bfD().h`);
19916
+ lines.push(` if (__last === undefined) { __last = __html; return }`);
19917
+ lines.push(` if (__html !== __last) { __last = __html; patchLeaf(__el, __html) }`);
19918
+ lines.push(` })`);
19919
+ lines.push(` return __el`);
19920
+ lines.push(` }, '${markerId}'${loopBfId})`);
19921
+ lines.push(` }))`);
19922
+ stringifyEventDelegation(lines, eventDelegation);
19923
+ return;
19924
+ }
19925
+ if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0 && preambleRegions.length === 0) {
19557
19926
  const cloneExpr = emitTemplateCloneInline(template);
19558
19927
  if (mapPreambleWrapped) {
19559
19928
  lines.push(` if (${containerVar}) mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => { ${unwrapInline}if (__existing) return __existing; ${mapPreambleWrapped}; ${cloneExpr} }, '${markerId}'${loopBfId})`);
@@ -19578,6 +19947,7 @@ function emitPlain(lines, plan) {
19578
19947
  stringifyReactiveEffects(lines, reactiveEffects, { indent: " ", elVar: "__el", bodyIsMultiRoot });
19579
19948
  }
19580
19949
  emitLoopChildRefs(lines, childRefs, { indent: " ", elVar: "__el", bodyIsMultiRoot });
19950
+ emitPreambleRegionEffects(lines, preambleRegions, mapPreambleWrapped, { indent: " ", elVar: "__el" });
19581
19951
  lines.push(` return __el`);
19582
19952
  lines.push(` }, '${markerId}'${loopBfId})`);
19583
19953
  }
@@ -19795,6 +20165,31 @@ function buildPlainLoopPlan(elem, profileComponentName) {
19795
20165
  const wrap = (expr) => wrapLoopParamAsAccessor(expr, elem.param, elem.paramBindings);
19796
20166
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(elem.param, elem.paramBindings);
19797
20167
  const hasReactive2 = elem.bindings.reactiveAttrs.length > 0 || elem.bindings.reactiveTexts.length > 0 || elem.bindings.conditionals.length > 0;
20168
+ if (elem.flatMapClient) {
20169
+ return {
20170
+ kind: "plain",
20171
+ rowConstruction: "string-template",
20172
+ containerVar: `_${varSlotId(elem.slotId)}`,
20173
+ markerId: elem.markerId,
20174
+ profileLoopId: profileComponentName ? `${profileComponentName}#binding:${elem.slotId}` : void 0,
20175
+ arrayExpr: `(${buildChainedArrayExpr(elem)}).flatMap(${elem.flatMapClient.params} => ${elem.flatMapClient.body})`,
20176
+ keyFn: elem.flatMapClient.keyed ? "(__bfD, __bfI) => String(__bfD.k ?? __bfI)" : "null",
20177
+ paramHead: "__bfD",
20178
+ paramUnwrap: "",
20179
+ indexParam: "__idx",
20180
+ mapPreambleWrapped: "",
20181
+ template: "",
20182
+ reactiveEffects: null,
20183
+ childRefs: [],
20184
+ bodyIsMultiRoot: false,
20185
+ anchored: false,
20186
+ anchorKeyExpr: "__idx",
20187
+ flatMapLeafItem: true,
20188
+ // flatMap loops carry no `MapCallbackPreamble` (jsx-to-ir.ts drops it
20189
+ // for flatMapCallback), so there is nothing to patch here.
20190
+ preambleRegions: []
20191
+ };
20192
+ }
19798
20193
  return {
19799
20194
  kind: "plain",
19800
20195
  rowConstruction: "string-template",
@@ -19818,6 +20213,7 @@ function buildPlainLoopPlan(elem, profileComponentName) {
19818
20213
  skeletonPaths: elem.skeletonPaths,
19819
20214
  reactiveEffects: hasReactive2 ? buildLoopReactiveEffectsPlan(elem, profileComponentName) : null,
19820
20215
  childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings),
20216
+ preambleRegions: buildPreambleRegionPlans(elem.preambleRegions, elem.param, elem.paramBindings),
19821
20217
  bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false,
19822
20218
  anchored: elem.bodyIsItemConditional ?? false,
19823
20219
  // Fall back to the iteration index when the loop has no key. A whole-item
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/cli",
3
- "version": "0.26.3",
3
+ "version": "0.26.4",
4
4
  "description": "CLI for agent-driven UI component discovery and scaffolding",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -29,11 +29,11 @@
29
29
  "dependencies": {
30
30
  "esbuild": "^0.25.0",
31
31
  "typescript": "^5.0.0",
32
- "@barefootjs/client": "0.26.3",
33
- "@barefootjs/shared": "0.26.3"
32
+ "@barefootjs/client": "0.26.4",
33
+ "@barefootjs/shared": "0.26.4"
34
34
  },
35
35
  "devDependencies": {
36
- "@barefootjs/jsx": "0.26.3",
36
+ "@barefootjs/jsx": "0.26.4",
37
37
  "@types/node": "^22.0.0",
38
38
  "@happy-dom/global-registrator": "^20.0.11",
39
39
  "happy-dom": "^20.0.11"