@barefootjs/jsx 0.26.2 → 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.
Files changed (98) hide show
  1. package/dist/adapters/interface.d.ts +29 -0
  2. package/dist/adapters/interface.d.ts.map +1 -1
  3. package/dist/adapters/jsx-adapter.d.ts +9 -0
  4. package/dist/adapters/jsx-adapter.d.ts.map +1 -1
  5. package/dist/adapters/loop-bound-names.d.ts.map +1 -1
  6. package/dist/adapters/parsed-expr-emitter.d.ts +0 -10
  7. package/dist/adapters/parsed-expr-emitter.d.ts.map +1 -1
  8. package/dist/adapters/test-adapter.d.ts.map +1 -1
  9. package/dist/analyzer-context.d.ts +11 -1
  10. package/dist/analyzer-context.d.ts.map +1 -1
  11. package/dist/analyzer.d.ts +40 -1
  12. package/dist/analyzer.d.ts.map +1 -1
  13. package/dist/expression-parser.d.ts.map +1 -1
  14. package/dist/index.js +1086 -269
  15. package/dist/ir-to-client-js/build-references.d.ts.map +1 -1
  16. package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
  17. package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts +14 -1
  18. package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts.map +1 -1
  19. package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts.map +1 -1
  20. package/dist/ir-to-client-js/control-flow/plan/build-component-loop.d.ts.map +1 -1
  21. package/dist/ir-to-client-js/control-flow/plan/build-composite-loop.d.ts.map +1 -1
  22. package/dist/ir-to-client-js/control-flow/plan/build-event-delegation.d.ts.map +1 -1
  23. package/dist/ir-to-client-js/control-flow/plan/build-inner-loop.d.ts.map +1 -1
  24. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
  25. package/dist/ir-to-client-js/control-flow/plan/event-delegation.d.ts +16 -1
  26. package/dist/ir-to-client-js/control-flow/plan/event-delegation.d.ts.map +1 -1
  27. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +39 -0
  28. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
  29. package/dist/ir-to-client-js/control-flow/shared.d.ts +12 -1
  30. package/dist/ir-to-client-js/control-flow/shared.d.ts.map +1 -1
  31. package/dist/ir-to-client-js/control-flow/stringify/event-delegation.d.ts +1 -1
  32. package/dist/ir-to-client-js/control-flow/stringify/event-delegation.d.ts.map +1 -1
  33. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts +19 -0
  34. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
  35. package/dist/ir-to-client-js/control-flow.d.ts.map +1 -1
  36. package/dist/ir-to-client-js/html-template.d.ts +67 -1
  37. package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
  38. package/dist/ir-to-client-js/imports.d.ts +2 -2
  39. package/dist/ir-to-client-js/imports.d.ts.map +1 -1
  40. package/dist/ir-to-client-js/plan/build-static-array-child-init.d.ts.map +1 -1
  41. package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
  42. package/dist/ir-to-client-js/types.d.ts +29 -4
  43. package/dist/ir-to-client-js/types.d.ts.map +1 -1
  44. package/dist/jsx-to-ir.d.ts.map +1 -1
  45. package/dist/loop-destructure.d.ts.map +1 -1
  46. package/dist/strip-types.d.ts +18 -0
  47. package/dist/strip-types.d.ts.map +1 -1
  48. package/dist/types.d.ts +143 -32
  49. package/dist/types.d.ts.map +1 -1
  50. package/package.json +2 -2
  51. package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +277 -33
  52. package/src/__tests__/client-js-generation.test.ts +18 -5
  53. package/src/__tests__/compiler-runtime-contract.test.ts +4 -4
  54. package/src/__tests__/compiler-stress-1244.test.ts +12 -1
  55. package/src/__tests__/delegated-handler-preamble.test.ts +153 -0
  56. package/src/__tests__/event-delegation-scope-2367.test.ts +108 -0
  57. package/src/__tests__/flatmap-segments.test.ts +262 -0
  58. package/src/__tests__/map-arbitrary-body.test.ts +226 -0
  59. package/src/__tests__/map-body-no-silent-divergence.test.ts +405 -0
  60. package/src/__tests__/map-multi-return-body.test.ts +172 -0
  61. package/src/__tests__/preamble-region-patch.test.ts +150 -0
  62. package/src/__tests__/static-loop-csr-materialize.test.ts +3 -2
  63. package/src/__tests__/unsupported-expression.test.ts +20 -2
  64. package/src/adapters/interface.ts +40 -0
  65. package/src/adapters/jsx-adapter.ts +10 -0
  66. package/src/adapters/loop-bound-names.ts +6 -2
  67. package/src/adapters/parsed-expr-emitter.ts +5 -10
  68. package/src/adapters/test-adapter.ts +10 -0
  69. package/src/analyzer-context.ts +35 -1
  70. package/src/analyzer.ts +162 -24
  71. package/src/compiler.ts +2 -2
  72. package/src/expression-parser.ts +27 -19
  73. package/src/ir-to-client-js/build-references.ts +19 -2
  74. package/src/ir-to-client-js/collect-elements.ts +69 -13
  75. package/src/ir-to-client-js/control-flow/plan/branch-loop.ts +14 -1
  76. package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +18 -5
  77. package/src/ir-to-client-js/control-flow/plan/build-component-loop.ts +4 -0
  78. package/src/ir-to-client-js/control-flow/plan/build-composite-loop.ts +15 -2
  79. package/src/ir-to-client-js/control-flow/plan/build-event-delegation.ts +32 -3
  80. package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +26 -3
  81. package/src/ir-to-client-js/control-flow/plan/build-loop.ts +53 -2
  82. package/src/ir-to-client-js/control-flow/plan/event-delegation.ts +16 -1
  83. package/src/ir-to-client-js/control-flow/plan/loop.ts +40 -0
  84. package/src/ir-to-client-js/control-flow/shared.ts +28 -2
  85. package/src/ir-to-client-js/control-flow/stringify/branch-loop.ts +25 -3
  86. package/src/ir-to-client-js/control-flow/stringify/event-delegation.ts +76 -21
  87. package/src/ir-to-client-js/control-flow/stringify/loop.ts +65 -1
  88. package/src/ir-to-client-js/control-flow.ts +11 -0
  89. package/src/ir-to-client-js/html-template.ts +244 -39
  90. package/src/ir-to-client-js/imports.ts +1 -1
  91. package/src/ir-to-client-js/plan/build-static-array-child-init.ts +22 -10
  92. package/src/ir-to-client-js/reactivity.ts +6 -0
  93. package/src/ir-to-client-js/types.ts +28 -3
  94. package/src/jsx-to-ir.ts +1059 -167
  95. package/src/loop-destructure.ts +9 -5
  96. package/src/rich-type-refusal.ts +6 -2
  97. package/src/strip-types.ts +47 -0
  98. package/src/types.ts +159 -35
package/dist/index.js CHANGED
@@ -250,6 +250,7 @@ var UNSUPPORTED_METHODS = new Set([
250
250
  "some",
251
251
  "forEach",
252
252
  "flatMap",
253
+ "fill",
253
254
  "charAt",
254
255
  "charCodeAt",
255
256
  "codePointAt",
@@ -3251,7 +3252,7 @@ function templateAttrExpr(attrName, valExpr, presenceOrUndefined) {
3251
3252
  return `\${((v) => v != null ? 'style="' + ${escapeAttrValueExpr("v")} + '"' : '')(styleToCss(${valExpr}))}`;
3252
3253
  }
3253
3254
  if (attrName === "data-key" || attrName.startsWith("data-key-")) {
3254
- return `${attrName}="\${${valExpr}}"`;
3255
+ return `${attrName}="\${${escapeAttrValueExpr(valExpr)}}"`;
3255
3256
  }
3256
3257
  return `\${(${valExpr}) != null ? '${attrName}="' + ${escapeAttrValueExpr(valExpr)} + '"' : ''}`;
3257
3258
  }
@@ -3380,6 +3381,86 @@ function buildSpreadAttrsMergeCall(args) {
3380
3381
  function itemAnchorTemplate(keyExpr) {
3381
3382
  return `<!--${loopItemMarker("${" + keyExpr + "}")}-->`;
3382
3383
  }
3384
+ function renderPreamble(preamble, opts) {
3385
+ let out = "";
3386
+ for (const seg of preamble.segments) {
3387
+ if (seg.kind === "js") {
3388
+ const text = opts.textVariant === "template" ? seg.templateText ?? seg.text : seg.text;
3389
+ out += opts.transformJs ? opts.transformJs(text) : text;
3390
+ } else if (opts.rawLeaf) {
3391
+ out += opts.renderLeaf(escapeLeafTextExpressions(seg.ir));
3392
+ } else {
3393
+ out += "`" + opts.renderLeaf(escapeLeafTextExpressions(seg.ir)) + "`";
3394
+ }
3395
+ }
3396
+ return out;
3397
+ }
3398
+ function flatMapLeafKeyExpr(ir) {
3399
+ if (ir.type !== "element")
3400
+ return null;
3401
+ const keyAttr = ir.attrs.find((a) => a.name === "key");
3402
+ if (!keyAttr)
3403
+ return null;
3404
+ switch (keyAttr.value.kind) {
3405
+ case "expression":
3406
+ return `(${keyAttr.value.expr})`;
3407
+ case "literal":
3408
+ return JSON.stringify(keyAttr.value.value);
3409
+ case "template":
3410
+ return attrValueToString(keyAttr.value);
3411
+ default:
3412
+ return null;
3413
+ }
3414
+ }
3415
+ function stripLeafKeyAttr(ir) {
3416
+ if (ir.type !== "element")
3417
+ return ir;
3418
+ return { ...ir, attrs: ir.attrs.filter((a) => a.name !== "key") };
3419
+ }
3420
+ function renderFlatMapClientBody(cb, restSpreadNames) {
3421
+ return renderPreamble(cb, {
3422
+ textVariant: "client",
3423
+ rawLeaf: true,
3424
+ renderLeaf: (ir) => {
3425
+ const key = flatMapLeafKeyExpr(ir);
3426
+ const html = irToHtmlTemplate(stripLeafKeyAttr(ir), restSpreadNames, 1, undefined, undefined, true);
3427
+ return `({ k: ${key ?? "undefined"}, h: \`${html}\` })`;
3428
+ }
3429
+ });
3430
+ }
3431
+ function flatMapCallbackHasKeyedLeaf(cb) {
3432
+ return cb.segments.some((s) => s.kind === "jsx" && flatMapLeafKeyExpr(s.ir) !== null);
3433
+ }
3434
+ function renderFlatMapProjectionClientBody(inner, restSpreadNames) {
3435
+ const chained = applyLoopChain(inner);
3436
+ const params = inner.index ? `(${inner.param}, ${inner.index})` : `(${inner.param})`;
3437
+ const key = inner.key ? `(${inner.key})` : "undefined";
3438
+ const html = inner.children.map((c) => irToHtmlTemplate(escapeLeafTextExpressions(c), restSpreadNames, 1, undefined, undefined, true)).join("");
3439
+ return `${chained}.map(${params} => ({ k: ${key}, h: \`${html}\` }))`;
3440
+ }
3441
+ function escapeLeafTextExpressions(ir) {
3442
+ switch (ir.type) {
3443
+ case "element":
3444
+ return { ...ir, children: ir.children.map(escapeLeafTextExpressions) };
3445
+ case "fragment":
3446
+ return { ...ir, children: ir.children.map(escapeLeafTextExpressions) };
3447
+ case "expression": {
3448
+ if (ir.expr === "null" || ir.expr === "undefined")
3449
+ return ir;
3450
+ if (ir.slotId || ir.expr.trimStart().startsWith("escapeText("))
3451
+ return ir;
3452
+ return { ...ir, expr: `escapeText((${ir.expr}))`, templateExpr: ir.templateExpr ? `escapeText((${ir.templateExpr}))` : ir.templateExpr };
3453
+ }
3454
+ case "conditional":
3455
+ return {
3456
+ ...ir,
3457
+ whenTrue: escapeLeafTextExpressions(ir.whenTrue),
3458
+ whenFalse: ir.whenFalse ? escapeLeafTextExpressions(ir.whenFalse) : ir.whenFalse
3459
+ };
3460
+ default:
3461
+ return ir;
3462
+ }
3463
+ }
3383
3464
  function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, branchSlotsVar, insideLoop = false, inHoistedChildren = false) {
3384
3465
  const recurse = (n) => irToHtmlTemplate(n, restSpreadNames, loopDepth, loopParams, branchSlotsVar, insideLoop, inHoistedChildren);
3385
3466
  const wrapExpr = (expr) => wrapExprWithLoopParams(expr, loopParams);
@@ -3421,15 +3502,17 @@ function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, bran
3421
3502
  }
3422
3503
  case "text":
3423
3504
  return escapeHtml(node.value);
3424
- case "expression":
3505
+ case "expression": {
3425
3506
  if (node.expr === "null" || node.expr === "undefined")
3426
3507
  return "";
3508
+ const inner = wrapInterpolation(wrapExpr(node.expr));
3509
+ const valueExpr = node.joinArrayChild ? `Array.isArray(${inner}) ? ${inner}.join('') : (${inner} ?? '')` : inner;
3427
3510
  if (node.slotId) {
3428
- const inner = wrapInterpolation(wrapExpr(node.expr));
3429
- const slotted = branchSlotsVar ? inner : escapeTextSlotExpr(inner);
3511
+ const slotted = branchSlotsVar || node.joinArrayChild ? valueExpr : escapeTextSlotExpr(valueExpr);
3430
3512
  return `<!--bf:${node.slotId}-->\${${slotted}}<!--/-->`;
3431
3513
  }
3432
- return `\${${wrapInterpolation(wrapExpr(node.expr))}}`;
3514
+ return `\${${valueExpr}}`;
3515
+ }
3433
3516
  case "conditional": {
3434
3517
  const trueBranch = recurse(node.whenTrue);
3435
3518
  const falseBranch = recurse(node.whenFalse);
@@ -3488,14 +3571,13 @@ function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, bran
3488
3571
  const iterMethod = node.method ?? "map";
3489
3572
  let mapExpr;
3490
3573
  if (node.flatMapCallback) {
3491
- let body = node.flatMapCallback.templateBody ?? node.flatMapCallback.body;
3492
- for (const frag of node.flatMapCallback.fragments) {
3493
- const renderedIr = irToHtmlTemplate(frag.ir, restSpreadNames, loopDepth + 1, loopParams, branchSlotsVar, insideLoop);
3494
- body = body.replace(frag.placeholder, `\`${renderedIr}\``);
3495
- }
3574
+ const body = renderPreamble(node.flatMapCallback, {
3575
+ renderLeaf: (ir) => irToHtmlTemplate(stripLeafKeyAttr(ir), restSpreadNames, loopDepth + 1, loopParams, branchSlotsVar, insideLoop)
3576
+ });
3496
3577
  mapExpr = `\${${wrappedArray}.flatMap(${node.flatMapCallback.params} => ${body}).join('')}`;
3497
- } else if (node.mapPreamble) {
3498
- mapExpr = `\${${wrappedArray}.${iterMethod}(${callbackParam} => { ${node.mapPreamble} return \`${childTemplate}\` }).join('')}`;
3578
+ } else if (node.preamble) {
3579
+ const preamble = renderPreamble(node.preamble, { textVariant: "client", renderLeaf: (ir) => irToHtmlTemplate(ir, restSpreadNames, loopDepth + 1, loopParams, branchSlotsVar, insideLoop) });
3580
+ mapExpr = `\${${wrappedArray}.${iterMethod}(${callbackParam} => { ${preamble} return \`${childTemplate}\` }).join('')}`;
3499
3581
  } else {
3500
3582
  mapExpr = `\${${wrappedArray}.${iterMethod}(${callbackParam} => \`${childTemplate}\`).join('')}`;
3501
3583
  }
@@ -3743,13 +3825,16 @@ function irToPlaceholderTemplate(node, restSpreadNames, loopDepth = 0, loopParam
3743
3825
  }
3744
3826
  case "text":
3745
3827
  return escapeHtml(node.value);
3746
- case "expression":
3828
+ case "expression": {
3747
3829
  if (node.expr === "null" || node.expr === "undefined")
3748
3830
  return "";
3831
+ const wrapped = wrapExpr(node.expr);
3832
+ const value = node.joinArrayChild ? `Array.isArray(${wrapped}) ? ${wrapped}.join('') : (${wrapped} ?? '')` : wrapped;
3749
3833
  if (node.slotId) {
3750
- return `<!--bf:${node.slotId}-->\${${escapeTextSlotExpr(wrapExpr(node.expr))}}<!--/-->`;
3834
+ return `<!--bf:${node.slotId}-->\${${node.joinArrayChild ? value : escapeTextSlotExpr(wrapped)}}<!--/-->`;
3751
3835
  }
3752
- return `\${${wrapExpr(node.expr)}}`;
3836
+ return `\${${value}}`;
3837
+ }
3753
3838
  case "conditional": {
3754
3839
  const trueBranch = recurse(node.whenTrue);
3755
3840
  const falseBranch = recurse(node.whenFalse);
@@ -3776,14 +3861,13 @@ function irToPlaceholderTemplate(node, restSpreadNames, loopDepth = 0, loopParam
3776
3861
  const iterMethod = node.method ?? "map";
3777
3862
  let mapExpr;
3778
3863
  if (node.flatMapCallback) {
3779
- let body = node.flatMapCallback.templateBody ?? node.flatMapCallback.body;
3780
- for (const frag of node.flatMapCallback.fragments) {
3781
- const renderedIr = irToPlaceholderTemplate(frag.ir, restSpreadNames, loopDepth + 1, loopParams);
3782
- body = body.replace(frag.placeholder, `\`${renderedIr}\``);
3783
- }
3864
+ const body = renderPreamble(node.flatMapCallback, {
3865
+ renderLeaf: (ir) => irToPlaceholderTemplate(stripLeafKeyAttr(ir), restSpreadNames, loopDepth + 1, loopParams)
3866
+ });
3784
3867
  mapExpr = `\${${wrappedArray}.flatMap(${node.flatMapCallback.params} => ${body}).join('')}`;
3785
- } else if (node.mapPreamble) {
3786
- mapExpr = `\${${wrappedArray}.${iterMethod}(${callbackParam} => { ${node.mapPreamble} return \`${childTemplate}\` }).join('')}`;
3868
+ } else if (node.preamble) {
3869
+ const preamble = renderPreamble(node.preamble, { textVariant: "client", renderLeaf: (ir) => irToPlaceholderTemplate(ir, restSpreadNames, loopDepth + 1, loopParams) });
3870
+ mapExpr = `\${${wrappedArray}.${iterMethod}(${callbackParam} => { ${preamble} return \`${childTemplate}\` }).join('')}`;
3787
3871
  } else {
3788
3872
  mapExpr = `\${${wrappedArray}.${iterMethod}(${callbackParam} => \`${childTemplate}\`).join('')}`;
3789
3873
  }
@@ -3971,13 +4055,16 @@ function irToComponentTemplateWithOpts(node, opts) {
3971
4055
  }
3972
4056
  case "text":
3973
4057
  return escapeHtml(node.value);
3974
- case "expression":
4058
+ case "expression": {
3975
4059
  if (node.expr === "null" || node.expr === "undefined")
3976
4060
  return "";
4061
+ const wrapped = transformExpr(node.expr, node.templateExpr);
4062
+ const value = node.joinArrayChild ? `Array.isArray(${wrapped}) ? ${wrapped}.join('') : (${wrapped} ?? '')` : wrapped;
3977
4063
  if (node.slotId) {
3978
- return `<!--bf:${node.slotId}-->\${${escapeTextSlotExpr(transformExpr(node.expr, node.templateExpr))}}<!--/-->`;
4064
+ return `<!--bf:${node.slotId}-->\${${node.joinArrayChild ? value : escapeTextSlotExpr(wrapped)}}<!--/-->`;
3979
4065
  }
3980
- return `\${${transformExpr(node.expr, node.templateExpr)}}`;
4066
+ return `\${${value}}`;
4067
+ }
3981
4068
  case "conditional": {
3982
4069
  if (node.clientOnly && node.slotId) {
3983
4070
  return `<!--bf-cond-start:${node.slotId}--><!--bf-cond-end:${node.slotId}-->`;
@@ -4298,10 +4385,11 @@ function generateCsrTemplateWithOpts(node, opts) {
4298
4385
  {
4299
4386
  const transformed = transformExpr(node.expr, node.templateExpr);
4300
4387
  const expr = transformed === UNSAFE_TEMPLATE_EXPR ? "''" : transformed;
4388
+ const value = node.joinArrayChild ? `Array.isArray(${expr}) ? ${expr}.join('') : (${expr} ?? '')` : expr;
4301
4389
  if (node.slotId) {
4302
- return `<!--bf:${node.slotId}-->\${${escapeTextSlotExpr(expr)}}<!--/-->`;
4390
+ return `<!--bf:${node.slotId}-->\${${node.joinArrayChild ? value : escapeTextSlotExpr(expr)}}<!--/-->`;
4303
4391
  }
4304
- return `\${${expr}}`;
4392
+ return `\${${value}}`;
4305
4393
  }
4306
4394
  case "conditional": {
4307
4395
  if (node.clientOnly && node.slotId) {
@@ -4397,16 +4485,14 @@ function generateCsrTemplateWithOpts(node, opts) {
4397
4485
  const iterMethod = node.method ?? "map";
4398
4486
  let mapExpr;
4399
4487
  if (node.flatMapCallback) {
4400
- let body = node.flatMapCallback.templateBody ?? node.flatMapCallback.body;
4401
- for (const frag of node.flatMapCallback.fragments) {
4402
- const renderedIr = recurseInLoopBody(frag.ir);
4403
- body = body.replace(frag.placeholder, `\`${renderedIr}\``);
4404
- }
4405
- body = applyPropsRewrite(body, propsObjectName ?? null);
4488
+ const body = renderPreamble(node.flatMapCallback, {
4489
+ textVariant: "template",
4490
+ transformJs: (t) => applyPropsRewrite(t, propsObjectName ?? null),
4491
+ renderLeaf: (ir) => recurseInLoopBody(stripLeafKeyAttr(ir))
4492
+ });
4406
4493
  mapExpr = `\${${iterArrayExpr}.flatMap(${node.flatMapCallback.params} => ${body}).join('')}`;
4407
- } else if (node.mapPreamble) {
4408
- const rawPreamble = node.templateMapPreamble ?? node.mapPreamble;
4409
- const preamble = applyPropsRewrite(rawPreamble, propsObjectName ?? null);
4494
+ } else if (node.preamble) {
4495
+ const preamble = renderPreamble(node.preamble, { textVariant: "template", transformJs: (t) => applyPropsRewrite(t, propsObjectName ?? null), renderLeaf: (ir) => recurseInLoopBody(ir) });
4410
4496
  mapExpr = `\${${iterArrayExpr}.${iterMethod}(${callbackParam} => { ${preamble} return \`${childTemplate}\` }).join('')}`;
4411
4497
  } else {
4412
4498
  mapExpr = `\${${iterArrayExpr}.${iterMethod}(${callbackParam} => \`${childTemplate}\`).join('')}`;
@@ -4561,6 +4647,39 @@ function reconstructWithoutTypes(node, sourceFile, ranges) {
4561
4647
  }
4562
4648
  return result;
4563
4649
  }
4650
+ function reconstructAsSegments(node, sourceFile, ranges, markers) {
4651
+ const nodeStart = node.getStart(sourceFile);
4652
+ const nodeEnd = node.getEnd();
4653
+ const fullText = sourceFile.text;
4654
+ const edits = [];
4655
+ for (const r of ranges) {
4656
+ if (r.end <= nodeStart || r.start >= nodeEnd)
4657
+ continue;
4658
+ edits.push({ start: r.start, end: r.end, marker: null });
4659
+ }
4660
+ markers.forEach((m, i) => edits.push({ start: m.start, end: m.end, marker: i }));
4661
+ edits.sort((a, b) => a.start - b.start || b.end - a.end);
4662
+ const segments = [];
4663
+ let jsBuf = "";
4664
+ let pos = nodeStart;
4665
+ for (const edit of edits) {
4666
+ if (edit.start < pos)
4667
+ continue;
4668
+ jsBuf += fullText.slice(pos, edit.start);
4669
+ if (edit.marker !== null) {
4670
+ if (jsBuf)
4671
+ segments.push({ js: jsBuf });
4672
+ jsBuf = "";
4673
+ segments.push({ marker: edit.marker });
4674
+ }
4675
+ pos = edit.end;
4676
+ }
4677
+ if (pos < nodeEnd)
4678
+ jsBuf += fullText.slice(pos, nodeEnd);
4679
+ if (jsBuf)
4680
+ segments.push({ js: jsBuf });
4681
+ return segments;
4682
+ }
4564
4683
  function mergeRanges(ranges) {
4565
4684
  if (ranges.length === 0)
4566
4685
  return [];
@@ -4706,10 +4825,11 @@ function findAngleBracketAfter(lastTypeArg, fullText) {
4706
4825
  }
4707
4826
 
4708
4827
  // src/analyzer-context.ts
4709
- function createAnalyzerContext(sourceFile, filePath) {
4828
+ function createAnalyzerContext(sourceFile, filePath, acceptsCallbackBody) {
4710
4829
  return {
4711
4830
  sourceFile,
4712
4831
  filePath,
4832
+ acceptsCallbackBody,
4713
4833
  componentName: null,
4714
4834
  componentNode: null,
4715
4835
  hasDefaultExport: false,
@@ -4755,6 +4875,9 @@ function createAnalyzerContext(sourceFile, filePath) {
4755
4875
  } catch {
4756
4876
  ownSourceFile = undefined;
4757
4877
  }
4878
+ if (process.env.BF_ASSERT_NO_JSX_IN_GETJS === "1" && this.errors.length === 0 && nodeContainsJsx(node)) {
4879
+ throw new Error("getJS() called on a JSX-bearing node — raw JSX must never be spliced " + "into emitted output. Carry mixed content as structured segments " + "(MapCallbackPreamble / FlatMapCallback) instead.");
4880
+ }
4758
4881
  if (ownSourceFile && ownSourceFile !== sourceFile) {
4759
4882
  return node.getText(ownSourceFile);
4760
4883
  }
@@ -4762,6 +4885,11 @@ function createAnalyzerContext(sourceFile, filePath) {
4762
4885
  }
4763
4886
  };
4764
4887
  }
4888
+ function nodeContainsJsx(node) {
4889
+ if (ts7.isJsxElement(node) || ts7.isJsxSelfClosingElement(node) || ts7.isJsxFragment(node))
4890
+ return true;
4891
+ return ts7.forEachChild(node, nodeContainsJsx) ?? false;
4892
+ }
4765
4893
  function getSourceLocation(node, sourceFile, filePath) {
4766
4894
  const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
4767
4895
  const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
@@ -5564,7 +5692,7 @@ function createProgramForFile(source, filePath) {
5564
5692
  return null;
5565
5693
  }
5566
5694
  }
5567
- function analyzeComponent(source, filePath, targetComponentName, program) {
5695
+ function analyzeComponent(source, filePath, targetComponentName, program, acceptsCallbackBody) {
5568
5696
  incrementCounter("filesAnalyzed");
5569
5697
  const hadSharedProgram = program !== undefined;
5570
5698
  const prescan = prescanReactiveFactoriesInSource(source, filePath);
@@ -5597,7 +5725,7 @@ function analyzeComponent(source, filePath, targetComponentName, program) {
5597
5725
  checker = result.checker;
5598
5726
  }
5599
5727
  }
5600
- const ctx = createAnalyzerContext(sourceFile, filePath);
5728
+ const ctx = createAnalyzerContext(sourceFile, filePath, acceptsCallbackBody);
5601
5729
  ctx.checker = checker;
5602
5730
  ctx.reactiveFactories = prescan.factories;
5603
5731
  ctx.declinedReactiveFactories = prescan.declined;
@@ -6649,9 +6777,15 @@ function extractSingleJsxReturn(body) {
6649
6777
  return null;
6650
6778
  return jsxReturn;
6651
6779
  }
6652
- function extractMultiReturnJsxBranches(body) {
6780
+ function preambleUnsafe(preamble, branches, fallback) {
6781
+ if (preamble.length === 0)
6782
+ return false;
6783
+ return fallback === null || branches.some((b) => b.jsxReturn === null);
6784
+ }
6785
+ function extractMultiReturnJsxBranches(body, allowPreamble = false) {
6653
6786
  const branches = [];
6654
6787
  let fallback = null;
6788
+ const preamble = [];
6655
6789
  const stmts = body.statements;
6656
6790
  for (let i = 0;i < stmts.length; i++) {
6657
6791
  const stmt = stmts[i];
@@ -6681,7 +6815,9 @@ function extractMultiReturnJsxBranches(body) {
6681
6815
  }
6682
6816
  if (branches.length === 0)
6683
6817
  return null;
6684
- return { branches, fallback };
6818
+ if (preambleUnsafe(preamble, branches, fallback))
6819
+ return null;
6820
+ return { branches, fallback, preamble };
6685
6821
  }
6686
6822
  break;
6687
6823
  }
@@ -6696,23 +6832,38 @@ function extractMultiReturnJsxBranches(body) {
6696
6832
  const hasDefault = stmt.caseBlock.clauses.some((c) => ts8.isDefaultClause(c));
6697
6833
  if (!hasDefault)
6698
6834
  return null;
6835
+ let pendingCases = [];
6699
6836
  for (const clause of stmt.caseBlock.clauses) {
6837
+ if (ts8.isCaseClause(clause) && clause.statements.length === 0) {
6838
+ pendingCases.push(clause.expression);
6839
+ continue;
6840
+ }
6700
6841
  const jsxReturn = findJsxReturnInCaseClause(clause);
6701
6842
  const nullReturn = findNullReturnInCaseClause(clause);
6702
6843
  if (!jsxReturn && !nullReturn)
6703
6844
  return null;
6845
+ if (!caseClauseIsDirectReturn(clause))
6846
+ return null;
6704
6847
  if (ts8.isCaseClause(clause)) {
6705
6848
  branches.push({
6706
6849
  condition: clause.expression,
6707
- jsxReturn: jsxReturn ?? null
6850
+ jsxReturn: jsxReturn ?? null,
6851
+ extraCaseConditions: pendingCases.length > 0 ? pendingCases : undefined
6708
6852
  });
6853
+ pendingCases = [];
6709
6854
  } else {
6855
+ if (pendingCases.length > 0)
6856
+ return null;
6710
6857
  fallback = jsxReturn ?? null;
6711
6858
  }
6712
6859
  }
6860
+ if (pendingCases.length > 0)
6861
+ return null;
6713
6862
  if (branches.length === 0)
6714
6863
  return null;
6715
- return { branches, fallback, switchDiscriminant: stmt.expression };
6864
+ if (preambleUnsafe(preamble, branches, fallback))
6865
+ return null;
6866
+ return { branches, fallback, switchDiscriminant: stmt.expression, preamble };
6716
6867
  }
6717
6868
  if (ts8.isReturnStatement(stmt) && stmt.expression) {
6718
6869
  const expr = unwrapJsxTransparent(stmt.expression);
@@ -6723,13 +6874,22 @@ function extractMultiReturnJsxBranches(body) {
6723
6874
  }
6724
6875
  continue;
6725
6876
  }
6726
- if (ts8.isVariableStatement(stmt))
6877
+ if (ts8.isVariableStatement(stmt)) {
6878
+ const declFlags = stmt.declarationList.flags;
6879
+ const isConstOrLet = (declFlags & ts8.NodeFlags.Const) !== 0 || (declFlags & ts8.NodeFlags.Let) !== 0;
6880
+ if (allowPreamble && isConstOrLet && branches.length === 0 && fallback === null) {
6881
+ preamble.push(stmt);
6882
+ continue;
6883
+ }
6727
6884
  return null;
6885
+ }
6728
6886
  return null;
6729
6887
  }
6730
6888
  if (branches.length === 0)
6731
6889
  return null;
6732
- return { branches, fallback };
6890
+ if (preambleUnsafe(preamble, branches, fallback))
6891
+ return null;
6892
+ return { branches, fallback, preamble };
6733
6893
  }
6734
6894
  function isDirectReturnBlock(node) {
6735
6895
  if (ts8.isReturnStatement(node))
@@ -6739,9 +6899,9 @@ function isDirectReturnBlock(node) {
6739
6899
  for (const stmt of node.statements) {
6740
6900
  if (ts8.isReturnStatement(stmt)) {
6741
6901
  returnCount++;
6742
- } else if (ts8.isIfStatement(stmt) || ts8.isSwitchStatement(stmt) || ts8.isForStatement(stmt) || ts8.isForOfStatement(stmt) || ts8.isForInStatement(stmt) || ts8.isWhileStatement(stmt) || ts8.isDoStatement(stmt) || ts8.isTryStatement(stmt)) {
6743
- return false;
6902
+ continue;
6744
6903
  }
6904
+ return false;
6745
6905
  }
6746
6906
  return returnCount === 1;
6747
6907
  }
@@ -6772,6 +6932,24 @@ function findJsxReturnInCaseClause(clause) {
6772
6932
  }
6773
6933
  return null;
6774
6934
  }
6935
+ function caseClauseIsDirectReturn(clause) {
6936
+ let returnCount = 0;
6937
+ let seenReturn = false;
6938
+ for (const stmt of clause.statements) {
6939
+ if (ts8.isReturnStatement(stmt)) {
6940
+ returnCount++;
6941
+ seenReturn = true;
6942
+ continue;
6943
+ }
6944
+ if (ts8.isBreakStatement(stmt)) {
6945
+ if (!seenReturn)
6946
+ return false;
6947
+ continue;
6948
+ }
6949
+ return false;
6950
+ }
6951
+ return returnCount === 1;
6952
+ }
6775
6953
  function findNullReturnInCaseClause(clause) {
6776
6954
  for (const stmt of clause.statements) {
6777
6955
  if (ts8.isReturnStatement(stmt) && stmt.expression) {
@@ -8812,6 +8990,23 @@ var REACTIVE_BINDING_KINDS = new Set([
8812
8990
  function isReactiveOrigin(origin) {
8813
8991
  return origin.freeRefs?.some((r) => REACTIVE_BINDING_KINDS.has(r.kind)) ?? false;
8814
8992
  }
8993
+ function tsxSourceText(raw) {
8994
+ return raw;
8995
+ }
8996
+ function preambleAnalysisText(p) {
8997
+ let out = "";
8998
+ for (const seg of p.segments)
8999
+ if (seg.kind === "js")
9000
+ out += seg.text;
9001
+ return out;
9002
+ }
9003
+ function preambleAnalysisTemplateText(p) {
9004
+ let out = "";
9005
+ for (const seg of p.segments)
9006
+ if (seg.kind === "js")
9007
+ out += seg.templateText ?? seg.text;
9008
+ return out;
9009
+ }
8815
9010
  var AttrValueOf = {
8816
9011
  literal(value) {
8817
9012
  return { kind: "literal", value };
@@ -10051,8 +10246,13 @@ function attachParsedExpressions(node, analyzer, bound = EMPTY_BOUND) {
10051
10246
  for (const child of nested.children)
10052
10247
  attachParsedExpressions(child, analyzer, loopBound);
10053
10248
  }
10054
- for (const frag of node.flatMapCallback?.fragments ?? []) {
10055
- attachParsedExpressions(frag.ir, analyzer, loopBound);
10249
+ for (const seg of node.flatMapCallback?.segments ?? []) {
10250
+ if (seg.kind === "jsx")
10251
+ attachParsedExpressions(seg.ir, analyzer, loopBound);
10252
+ }
10253
+ for (const seg of node.preamble?.segments ?? []) {
10254
+ if (seg.kind === "jsx")
10255
+ attachParsedExpressions(seg.ir, analyzer, loopBound);
10056
10256
  }
10057
10257
  break;
10058
10258
  }
@@ -10687,74 +10887,78 @@ function transformMultiReturnJsxFunctionCall(callExpr, info, ctx) {
10687
10887
  ctx.analyzer.getJS = substitutedGetJS;
10688
10888
  try {
10689
10889
  const loc = getSourceLocation(callExpr, ctx.sourceFile, ctx.filePath);
10690
- const nullExpr = {
10691
- type: "expression",
10692
- expr: "null",
10693
- typeInfo: { kind: "primitive", raw: "null", primitive: "null" },
10694
- reactive: false,
10695
- slotId: null,
10696
- loc,
10697
- origin: { phase: "tick", scope: "template", effect: "pure", freeRefs: [] }
10698
- };
10699
- let result = info.fallback ? transformNode(info.fallback, ctx) ?? nullExpr : nullExpr;
10700
- for (let i = info.branches.length - 1;i >= 0; i--) {
10701
- const branch = info.branches[i];
10702
- let conditionText;
10703
- if (info.switchDiscriminant) {
10704
- const discText = substitutedGetJS(info.switchDiscriminant);
10705
- const caseText = substitutedGetJS(branch.condition);
10706
- conditionText = `${discText} === ${caseText}`;
10707
- } else {
10708
- conditionText = substitutedGetJS(branch.condition);
10709
- }
10710
- const env = makeBindingEnv(ctx);
10711
- const caseFreeRefs = resolveFreeRefs(branch.condition, env);
10712
- const discFreeRefs = info.switchDiscriminant ? resolveFreeRefs(info.switchDiscriminant, env) : [];
10713
- const conditionOrigin = {
10714
- phase: "tick",
10715
- scope: "template",
10716
- effect: "pure",
10717
- freeRefs: [...discFreeRefs, ...caseFreeRefs]
10718
- };
10719
- const reactive = isReactiveExpression(conditionText, ctx, branch.condition) || isReactiveOrigin(conditionOrigin);
10720
- const loopParamReactive = !reactive && referencesLoopParam(conditionText, ctx);
10721
- const callsReactive = exprCallsReactiveGetters(branch.condition, ctx) || (info.switchDiscriminant ? exprCallsReactiveGetters(info.switchDiscriminant, ctx) : false);
10722
- const hasCalls = exprHasFunctionCalls(branch.condition) || (info.switchDiscriminant ? exprHasFunctionCalls(info.switchDiscriminant) : false);
10723
- const needsSlot = reactive || loopParamReactive || callsReactive || hasCalls;
10724
- const slotId = needsSlot ? generateSlotId(ctx) : null;
10725
- const whenTrue = branch.jsxReturn ? transformNode(branch.jsxReturn, ctx) ?? nullExpr : nullExpr;
10726
- let templateCondition;
10727
- if (info.switchDiscriminant) {
10728
- const discRewritten = rewriteBarePropRefs2(substitutedGetJS(info.switchDiscriminant), info.switchDiscriminant, ctx);
10729
- const caseRewritten = rewriteBarePropRefs2(substitutedGetJS(branch.condition), branch.condition, ctx);
10730
- const discPart = discRewritten ?? substitutedGetJS(info.switchDiscriminant);
10731
- const casePart = caseRewritten ?? substitutedGetJS(branch.condition);
10732
- templateCondition = `${discPart} === ${casePart}`;
10733
- } else {
10734
- templateCondition = rewriteBarePropRefs2(conditionText, branch.condition, ctx);
10735
- }
10736
- const conditional = {
10737
- type: "conditional",
10738
- condition: conditionText,
10739
- templateCondition,
10740
- conditionType: null,
10741
- reactive,
10742
- whenTrue,
10743
- whenFalse: result,
10744
- slotId,
10745
- callsReactiveGetters: callsReactive || undefined,
10746
- hasFunctionCalls: hasCalls || undefined,
10747
- loc,
10748
- origin: conditionOrigin
10749
- };
10750
- result = conditional;
10751
- }
10752
- return result;
10890
+ return foldMultiReturnBranches(info, ctx, loc, substitutedGetJS);
10753
10891
  } finally {
10754
10892
  ctx.getJS = originalCtxGetJS;
10755
10893
  ctx.analyzer.getJS = originalAnalyzerGetJS;
10756
10894
  }
10757
10895
  }
10896
+ function foldMultiReturnBranches(info, ctx, loc, getText) {
10897
+ const nullExpr = {
10898
+ type: "expression",
10899
+ expr: "null",
10900
+ typeInfo: { kind: "primitive", raw: "null", primitive: "null" },
10901
+ reactive: false,
10902
+ slotId: null,
10903
+ loc,
10904
+ origin: { phase: "tick", scope: "template", effect: "pure", freeRefs: [] }
10905
+ };
10906
+ let result = info.fallback ? transformNode(info.fallback, ctx) ?? nullExpr : nullExpr;
10907
+ for (let i = info.branches.length - 1;i >= 0; i--) {
10908
+ const branch = info.branches[i];
10909
+ const caseConds = info.switchDiscriminant ? [branch.condition, ...branch.extraCaseConditions ?? []] : [branch.condition];
10910
+ let conditionText;
10911
+ if (info.switchDiscriminant) {
10912
+ const discText = getText(info.switchDiscriminant);
10913
+ conditionText = caseConds.map((c) => `(${discText}) === (${getText(c)})`).join(" || ");
10914
+ } else {
10915
+ conditionText = getText(branch.condition);
10916
+ }
10917
+ const env = makeBindingEnv(ctx);
10918
+ const caseFreeRefs = caseConds.flatMap((c) => resolveFreeRefs(c, env));
10919
+ const discFreeRefs = info.switchDiscriminant ? resolveFreeRefs(info.switchDiscriminant, env) : [];
10920
+ const conditionOrigin = {
10921
+ phase: "tick",
10922
+ scope: "template",
10923
+ effect: "pure",
10924
+ freeRefs: [...discFreeRefs, ...caseFreeRefs]
10925
+ };
10926
+ const reactive = isReactiveExpression(conditionText, ctx, branch.condition) || isReactiveOrigin(conditionOrigin);
10927
+ const loopParamReactive = !reactive && referencesLoopParam(conditionText, ctx);
10928
+ const callsReactive = caseConds.some((c) => exprCallsReactiveGetters(c, ctx)) || (info.switchDiscriminant ? exprCallsReactiveGetters(info.switchDiscriminant, ctx) : false);
10929
+ const hasCalls = caseConds.some((c) => exprHasFunctionCalls(c)) || (info.switchDiscriminant ? exprHasFunctionCalls(info.switchDiscriminant) : false);
10930
+ const needsSlot = reactive || loopParamReactive || callsReactive || hasCalls;
10931
+ const slotId = needsSlot ? generateSlotId(ctx) : null;
10932
+ const whenTrue = branch.jsxReturn ? transformNode(branch.jsxReturn, ctx) ?? nullExpr : nullExpr;
10933
+ let templateCondition;
10934
+ if (info.switchDiscriminant) {
10935
+ const discRewritten = rewriteBarePropRefs2(getText(info.switchDiscriminant), info.switchDiscriminant, ctx);
10936
+ const discPart = discRewritten ?? getText(info.switchDiscriminant);
10937
+ templateCondition = caseConds.map((c) => {
10938
+ const casePart = rewriteBarePropRefs2(getText(c), c, ctx) ?? getText(c);
10939
+ return `(${discPart}) === (${casePart})`;
10940
+ }).join(" || ");
10941
+ } else {
10942
+ templateCondition = rewriteBarePropRefs2(conditionText, branch.condition, ctx);
10943
+ }
10944
+ const conditional = {
10945
+ type: "conditional",
10946
+ condition: conditionText,
10947
+ templateCondition,
10948
+ conditionType: null,
10949
+ reactive,
10950
+ whenTrue,
10951
+ whenFalse: result,
10952
+ slotId,
10953
+ callsReactiveGetters: callsReactive || undefined,
10954
+ hasFunctionCalls: hasCalls || undefined,
10955
+ loc,
10956
+ origin: conditionOrigin
10957
+ };
10958
+ result = conditional;
10959
+ }
10960
+ return result;
10961
+ }
10758
10962
  function transformConditional(node, ctx) {
10759
10963
  const condition = ctx.getJS(node.condition);
10760
10964
  const conditionOrigin = {
@@ -11551,6 +11755,87 @@ function checkLoopKey(callback, ctx, isNested) {
11551
11755
  return;
11552
11756
  }
11553
11757
  }
11758
+ function flatMapProjectionCall(body) {
11759
+ let expr;
11760
+ if (ts11.isBlock(body)) {
11761
+ const real = body.statements;
11762
+ if (real.length !== 1 || !ts11.isReturnStatement(real[0]) || !real[0].expression)
11763
+ return null;
11764
+ expr = real[0].expression;
11765
+ } else {
11766
+ expr = body;
11767
+ }
11768
+ while (ts11.isParenthesizedExpression(expr))
11769
+ expr = expr.expression;
11770
+ if (!ts11.isCallExpression(expr))
11771
+ return null;
11772
+ if (!getMapLikeMethod(expr))
11773
+ return null;
11774
+ const cb = expr.arguments[0];
11775
+ if (!cb || !ts11.isArrowFunction(cb) && !ts11.isFunctionExpression(cb))
11776
+ return null;
11777
+ for (const p of cb.parameters) {
11778
+ if (!ts11.isIdentifier(p.name))
11779
+ return null;
11780
+ }
11781
+ let innerBody = cb.body;
11782
+ if (ts11.isBlock(innerBody)) {
11783
+ const ret = innerBody.statements.find((s) => ts11.isReturnStatement(s) && s.expression != null);
11784
+ if (innerBody.statements.length !== 1 || !ret?.expression)
11785
+ return null;
11786
+ innerBody = ret.expression;
11787
+ }
11788
+ while (ts11.isParenthesizedExpression(innerBody))
11789
+ innerBody = innerBody.expression;
11790
+ const isElementish = (n) => {
11791
+ let m = n;
11792
+ while (ts11.isParenthesizedExpression(m))
11793
+ m = m.expression;
11794
+ if (ts11.isJsxElement(m) || ts11.isJsxSelfClosingElement(m))
11795
+ return leafIsWirelessElement(m);
11796
+ if (ts11.isConditionalExpression(m))
11797
+ return isElementish(m.whenTrue) && isElementish(m.whenFalse);
11798
+ return false;
11799
+ };
11800
+ if (!isElementish(innerBody))
11801
+ return null;
11802
+ return expr;
11803
+ }
11804
+ function leafIsWirelessElement(el) {
11805
+ let ok = true;
11806
+ const visit2 = (n) => {
11807
+ if (!ok)
11808
+ return;
11809
+ if (ts11.isJsxOpeningElement(n) || ts11.isJsxSelfClosingElement(n)) {
11810
+ const tagNode = n.tagName;
11811
+ const isIntrinsic = ts11.isIdentifier(tagNode) ? !/^[A-Z]/.test(tagNode.text) : ts11.isJsxNamespacedName(tagNode);
11812
+ if (!isIntrinsic) {
11813
+ ok = false;
11814
+ return;
11815
+ }
11816
+ for (const attr of n.attributes.properties) {
11817
+ if (ts11.isJsxSpreadAttribute(attr)) {
11818
+ ok = false;
11819
+ return;
11820
+ }
11821
+ if (ts11.isJsxAttribute(attr)) {
11822
+ const name = attr.name.getText();
11823
+ if (/^on[A-Z]/.test(name)) {
11824
+ ok = false;
11825
+ return;
11826
+ }
11827
+ }
11828
+ }
11829
+ }
11830
+ if (ts11.isCallExpression(n) && getMapLikeMethod(n) && containsJsxInExpression(n)) {
11831
+ ok = false;
11832
+ return;
11833
+ }
11834
+ ts11.forEachChild(n, visit2);
11835
+ };
11836
+ visit2(el);
11837
+ return ok;
11838
+ }
11554
11839
  function loopBodyIsMultiRoot(children) {
11555
11840
  const real = children.filter((c) => !(c.type === "text" && typeof c.value === "string" && !c.value.trim()));
11556
11841
  if (real.length === 0)
@@ -11596,6 +11881,7 @@ function extractItemConditionalKey(cond) {
11596
11881
  }
11597
11882
  function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
11598
11883
  const isNested = ctx.loopParams.size > 0;
11884
+ const diagCountAtEntry = ctx.analyzer.errors.length;
11599
11885
  const depth = ctx.loopDepth;
11600
11886
  const propAccess = node.expression;
11601
11887
  const mapSource = propAccess.expression;
@@ -11605,9 +11891,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
11605
11891
  let filterPredicate;
11606
11892
  let sortComparator;
11607
11893
  let chainOrder;
11608
- let mapPreamble;
11609
- let templateMapPreamble;
11610
- let typedMapPreamble;
11894
+ let preamble;
11611
11895
  let iterationShape;
11612
11896
  let objectIteration;
11613
11897
  const setArray = (node2) => {
@@ -11637,7 +11921,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
11637
11921
  const innerFilter = isFilterCall(sortInfo.array);
11638
11922
  const sortExtraction = extractSortComparator(sortInfo.callback, sortInfo.method, ctx);
11639
11923
  if (isClientOnly || !sortExtraction.result) {
11640
- if (!isClientOnly && sortExtraction.unsupportedReason) {
11924
+ if (!isClientOnly && sortExtraction.unsupportedReason && !(ctx.analyzer.acceptsCallbackBody?.("sort") ?? false)) {
11641
11925
  ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(sortInfo.callback, ctx.sourceFile, ctx.filePath), {
11642
11926
  message: `Expression cannot be compiled to marked template: ${sortExtraction.unsupportedReason}`,
11643
11927
  suggestion: {
@@ -11652,7 +11936,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
11652
11936
  chainOrder = "filter-sort";
11653
11937
  const filterExtraction = extractFilterPredicate(innerFilter.callback, ctx);
11654
11938
  if (isClientOnly || !filterExtraction.result) {
11655
- if (!isClientOnly && filterExtraction.unsupportedReason) {
11939
+ if (!isClientOnly && filterExtraction.unsupportedReason && !(ctx.analyzer.acceptsCallbackBody?.("filter") ?? false)) {
11656
11940
  ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(innerFilter.callback, ctx.sourceFile, ctx.filePath), {
11657
11941
  message: `Expression cannot be compiled to marked template: ${filterExtraction.unsupportedReason}`,
11658
11942
  suggestion: {
@@ -11675,7 +11959,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
11675
11959
  const innerSort = isSortCall(filterInfo.array);
11676
11960
  const filterExtraction = extractFilterPredicate(filterInfo.callback, ctx);
11677
11961
  if (isClientOnly || !filterExtraction.result) {
11678
- if (!isClientOnly && filterExtraction.unsupportedReason) {
11962
+ if (!isClientOnly && filterExtraction.unsupportedReason && !(ctx.analyzer.acceptsCallbackBody?.("filter") ?? false)) {
11679
11963
  ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(filterInfo.callback, ctx.sourceFile, ctx.filePath), {
11680
11964
  message: `Expression cannot be compiled to marked template: ${filterExtraction.unsupportedReason}`,
11681
11965
  suggestion: {
@@ -11690,7 +11974,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
11690
11974
  chainOrder = "sort-filter";
11691
11975
  const sortExtraction = extractSortComparator(innerSort.callback, innerSort.method, ctx);
11692
11976
  if (isClientOnly || !sortExtraction.result) {
11693
- if (!isClientOnly && sortExtraction.unsupportedReason) {
11977
+ if (!isClientOnly && sortExtraction.unsupportedReason && !(ctx.analyzer.acceptsCallbackBody?.("sort") ?? false)) {
11694
11978
  ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(innerSort.callback, ctx.sourceFile, ctx.filePath), {
11695
11979
  message: `Expression cannot be compiled to marked template: ${sortExtraction.unsupportedReason}`,
11696
11980
  suggestion: {
@@ -11805,7 +12089,24 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
11805
12089
  } else if (method === "flatMap" && ts11.isArrayLiteralExpression(body)) {
11806
12090
  children = transformArrayLiteralChildren(body, ctx);
11807
12091
  } else if (ts11.isBlock(body)) {
11808
- const returnStmt = body.statements.find((s) => ts11.isReturnStatement(s) && s.expression != null);
12092
+ const multiReturn = method !== "flatMap" ? extractMultiReturnJsxBranches(body, true) : null;
12093
+ if (multiReturn && multiReturn.branches.length > 0) {
12094
+ const loc = getSourceLocation(body, ctx.sourceFile, ctx.filePath);
12095
+ children = [foldMultiReturnBranches(multiReturn, ctx, loc, ctx.getJS)];
12096
+ const pre = multiReturn.preamble ?? [];
12097
+ if (pre.length > 0) {
12098
+ preamble = preambleFromValueStatements(pre, ctx);
12099
+ if (!isClientOnly && !(ctx.analyzer.acceptsCallbackBody?.("map") ?? false)) {
12100
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, loc, {
12101
+ message: "A .map() callback body with a `const`/`let` preamble before its " + "branches cannot be lowered to a template: the loop-local binding " + "cannot be carried into a conditional branch on this backend.",
12102
+ suggestion: {
12103
+ message: "Add /* @client */ to evaluate this expression on the client only"
12104
+ }
12105
+ }));
12106
+ }
12107
+ }
12108
+ }
12109
+ const returnStmt = children.length === 0 ? body.statements.find((s) => ts11.isReturnStatement(s) && s.expression != null) : undefined;
11809
12110
  if (returnStmt && returnStmt.expression) {
11810
12111
  let returnExpr = returnStmt.expression;
11811
12112
  while (ts11.isParenthesizedExpression(returnExpr)) {
@@ -11817,41 +12118,84 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
11817
12118
  children = [transformed];
11818
12119
  }
11819
12120
  }
11820
- const preambleStmts = [];
11821
- const templatePreambleStmts = [];
11822
- const typedPreambleStmts = [];
11823
- let hasTypeDiff = false;
11824
- let hasTemplateDiff = false;
12121
+ let jsxPreambleStmt;
11825
12122
  for (const stmt of body.statements) {
11826
12123
  if (stmt === returnStmt)
11827
12124
  break;
11828
- const js = ctx.getJS(stmt);
11829
- const tjs = ctx.getTemplateJS(stmt);
11830
- const ts12 = stmt.getText(ctx.sourceFile);
11831
- preambleStmts.push(js.endsWith(";") ? js : js + ";");
11832
- templatePreambleStmts.push(tjs.endsWith(";") ? tjs : tjs + ";");
11833
- typedPreambleStmts.push(ts12.endsWith(";") ? ts12 : ts12 + ";");
11834
- if (js !== ts12)
11835
- hasTypeDiff = true;
11836
- if (js !== tjs)
11837
- hasTemplateDiff = true;
12125
+ if (containsJsxInExpression(stmt)) {
12126
+ jsxPreambleStmt = stmt;
12127
+ break;
12128
+ }
11838
12129
  }
11839
- if (preambleStmts.length > 0) {
11840
- mapPreamble = preambleStmts.join(" ");
11841
- if (hasTemplateDiff) {
11842
- templateMapPreamble = templatePreambleStmts.join(" ");
12130
+ if (jsxPreambleStmt) {
12131
+ const jsRuntime = isClientOnly || (ctx.analyzer.acceptsCallbackBody?.("map") ?? false);
12132
+ if (children.length === 0) {
12133
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(jsxPreambleStmt, ctx.sourceFile, ctx.filePath), {
12134
+ message: "A .map() callback that builds JSX in a statement before its " + "`return` must return a single JSX element that embeds the " + "built array — this return shape has no element root to host it.",
12135
+ suggestion: {
12136
+ message: "Wrap the result in one element root, e.g. `return <tr key={item.id}>{out}</tr>`."
12137
+ }
12138
+ }));
12139
+ } else if (!jsRuntime) {
12140
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(jsxPreambleStmt, ctx.sourceFile, ctx.filePath), {
12141
+ message: "A .map() callback that builds JSX in a statement before its " + "`return` (for example pushing elements into an array in a loop) " + "cannot be lowered to a template on this backend.",
12142
+ suggestion: {
12143
+ message: "Add /* @client */ to render this loop on the client only"
12144
+ }
12145
+ }));
12146
+ } else {
12147
+ const collected = buildPreambleSegments(body.statements, returnStmt, ctx);
12148
+ if (collected.refusalNode) {
12149
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(collected.refusalNode, ctx.sourceFile, ctx.filePath), {
12150
+ message: "A JSX element built in a .map() callback preamble cannot carry " + "event handlers, components, nested loops, or reactive expressions, " + "and cannot sit inside a template literal — the verbatim render " + "path builds it once, with no reactive wiring.",
12151
+ suggestion: {
12152
+ message: "Return the element directly (not through a preamble variable), " + "or add /* @client */ to render the loop on the client only."
12153
+ }
12154
+ }));
12155
+ } else {
12156
+ preamble = collected.preamble;
12157
+ }
12158
+ }
12159
+ } else {
12160
+ const valueStmts = [];
12161
+ for (const stmt of body.statements) {
12162
+ if (stmt === returnStmt)
12163
+ break;
12164
+ valueStmts.push(stmt);
11843
12165
  }
11844
- if (hasTypeDiff) {
11845
- typedMapPreamble = typedPreambleStmts.join(" ");
12166
+ if (valueStmts.length > 0) {
12167
+ preamble = preambleFromValueStatements(valueStmts, ctx);
11846
12168
  }
11847
12169
  }
11848
12170
  }
11849
- if (method === "flatMap" && children.length === 0) {
12171
+ if (method === "flatMap" && children.length === 0 && !flatMapProjectionCall(body)) {
11850
12172
  flatMapCallback = buildFlatMapCallback(callback, body, ctx);
11851
12173
  }
11852
12174
  } else {
11853
12175
  tryTransformRenderableBody(body);
11854
12176
  }
12177
+ if (method === "flatMap" && children.length === 0 && !flatMapCallback) {
12178
+ const projection = flatMapProjectionCall(body);
12179
+ if (projection) {
12180
+ const transformed = transformJsxExpression(projection, ctx, isClientOnly);
12181
+ if (transformed && transformed.type === "loop") {
12182
+ children = [transformed];
12183
+ }
12184
+ }
12185
+ }
12186
+ if (method === "flatMap" && children.length === 0 && !flatMapCallback && !ts11.isBlock(body)) {
12187
+ flatMapCallback = buildFlatMapCallback(callback, body, ctx);
12188
+ }
12189
+ if (flatMapCallback)
12190
+ preamble = undefined;
12191
+ if (flatMapCallback && !isClientOnly && !(ctx.analyzer.acceptsCallbackBody?.("flatMap") ?? false)) {
12192
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(body, ctx.sourceFile, ctx.filePath), {
12193
+ message: "A .flatMap() callback body with statements or a nested projection " + "cannot be lowered to a template on this backend.",
12194
+ suggestion: {
12195
+ message: "Add /* @client */ to render this loop on the client only"
12196
+ }
12197
+ }));
12198
+ }
11855
12199
  if (paramBindings) {
11856
12200
  for (const b of paramBindings)
11857
12201
  ctx.loopParams.delete(b.name);
@@ -11863,6 +12207,16 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
11863
12207
  ctx.loopDepth--;
11864
12208
  }
11865
12209
  if (children.length === 0 && !flatMapCallback) {
12210
+ const cb = node.arguments[0];
12211
+ const cbBody = cb && (ts11.isArrowFunction(cb) || ts11.isFunctionExpression(cb)) ? cb.body : undefined;
12212
+ if (cbBody && containsJsxInExpression(cbBody) && ctx.analyzer.errors.length === diagCountAtEntry) {
12213
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(cbBody, ctx.sourceFile, ctx.filePath), {
12214
+ message: `A .${method}() callback that builds JSX in this shape cannot be ` + "compiled — 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.",
12215
+ suggestion: {
12216
+ message: "Restructure the callback to return the JSX element directly " + "(or via a block body with a plain `return`)."
12217
+ }
12218
+ }));
12219
+ }
11866
12220
  return null;
11867
12221
  }
11868
12222
  if (ts11.isArrowFunction(node.arguments[0]) && children.length > 0) {
@@ -11871,6 +12225,31 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
11871
12225
  const itemConditional = children.length > 0 ? loopBodyItemConditional(children) : null;
11872
12226
  const bodyIsItemConditional = itemConditional !== null;
11873
12227
  const key = bodyIsItemConditional ? extractItemConditionalKey(itemConditional) : children.length > 0 ? extractLoopKey(children[0]) : null;
12228
+ const declaredNameSet = preamble && preamble.declaredNames.length > 0 ? new Set(preamble.declaredNames) : undefined;
12229
+ if (key && declaredNameSet) {
12230
+ const keyRefs = extractFreeIdentifiersFromText(key);
12231
+ const usesLocal = [...keyRefs].some((r) => declaredNameSet.has(r));
12232
+ if (usesLocal) {
12233
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(node, ctx.sourceFile, ctx.filePath), {
12234
+ message: "A .map() loop key must be derivable from the loop item — it is " + "evaluated before the callback body runs, so it cannot reference a " + "value computed in the callback preamble.",
12235
+ suggestion: {
12236
+ message: "Derive the key directly from the loop item (e.g. key={item.id})."
12237
+ }
12238
+ }));
12239
+ }
12240
+ }
12241
+ if (preamble && preamble.builderNames.length > 0) {
12242
+ flagArrayChildExpressions(children, new Set(preamble.builderNames));
12243
+ }
12244
+ if (preamble && preamble.builderNames.length > 0 && children.length === 1 && children[0].type === "component") {
12245
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(node, ctx.sourceFile, ctx.filePath), {
12246
+ message: "A .map() callback that builds JSX in its preamble cannot return a " + "component: the built elements would reach the component as raw HTML " + "strings on the client but as JSX elements at SSR (a silent divergence).",
12247
+ suggestion: {
12248
+ message: "Return a plain element root that embeds the array, or move the " + "building logic inside the component."
12249
+ }
12250
+ }));
12251
+ preamble = undefined;
12252
+ }
11874
12253
  let childComponent;
11875
12254
  if (children.length === 1 && children[0].type === "component") {
11876
12255
  const comp = children[0];
@@ -11890,6 +12269,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
11890
12269
  const hasCalls = exprHasFunctionCalls(arrayExpr);
11891
12270
  const isDirectPropArray = method !== "flatMap" && isArrayExprDirectPropRef(arrayExpr, ctx);
11892
12271
  const isStaticArray = !isSignalOrMemoArray(array, ctx) && !isDirectPropArray && !hasCalls && !objectIteration;
12272
+ const preambleRegions = preamble && !isStaticArray ? collectPreambleRegions(children, new Set(preamble.declaredNames), ctx) : undefined;
11893
12273
  const nestedComponents = collectNestedComponents(children).filter((c) => c.name !== childComponent?.name);
11894
12274
  return {
11895
12275
  type: "loop",
@@ -11919,11 +12299,10 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
11919
12299
  objectIteration,
11920
12300
  depth,
11921
12301
  clientOnly: isClientOnly || undefined,
11922
- mapPreamble,
11923
- templateMapPreamble,
12302
+ preamble,
12303
+ preambleRegions: preambleRegions && preambleRegions.length > 0 ? preambleRegions : undefined,
11924
12304
  paramType,
11925
12305
  indexType,
11926
- typedMapPreamble,
11927
12306
  paramBindings,
11928
12307
  arrayFreeIdentifiers: extractFreeIdentifiersFromNode(arrayExpr),
11929
12308
  flatMapCallback,
@@ -11959,47 +12338,281 @@ function containsJsx(node) {
11959
12338
  function buildFlatMapCallback(callback, body, ctx) {
11960
12339
  if (!containsJsx(body))
11961
12340
  return;
11962
- const fragments = [];
11963
- const sourceText = ctx.sourceFile.text;
11964
- const bodyStart = body.getStart(ctx.sourceFile);
11965
- const bodyEnd = body.getEnd();
11966
- const bodyText = sourceText.slice(bodyStart, bodyEnd);
11967
- const jsxNodes = [];
11968
- function collectJsx(n) {
12341
+ const leafSpans = [];
12342
+ const leafIrs = [];
12343
+ let refusalNode;
12344
+ const collectJsx = (n, underTemplate) => {
11969
12345
  if (ts11.isJsxElement(n) || ts11.isJsxSelfClosingElement(n) || ts11.isJsxFragment(n)) {
11970
- jsxNodes.push({
11971
- node: n,
11972
- start: n.getStart(ctx.sourceFile) - bodyStart,
11973
- end: n.getEnd() - bodyStart
11974
- });
12346
+ if (underTemplate)
12347
+ refusalNode ??= n;
12348
+ leafSpans.push({ start: n.getStart(ctx.sourceFile), end: n.getEnd() });
12349
+ const ir = transformNode(n, ctx);
12350
+ leafIrs.push(ir ?? { type: "text", value: "", loc: getSourceLocation(n, ctx.sourceFile, ctx.filePath) });
11975
12351
  return;
11976
12352
  }
11977
- n.forEachChild(collectJsx);
11978
- }
11979
- collectJsx(body);
11980
- if (jsxNodes.length === 0)
12353
+ const inTemplate = underTemplate || ts11.isTemplateExpression(n) || ts11.isTaggedTemplateExpression(n);
12354
+ n.forEachChild((c) => collectJsx(c, inTemplate));
12355
+ };
12356
+ collectJsx(body, false);
12357
+ if (leafSpans.length === 0)
11981
12358
  return;
11982
- let compiledBody = "";
11983
- let lastEnd = 0;
11984
- for (let i = 0;i < jsxNodes.length; i++) {
11985
- const { node, start, end } = jsxNodes[i];
11986
- const placeholder = `__BF_JSX_${i}__`;
11987
- compiledBody += bodyText.slice(lastEnd, start) + placeholder;
11988
- lastEnd = end;
11989
- const ir = transformNode(node, ctx);
11990
- fragments.push({
11991
- placeholder,
11992
- ir: ir ?? { type: "text", value: "", loc: getSourceLocation(node, ctx.sourceFile, ctx.filePath) }
11993
- });
12359
+ if (refusalNode) {
12360
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(refusalNode, ctx.sourceFile, ctx.filePath), {
12361
+ message: "A JSX element inside a template literal in a .flatMap() callback " + "body cannot be compiled.",
12362
+ suggestion: { message: "Build the element outside the template literal." }
12363
+ }));
12364
+ return;
12365
+ }
12366
+ for (const leafIr of leafIrs) {
12367
+ if (leafIr.type !== "element") {
12368
+ const loc = "loc" in leafIr && leafIr.loc ? leafIr.loc : getSourceLocation(body, ctx.sourceFile, ctx.filePath);
12369
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, loc, {
12370
+ message: "A JSX leaf produced by a .flatMap() callback must be a single " + "element — a fragment or non-element root cannot ride the keyed " + "descriptor path (each leaf hydrates and patches as one element).",
12371
+ suggestion: {
12372
+ message: "Wrap the leaf content in a single keyed element."
12373
+ }
12374
+ }));
12375
+ return;
12376
+ }
12377
+ if (flatMapLeafNeedsWiring(leafIr)) {
12378
+ const loc = "loc" in leafIr && leafIr.loc ? leafIr.loc : getSourceLocation(body, ctx.sourceFile, ctx.filePath);
12379
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, loc, {
12380
+ message: "A JSX element produced by a .flatMap() callback cannot carry " + "event handlers, components, nested loops, or spreads — the " + "leaf renders as a keyed HTML string with no per-element wiring.",
12381
+ suggestion: {
12382
+ message: "Restructure so the interactive element lives in a .map() body — " + "the descriptor path has no per-element wiring on any backend, " + "so /* @client */ does not lift this."
12383
+ }
12384
+ }));
12385
+ return;
12386
+ }
11994
12387
  }
11995
- compiledBody += bodyText.slice(lastEnd);
12388
+ const pieces = reconstructAsSegments(body, ctx.sourceFile, ctx.analyzer.typeExcludeRanges, leafSpans);
12389
+ const segments = pieces.map((piece) => {
12390
+ if ("marker" in piece)
12391
+ return { kind: "jsx", ir: leafIrs[piece.marker] };
12392
+ const tpl = rewriteBarePropRefs2(piece.js, body, ctx);
12393
+ return tpl !== undefined && tpl !== piece.js ? { kind: "js", text: piece.js, templateText: tpl } : { kind: "js", text: piece.js };
12394
+ });
11996
12395
  const paramsText = callback.parameters.map((p) => p.getText(ctx.sourceFile)).join(", ");
11997
12396
  return {
11998
12397
  params: `(${paramsText})`,
11999
- body: compiledBody,
12000
- templateBody: compiledBody,
12001
- rawBody: bodyText,
12002
- fragments
12398
+ segments,
12399
+ rawBody: tsxSourceText(body.getText(ctx.sourceFile))
12400
+ };
12401
+ }
12402
+ function flatMapLeafNeedsWiring(ir) {
12403
+ switch (ir.type) {
12404
+ case "component":
12405
+ case "loop":
12406
+ return true;
12407
+ case "element":
12408
+ if (ir.events.length > 0)
12409
+ return true;
12410
+ if (ir.attrs.some((a) => a.name.startsWith("...")))
12411
+ return true;
12412
+ return ir.children.some(flatMapLeafNeedsWiring);
12413
+ case "conditional":
12414
+ return flatMapLeafNeedsWiring(ir.whenTrue) || (ir.whenFalse ? flatMapLeafNeedsWiring(ir.whenFalse) : false);
12415
+ case "fragment":
12416
+ return ir.children.some(flatMapLeafNeedsWiring);
12417
+ default:
12418
+ return false;
12419
+ }
12420
+ }
12421
+ function preambleFragmentNeedsWiring(ir) {
12422
+ switch (ir.type) {
12423
+ case "component":
12424
+ case "loop":
12425
+ return true;
12426
+ case "element":
12427
+ if (ir.events.length > 0)
12428
+ return true;
12429
+ if (ir.attrs.some((a) => a.name.startsWith("...")))
12430
+ return true;
12431
+ return ir.children.some(preambleFragmentNeedsWiring);
12432
+ case "expression":
12433
+ return ir.reactive === true;
12434
+ case "conditional":
12435
+ return preambleFragmentNeedsWiring(ir.whenTrue) || (ir.whenFalse ? preambleFragmentNeedsWiring(ir.whenFalse) : false);
12436
+ case "fragment":
12437
+ return ir.children.some(preambleFragmentNeedsWiring);
12438
+ default:
12439
+ return false;
12440
+ }
12441
+ }
12442
+ function flagArrayChildExpressions(nodes, declared) {
12443
+ for (const node of nodes) {
12444
+ switch (node.type) {
12445
+ case "expression": {
12446
+ const name = node.expr.trim();
12447
+ const refs = extractFreeIdentifiersFromText(node.expr);
12448
+ if (refs.size === 1 && refs.has(name) && declared.has(name)) {
12449
+ node.joinArrayChild = true;
12450
+ }
12451
+ break;
12452
+ }
12453
+ case "element":
12454
+ case "fragment":
12455
+ flagArrayChildExpressions(node.children, declared);
12456
+ break;
12457
+ case "conditional":
12458
+ flagArrayChildExpressions([node.whenTrue, ...node.whenFalse ? [node.whenFalse] : []], declared);
12459
+ break;
12460
+ }
12461
+ }
12462
+ }
12463
+ function collectPreambleRegions(nodes, declared, ctx) {
12464
+ const regions = [];
12465
+ const visit2 = (list) => {
12466
+ for (const node of list) {
12467
+ switch (node.type) {
12468
+ case "expression": {
12469
+ const refs = extractFreeIdentifiersFromText(node.expr);
12470
+ const usesPreambleLocal = [...refs].some((r) => declared.has(r));
12471
+ if (usesPreambleLocal) {
12472
+ if (!node.slotId)
12473
+ node.slotId = generateSlotId(ctx);
12474
+ node.preambleRegion = true;
12475
+ node.reactive = true;
12476
+ regions.push({
12477
+ slotId: node.slotId,
12478
+ expr: node.expr,
12479
+ joinArrayChild: node.joinArrayChild || undefined
12480
+ });
12481
+ }
12482
+ break;
12483
+ }
12484
+ case "element":
12485
+ case "fragment":
12486
+ visit2(node.children);
12487
+ break;
12488
+ case "conditional":
12489
+ visit2([node.whenTrue, ...node.whenFalse ? [node.whenFalse] : []]);
12490
+ break;
12491
+ }
12492
+ }
12493
+ };
12494
+ visit2(nodes);
12495
+ return regions;
12496
+ }
12497
+ function collectBindingNames(name, out) {
12498
+ if (ts11.isIdentifier(name)) {
12499
+ out.add(name.text);
12500
+ return;
12501
+ }
12502
+ for (const el of name.elements) {
12503
+ if (ts11.isBindingElement(el))
12504
+ collectBindingNames(el.name, out);
12505
+ }
12506
+ }
12507
+ function collectPreambleDeclaredNames(stmt, out) {
12508
+ if (ts11.isVariableStatement(stmt)) {
12509
+ for (const decl of stmt.declarationList.declarations) {
12510
+ collectBindingNames(decl.name, out);
12511
+ }
12512
+ } else if (ts11.isFunctionDeclaration(stmt) && stmt.name) {
12513
+ out.add(stmt.name.text);
12514
+ }
12515
+ }
12516
+ function preambleFromValueStatements(statements, ctx) {
12517
+ const segments = [];
12518
+ const typedParts = [];
12519
+ const declared = new Set;
12520
+ for (const stmt of statements) {
12521
+ collectPreambleDeclaredNames(stmt, declared);
12522
+ const js0 = ctx.getJS(stmt);
12523
+ const tjs0 = ctx.getTemplateJS(stmt);
12524
+ const raw0 = stmt.getText(ctx.sourceFile);
12525
+ const js = (js0.endsWith(";") ? js0 : js0 + ";") + " ";
12526
+ const tjs = (tjs0.endsWith(";") ? tjs0 : tjs0 + ";") + " ";
12527
+ typedParts.push(raw0.endsWith(";") ? raw0 : raw0 + ";");
12528
+ segments.push(tjs !== js ? { kind: "js", text: js, templateText: tjs } : { kind: "js", text: js });
12529
+ }
12530
+ return {
12531
+ segments: trimPreambleSegments(segments),
12532
+ ssrText: tsxSourceText(typedParts.join(" ")),
12533
+ declaredNames: [...declared],
12534
+ builderNames: []
12535
+ };
12536
+ }
12537
+ function trimPreambleSegments(segments) {
12538
+ const last = segments[segments.length - 1];
12539
+ if (last?.kind === "js") {
12540
+ const text = last.text.trimEnd();
12541
+ const templateText = last.templateText?.trimEnd();
12542
+ segments[segments.length - 1] = templateText !== undefined ? { kind: "js", text, templateText } : { kind: "js", text };
12543
+ }
12544
+ return segments;
12545
+ }
12546
+ function buildPreambleSegments(statements, returnStmt, ctx) {
12547
+ const segments = [];
12548
+ const typedParts = [];
12549
+ const declared = new Set;
12550
+ const builders = new Set;
12551
+ let refusalNode;
12552
+ const recordBuilderTarget = (leaf, stmt) => {
12553
+ for (let n = leaf.parent;n && n !== stmt.parent; n = n.parent) {
12554
+ if (ts11.isCallExpression(n) && ts11.isPropertyAccessExpression(n.expression) && (n.expression.name.text === "push" || n.expression.name.text === "unshift") && ts11.isIdentifier(n.expression.expression)) {
12555
+ builders.add(n.expression.expression.text);
12556
+ return;
12557
+ }
12558
+ if (ts11.isVariableDeclaration(n) && ts11.isIdentifier(n.name)) {
12559
+ builders.add(n.name.text);
12560
+ return;
12561
+ }
12562
+ }
12563
+ };
12564
+ for (const stmt of statements) {
12565
+ if (stmt === returnStmt)
12566
+ break;
12567
+ collectPreambleDeclaredNames(stmt, declared);
12568
+ const leafSpans = [];
12569
+ const leafIrs = [];
12570
+ const collect = (n, underTemplate) => {
12571
+ if (ts11.isJsxElement(n) || ts11.isJsxSelfClosingElement(n) || ts11.isJsxFragment(n)) {
12572
+ if (underTemplate)
12573
+ refusalNode ??= n;
12574
+ recordBuilderTarget(n, stmt);
12575
+ leafSpans.push({ start: n.getStart(ctx.sourceFile), end: n.getEnd() });
12576
+ const ir = transformNode(n, ctx);
12577
+ if (ir && preambleFragmentNeedsWiring(ir))
12578
+ refusalNode ??= n;
12579
+ leafIrs.push(ir ?? { type: "text", value: "", loc: getSourceLocation(n, ctx.sourceFile, ctx.filePath) });
12580
+ return;
12581
+ }
12582
+ const inTemplate = underTemplate || ts11.isTemplateExpression(n) || ts11.isTaggedTemplateExpression(n);
12583
+ n.forEachChild((c) => collect(c, inTemplate));
12584
+ };
12585
+ collect(stmt, false);
12586
+ const raw0 = stmt.getText(ctx.sourceFile);
12587
+ typedParts.push(raw0.endsWith(";") ? raw0 : raw0 + ";");
12588
+ if (leafSpans.length === 0) {
12589
+ const js0 = ctx.getJS(stmt);
12590
+ const tjs0 = ctx.getTemplateJS(stmt);
12591
+ const js = (js0.endsWith(";") ? js0 : js0 + ";") + " ";
12592
+ const tjs = (tjs0.endsWith(";") ? tjs0 : tjs0 + ";") + " ";
12593
+ segments.push(tjs !== js ? { kind: "js", text: js, templateText: tjs } : { kind: "js", text: js });
12594
+ continue;
12595
+ }
12596
+ const pieces = reconstructAsSegments(stmt, ctx.sourceFile, ctx.analyzer.typeExcludeRanges, leafSpans);
12597
+ for (const piece of pieces) {
12598
+ if ("marker" in piece) {
12599
+ segments.push({ kind: "jsx", ir: leafIrs[piece.marker] });
12600
+ } else {
12601
+ const tpl = rewriteBarePropRefs2(piece.js, stmt, ctx);
12602
+ segments.push(tpl !== undefined && tpl !== piece.js ? { kind: "js", text: piece.js, templateText: tpl } : { kind: "js", text: piece.js });
12603
+ }
12604
+ }
12605
+ const sep2 = raw0.endsWith(";") ? " " : "; ";
12606
+ segments.push({ kind: "js", text: sep2 });
12607
+ }
12608
+ return {
12609
+ preamble: {
12610
+ segments: trimPreambleSegments(segments),
12611
+ ssrText: tsxSourceText(typedParts.join(" ")),
12612
+ declaredNames: [...declared],
12613
+ builderNames: [...builders]
12614
+ },
12615
+ refusalNode
12003
12616
  };
12004
12617
  }
12005
12618
  function collectNestedComponents(nodes) {
@@ -13258,6 +13871,8 @@ function collectLoopChildReactiveTexts(node, ctx, loopParam, loopParamBindings,
13258
13871
  expression: ({ node: n, scope: insideConditional }) => {
13259
13872
  if (!n.slotId)
13260
13873
  return;
13874
+ if (n.preambleRegion)
13875
+ return;
13261
13876
  const originFreeIds = freeIdsFromRefs(n.origin?.freeRefs);
13262
13877
  const expanded = expandConstantForReactivity(n.expr, ctx, originFreeIds);
13263
13878
  const reactive = classifyReactivity(expanded.expr, ctx, loopParam, loopParamBindings, expanded.freeIds).kind !== "none" || decideWrapFromAstFlags(n).wrap;
@@ -13642,7 +14257,7 @@ function collectInnerLoops(nodes, siblingOffsets, outerLoopParam, ctx, options)
13642
14257
  objectIteration: n.objectIteration,
13643
14258
  containerSlotId: scope.parentSlotId,
13644
14259
  template,
13645
- mapPreamble: n.mapPreamble,
14260
+ preamble: n.preamble,
13646
14261
  refsOuterParam: refsOuter,
13647
14262
  childComponents,
13648
14263
  insideConditional: !flat && scope.insideCond ? true : undefined,
@@ -13813,10 +14428,13 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
13813
14428
  loop: ({ node: l, scope: inCond }) => {
13814
14429
  if (!l.slotId || inCond)
13815
14430
  return;
14431
+ const projectionInner = l.method === "flatMap" && l.children.length === 1 && l.children[0].type === "loop" ? l.children[0] : undefined;
13816
14432
  const childHandlers = [];
13817
- const bindings = collectLoopChildBindings(l.children, ctx, siblingOffsets, l.param, l.paramBindings);
13818
- for (const child of l.children) {
13819
- childHandlers.push(...collectEventHandlersFromIR(child));
14433
+ const bindings = projectionInner ? emptyLoopChildBindings() : collectLoopChildBindings(l.children, ctx, siblingOffsets, l.param, l.paramBindings);
14434
+ if (!projectionInner) {
14435
+ for (const child of l.children) {
14436
+ childHandlers.push(...collectEventHandlersFromIR(child));
14437
+ }
13820
14438
  }
13821
14439
  if (l.childComponent) {
13822
14440
  for (const prop of l.childComponent.props) {
@@ -13827,7 +14445,7 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
13827
14445
  }
13828
14446
  }
13829
14447
  }
13830
- const { useElementReconciliation, innerLoops } = decideLoopRendering(l, siblingOffsets, ctx);
14448
+ const { useElementReconciliation, innerLoops } = projectionInner ? { useElementReconciliation: false, innerLoops: undefined } : decideLoopRendering(l, siblingOffsets, ctx);
13831
14449
  let template = "";
13832
14450
  let staticItemTemplate;
13833
14451
  let skeletonTemplate;
@@ -13837,7 +14455,7 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
13837
14455
  if (l.isStaticArray && l.children[0]) {
13838
14456
  staticItemTemplate = irToHtmlTemplate(l.children[0], buildRestSpreadNames(ctx), 0, undefined, undefined, true);
13839
14457
  }
13840
- } else if (l.children[0]) {
14458
+ } else if (l.children[0] && !projectionInner) {
13841
14459
  const loopParamSpec = [{ param: l.param, bindings: l.paramBindings }];
13842
14460
  template = useElementReconciliation ? irToPlaceholderTemplate(l.children[0], buildRestSpreadNames(ctx), 0, loopParamSpec) : irToHtmlTemplate(l.children[0], buildRestSpreadNames(ctx), 0, loopParamSpec);
13843
14461
  if (l.isStaticArray) {
@@ -13889,7 +14507,17 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
13889
14507
  raw: l.sortComparator.raw
13890
14508
  } : undefined,
13891
14509
  chainOrder: l.chainOrder,
13892
- mapPreamble: l.mapPreamble
14510
+ preamble: l.preamble,
14511
+ preambleRegions: l.preambleRegions,
14512
+ flatMapClient: projectionInner ? {
14513
+ params: l.index ? `(${l.param}, ${l.index})` : `(${l.param})`,
14514
+ body: renderFlatMapProjectionClientBody(projectionInner, buildRestSpreadNames(ctx)),
14515
+ keyed: projectionInner.key !== null
14516
+ } : l.flatMapCallback ? {
14517
+ params: l.flatMapCallback.params,
14518
+ body: renderFlatMapClientBody(l.flatMapCallback, buildRestSpreadNames(ctx)),
14519
+ keyed: flatMapCallbackHasKeyedLeaf(l.flatMapCallback)
14520
+ } : undefined
13893
14521
  });
13894
14522
  },
13895
14523
  component: ({ node: c, descend, descendJsxChildren }) => {
@@ -14046,15 +14674,18 @@ function collectBranchLoops(node, ctx, siblingOffsets) {
14046
14674
  const containerSlot = parentSlotId ?? n.slotId;
14047
14675
  if (!containerSlot)
14048
14676
  return;
14049
- const { useElementReconciliation, innerLoops: innerLoopsCollected } = decideLoopRendering(n, siblingOffsets, undefined);
14677
+ const projectionInner = n.method === "flatMap" && n.children.length === 1 && n.children[0].type === "loop" ? n.children[0] : undefined;
14678
+ const { useElementReconciliation, innerLoops: innerLoopsCollected } = projectionInner ? { useElementReconciliation: false, innerLoops: undefined } : decideLoopRendering(n, siblingOffsets, undefined);
14050
14679
  let childTemplate;
14051
14680
  const branchLoopParamSpec = [{ param: n.param, bindings: n.paramBindings }];
14052
- if (useElementReconciliation && n.children[0]) {
14681
+ if (projectionInner) {
14682
+ childTemplate = "";
14683
+ } else if (useElementReconciliation && n.children[0]) {
14053
14684
  childTemplate = irToPlaceholderTemplate(n.children[0], restNames, 0, branchLoopParamSpec);
14054
14685
  } else {
14055
14686
  childTemplate = n.children.map((c) => irToHtmlTemplate(c, undefined, 0, branchLoopParamSpec)).join("");
14056
14687
  }
14057
- const branchBindings = ctx ? collectLoopChildBindings(n.children, ctx, siblingOffsets, n.param, n.paramBindings) : emptyLoopChildBindings();
14688
+ const branchBindings = ctx && !projectionInner ? collectLoopChildBindings(n.children, ctx, siblingOffsets, n.param, n.paramBindings) : emptyLoopChildBindings();
14058
14689
  loops.push({
14059
14690
  kind: "branch",
14060
14691
  array: n.array,
@@ -14070,7 +14701,8 @@ function collectBranchLoops(node, ctx, siblingOffsets) {
14070
14701
  objectIteration: n.objectIteration,
14071
14702
  template: childTemplate,
14072
14703
  containerSlotId: containerSlot,
14073
- mapPreamble: n.mapPreamble ?? null,
14704
+ preamble: n.preamble,
14705
+ preambleRegions: n.preambleRegions,
14074
14706
  nestedComponents: useElementReconciliation ? n.nestedComponents : undefined,
14075
14707
  bindings: branchBindings,
14076
14708
  innerLoops: useElementReconciliation ? innerLoopsCollected : undefined,
@@ -14084,7 +14716,16 @@ function collectBranchLoops(node, ctx, siblingOffsets) {
14084
14716
  paramB: n.sortComparator.paramB,
14085
14717
  raw: n.sortComparator.raw
14086
14718
  } : undefined,
14087
- chainOrder: n.chainOrder
14719
+ chainOrder: n.chainOrder,
14720
+ flatMapClient: projectionInner ? {
14721
+ params: n.index ? `(${n.param}, ${n.index})` : `(${n.param})`,
14722
+ body: renderFlatMapProjectionClientBody(projectionInner, restNames),
14723
+ keyed: projectionInner.key !== null
14724
+ } : n.flatMapCallback ? {
14725
+ params: n.flatMapCallback.params,
14726
+ body: renderFlatMapClientBody(n.flatMapCallback, restNames),
14727
+ keyed: flatMapCallbackHasKeyedLeaf(n.flatMapCallback)
14728
+ } : undefined
14088
14729
  });
14089
14730
  }
14090
14731
  });
@@ -14346,8 +14987,8 @@ function buildReferencesGraph(ctx, irRoot) {
14346
14987
  addExprEdges(ROOT_SOURCE, elem.filterPredicate.raw, "template-closure");
14347
14988
  if (elem.sortComparator)
14348
14989
  addExprEdges(ROOT_SOURCE, elem.sortComparator.raw, "template-closure");
14349
- if (elem.mapPreamble)
14350
- addExprEdges(ROOT_SOURCE, elem.mapPreamble, "template-closure");
14990
+ if (elem.preamble)
14991
+ addExprEdges(ROOT_SOURCE, preambleAnalysisText(elem.preamble), "template-closure");
14351
14992
  for (const attr of elem.bindings.reactiveAttrs) {
14352
14993
  addExprEdges(ROOT_SOURCE, attr.expression, "template-closure");
14353
14994
  }
@@ -14457,8 +15098,15 @@ function buildReferencesGraph(ctx, irRoot) {
14457
15098
  addExprEdges(ROOT_SOURCE, l.filterPredicate.raw, "template-closure");
14458
15099
  if (l.sortComparator)
14459
15100
  addExprEdges(ROOT_SOURCE, l.sortComparator.raw, "template-closure");
14460
- if (l.mapPreamble)
14461
- addExprEdges(ROOT_SOURCE, l.mapPreamble, "template-closure");
15101
+ if (l.preamble)
15102
+ addExprEdges(ROOT_SOURCE, preambleAnalysisText(l.preamble), "template-closure");
15103
+ if (l.flatMapCallback) {
15104
+ addExprEdges(ROOT_SOURCE, preambleAnalysisText(l.flatMapCallback), "template-closure");
15105
+ for (const seg of l.flatMapCallback.segments) {
15106
+ if (seg.kind === "jsx")
15107
+ walkIR(seg.ir, null, visitor);
15108
+ }
15109
+ }
14462
15110
  descend();
14463
15111
  if (l.childComponent)
14464
15112
  walkChildComponent(l.childComponent);
@@ -14636,6 +15284,8 @@ var RUNTIME_IMPORT_CANDIDATES = [
14636
15284
  "getLoopNodes",
14637
15285
  "mapArray",
14638
15286
  "mapArrayAnchored",
15287
+ "patchLeaf",
15288
+ "patchSlotRange",
14639
15289
  "createDisposableEffect",
14640
15290
  "createComponent",
14641
15291
  "renderChild",
@@ -16567,7 +17217,7 @@ function nestedLoopReferencesIndex(inner, comps, events) {
16567
17217
  if (exprRefs(a.expression, a.freeIdentifiers))
16568
17218
  return true;
16569
17219
  }
16570
- if (inner.mapPreamble && extractFreeIdentifiersFromStatementText(inner.mapPreamble).has(index))
17220
+ if (inner.preamble && extractFreeIdentifiersFromStatementText(preambleAnalysisText(inner.preamble)).has(index))
16571
17221
  return true;
16572
17222
  if (inner.template && extractFreeIdentifiersFromTemplateText(inner.template).has(index))
16573
17223
  return true;
@@ -16638,6 +17288,15 @@ function destructureLoopParam(param, paramBindings) {
16638
17288
  }
16639
17289
  return { head: param, unwrap: "" };
16640
17290
  }
17291
+ function buildPreambleRegionPlans(regions, loopParam, loopParamBindings) {
17292
+ if (!regions || regions.length === 0)
17293
+ return [];
17294
+ return regions.map((r) => {
17295
+ const wrapped = wrapLoopParamAsAccessor(r.expr, loopParam, loopParamBindings);
17296
+ const valueExpr = r.joinArrayChild ? `Array.isArray(${wrapped}) ? ${wrapped}.join('') : (${wrapped} ?? '')` : `escapeText(${wrapped})`;
17297
+ return { slotId: r.slotId, valueExpr };
17298
+ });
17299
+ }
16641
17300
  function buildComponentPropsExpr2(comp, loopParam, loopParamBindings) {
16642
17301
  const wrap = loopParam ? (expr) => wrapLoopParamAsAccessor(expr, loopParam, loopParamBindings) : (expr) => expr;
16643
17302
  const entries = comp.props.map((p) => {
@@ -16722,6 +17381,11 @@ function emitComponentAndEventSetup(ls, indent, elVar, comps, events, loopParam,
16722
17381
  }
16723
17382
 
16724
17383
  // src/ir-to-client-js/plan/build-static-array-child-init.ts
17384
+ function staticPreludeStatements(preamble) {
17385
+ return preamble ? [renderPreamble(preamble, {
17386
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, undefined, undefined, true)
17387
+ })] : [];
17388
+ }
16725
17389
  function buildStaticArrayChildInitsPlan(ctx) {
16726
17390
  const plans = [];
16727
17391
  for (const elem of ctx.loopElements) {
@@ -16759,7 +17423,7 @@ function buildSingleCompPlan(elem, childComponent) {
16759
17423
  arrayExpr: elem.array,
16760
17424
  param: elem.param,
16761
17425
  indexParam: elem.index || "__idx",
16762
- outerPreludeStatements: elem.mapPreamble ? [elem.mapPreamble] : [],
17426
+ outerPreludeStatements: staticPreludeStatements(elem.preamble),
16763
17427
  propsExpr: buildStaticPropsExpr(props)
16764
17428
  };
16765
17429
  }
@@ -16774,7 +17438,7 @@ function buildOuterNestedPlan(elem, comp) {
16774
17438
  param: elem.param,
16775
17439
  indexParam,
16776
17440
  offsetExpr: buildLoopChildIndexExpr(indexParam, elem.offset),
16777
- outerPreludeStatements: elem.mapPreamble ? [elem.mapPreamble] : [],
17441
+ outerPreludeStatements: staticPreludeStatements(elem.preamble),
16778
17442
  propsExpr: buildStaticPropsExpr(comp.props)
16779
17443
  };
16780
17444
  }
@@ -16793,13 +17457,13 @@ function buildInnerLoopNestedPlan(elem, innerLoop, innerComps) {
16793
17457
  outerParam: elem.param,
16794
17458
  outerIndexParam,
16795
17459
  outerOffsetExpr: buildLoopChildIndexExpr(outerIndexParam, elem.offset),
16796
- outerPreludeStatements: elem.mapPreamble ? [elem.mapPreamble] : [],
17460
+ outerPreludeStatements: staticPreludeStatements(elem.preamble),
16797
17461
  innerContainerSlotId: innerLoop.containerSlotId ?? null,
16798
17462
  innerArrayExpr: innerLoop.array,
16799
17463
  innerParam: innerLoop.param,
16800
17464
  innerIndexParam,
16801
17465
  innerOffsetExpr: buildLoopChildIndexExpr(innerIndexParam, innerLoop.offset),
16802
- innerPreludeStatements: innerLoop.mapPreamble ? [innerLoop.mapPreamble] : [],
17466
+ innerPreludeStatements: staticPreludeStatements(innerLoop.preamble),
16803
17467
  depth: innerLoop.depth,
16804
17468
  comps
16805
17469
  };
@@ -16816,11 +17480,11 @@ function buildComponentRootedInnerLoopPlan(elem, innerLoop, innerComps) {
16816
17480
  outerArrayExpr: elem.array,
16817
17481
  outerParam: elem.param,
16818
17482
  outerIndexParam: elem.index,
16819
- outerPreludeStatements: elem.mapPreamble ? [elem.mapPreamble] : [],
17483
+ outerPreludeStatements: staticPreludeStatements(elem.preamble),
16820
17484
  innerArrayExpr: innerLoop.array,
16821
17485
  innerParam: innerLoop.param,
16822
17486
  innerIndexParam: innerLoop.index,
16823
- innerPreludeStatements: innerLoop.mapPreamble ? [innerLoop.mapPreamble] : [],
17487
+ innerPreludeStatements: staticPreludeStatements(innerLoop.preamble),
16824
17488
  depth: innerLoop.depth,
16825
17489
  comps
16826
17490
  };
@@ -17389,7 +18053,7 @@ function buildInnerLoopsPlan(args) {
17389
18053
  const containerExpr = inner.containerSlotId ? `qsa(${parentElVar}, '[bf="${inner.containerSlotId}"]')` : parentElVar;
17390
18054
  const refsParent = !!outerLoopParam && (inner.arrayFreeIdentifiers?.has(outerLoopParam) ?? false);
17391
18055
  const useReactive = refsParent && !!inner.template;
17392
- const emit = useReactive ? buildReactiveEmit(inner, level, wrapOuter, uidSuffix) : buildStaticEmit(inner, level, uidSuffix);
18056
+ const emit = useReactive ? buildReactiveEmit(inner, level, wrapOuter, uidSuffix, outerLoopParam, outerLoopParamBindings) : buildStaticEmit(inner, level, uidSuffix);
17393
18057
  const arrayExpr = useReactive ? wrapOuter(inner.array) : inner.array;
17394
18058
  const childLevelsPlan = childLevels.length > 0 ? buildInnerLoopsPlan({
17395
18059
  levels: childLevels,
@@ -17415,7 +18079,7 @@ function buildInnerLoopsPlan(args) {
17415
18079
  }
17416
18080
  return plan;
17417
18081
  }
17418
- function buildReactiveEmit(inner, level, wrapOuter, uidSuffix) {
18082
+ function buildReactiveEmit(inner, level, wrapOuter, uidSuffix, outerLoopParam, outerLoopParamBindings) {
17419
18083
  const wrapInner = (expr) => wrapLoopParamAsAccessor(expr, inner.param, inner.paramBindings);
17420
18084
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(inner.param, inner.paramBindings);
17421
18085
  const wrappedKey = inner.key ? wrapLoopParamAsAccessor(inner.key, inner.param, inner.paramBindings) : null;
@@ -17467,8 +18131,16 @@ function buildReactiveEmit(inner, level, wrapOuter, uidSuffix) {
17467
18131
  preludeStatements.push(indexAlias);
17468
18132
  if (paramUnwrap)
17469
18133
  preludeStatements.push(paramUnwrap);
17470
- if (inner.mapPreamble)
17471
- preludeStatements.push(wrapInner(wrapOuter(inner.mapPreamble)));
18134
+ if (inner.preamble) {
18135
+ const leafLoopParams = outerLoopParam ? [
18136
+ { param: outerLoopParam, bindings: outerLoopParamBindings },
18137
+ { param: inner.param, bindings: inner.paramBindings }
18138
+ ] : [{ param: inner.param, bindings: inner.paramBindings }];
18139
+ preludeStatements.push(renderPreamble(inner.preamble, {
18140
+ transformJs: (t) => wrapInner(wrapOuter(t)),
18141
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, leafLoopParams, undefined, true)
18142
+ }));
18143
+ }
17472
18144
  const childRefs = buildChildRefBindings(inner.bindings.refs, inner.param, inner.paramBindings);
17473
18145
  return {
17474
18146
  mode: "reactive",
@@ -17490,8 +18162,11 @@ function buildStaticEmit(inner, level, uidSuffix) {
17490
18162
  const indexAlias = nestedLoopIndexAlias(inner, `__innerIdx${uidSuffix}`, inner.param, level.comps, level.events);
17491
18163
  if (indexAlias)
17492
18164
  preludeStatements.push(indexAlias);
17493
- if (inner.mapPreamble)
17494
- preludeStatements.push(inner.mapPreamble);
18165
+ if (inner.preamble) {
18166
+ preludeStatements.push(renderPreamble(inner.preamble, {
18167
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, undefined, undefined, true)
18168
+ }));
18169
+ }
17495
18170
  return {
17496
18171
  mode: "static",
17497
18172
  rawKey: inner.key ?? null,
@@ -17511,6 +18186,7 @@ function buildTopLevelCompositePlan(elem, profileComponentName) {
17511
18186
  const outerCompsByDepth = nestedComps.filter((c) => !c.loopDepth || c.loopDepth === 0);
17512
18187
  return {
17513
18188
  kind: "composite",
18189
+ rowConstruction: "string-template",
17514
18190
  containerVar: `_${varSlotId(elem.slotId)}`,
17515
18191
  markerId: elem.markerId,
17516
18192
  arrayExpr: buildChainedArrayExpr(elem),
@@ -17518,7 +18194,10 @@ function buildTopLevelCompositePlan(elem, profileComponentName) {
17518
18194
  paramHead,
17519
18195
  paramUnwrap,
17520
18196
  indexParam: elem.index || "__idx",
17521
- mapPreambleWrapped: elem.mapPreamble ? wrap(elem.mapPreamble) : "",
18197
+ mapPreambleWrapped: elem.preamble ? renderPreamble(elem.preamble, {
18198
+ transformJs: wrap,
18199
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: elem.param, bindings: elem.paramBindings }], undefined, true)
18200
+ }) : "",
17522
18201
  template: elem.template,
17523
18202
  outerComps: filterCondCompsOut(outerCompsByDepth, elem.bindings.conditionals),
17524
18203
  outerEvents: elem.bindings.events.filter((ev) => ev.nestedLoops.length === 0),
@@ -17557,6 +18236,7 @@ function buildBranchCompositePlan(loop, cv, profileComponentName) {
17557
18236
  const outerCompsByDepth = nestedComps.filter((c) => !c.loopDepth || c.loopDepth === 0);
17558
18237
  return {
17559
18238
  kind: "composite",
18239
+ rowConstruction: "string-template",
17560
18240
  containerVar: `__loop_${cv}`,
17561
18241
  markerId: loop.markerId,
17562
18242
  arrayExpr: buildChainedArrayExpr(loop),
@@ -17564,7 +18244,10 @@ function buildBranchCompositePlan(loop, cv, profileComponentName) {
17564
18244
  paramHead,
17565
18245
  paramUnwrap,
17566
18246
  indexParam: loop.index || "__idx",
17567
- mapPreambleWrapped: loop.mapPreamble ? wrap(loop.mapPreamble) : "",
18247
+ mapPreambleWrapped: loop.preamble ? renderPreamble(loop.preamble, {
18248
+ transformJs: wrap,
18249
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: loop.param, bindings: loop.paramBindings }], undefined, true)
18250
+ }) : "",
17568
18251
  template: loop.template,
17569
18252
  outerComps: filterCondCompsOut(outerCompsByDepth, loop.bindings.conditionals),
17570
18253
  outerEvents: childEvents.filter((ev) => ev.nestedLoops.length === 0),
@@ -17627,7 +18310,10 @@ function buildDynamicLoopDelegationPlan(elem, profileComponentName) {
17627
18310
  paramBindings: elem.paramBindings,
17628
18311
  key: elem.key,
17629
18312
  index: elem.index,
17630
- mapPreamble: elem.mapPreamble ?? null
18313
+ mapPreamble: elem.preamble ? renderPreamble(elem.preamble, {
18314
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, undefined, undefined, true)
18315
+ }) : null,
18316
+ mapPreambleDeclaredNames: elem.preamble?.declaredNames ?? []
17631
18317
  })
17632
18318
  };
17633
18319
  }
@@ -17643,7 +18329,10 @@ function buildBranchLoopDelegationPlan(loop, cv, profileComponentName) {
17643
18329
  paramBindings: loop.paramBindings,
17644
18330
  key: loop.key,
17645
18331
  index: loop.index,
17646
- mapPreamble: loop.mapPreamble ?? null
18332
+ mapPreamble: loop.preamble ? renderPreamble(loop.preamble, {
18333
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, undefined, undefined, true)
18334
+ }) : null,
18335
+ mapPreambleDeclaredNames: loop.preamble?.declaredNames ?? []
17647
18336
  })
17648
18337
  };
17649
18338
  }
@@ -17657,7 +18346,10 @@ function buildStaticArrayDelegationPlan(elem, profileComponentName) {
17657
18346
  kind: "static-index",
17658
18347
  arrayExpr: buildChainedArrayExpr(elem),
17659
18348
  param: elem.param,
17660
- mapPreamble: elem.mapPreamble ?? null,
18349
+ mapPreamble: elem.preamble ? renderPreamble(elem.preamble, {
18350
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, undefined, undefined, true)
18351
+ }) : null,
18352
+ mapPreambleDeclaredNames: elem.preamble?.declaredNames ?? [],
17661
18353
  offset: elem.offset ?? null,
17662
18354
  indexParam: elem.index ?? null
17663
18355
  }
@@ -17674,6 +18366,7 @@ function buildKeyedOrIndexLookup(args) {
17674
18366
  paramBindings: args.paramBindings,
17675
18367
  keyWithItem,
17676
18368
  mapPreamble: args.mapPreamble,
18369
+ mapPreambleDeclaredNames: args.mapPreambleDeclaredNames,
17677
18370
  hasBindings,
17678
18371
  indexParam: args.index
17679
18372
  };
@@ -17683,6 +18376,7 @@ function buildKeyedOrIndexLookup(args) {
17683
18376
  arrayExpr: args.array,
17684
18377
  param: args.param,
17685
18378
  mapPreamble: args.mapPreamble,
18379
+ mapPreambleDeclaredNames: args.mapPreambleDeclaredNames,
17686
18380
  hasBindings,
17687
18381
  indexParam: args.index
17688
18382
  };
@@ -17704,17 +18398,23 @@ function buildBranchLoopPlan(loop, profileComponentName) {
17704
18398
  }
17705
18399
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(loop.param, loop.paramBindings);
17706
18400
  const hasReactiveEffects = loop.bindings.reactiveAttrs.length > 0 || loop.bindings.reactiveTexts.length > 0 || loop.bindings.conditionals.length > 0;
18401
+ const fm = loop.flatMapClient;
17707
18402
  const plan = {
17708
18403
  kind: "plain",
18404
+ rowConstruction: "string-template",
17709
18405
  containerSlotId,
17710
18406
  containerVar,
17711
18407
  markerId: loop.markerId,
17712
- arrayExpr: buildChainedArrayExpr(loop),
17713
- keyFn: loopKeyFn(loop),
18408
+ flatMapLeafItem: fm ? true : undefined,
18409
+ arrayExpr: fm ? `(${buildChainedArrayExpr(loop)}).flatMap(${fm.params} => ${fm.body})` : buildChainedArrayExpr(loop),
18410
+ keyFn: fm ? fm.keyed ? "(__bfD, __bfI) => String(__bfD.k ?? __bfI)" : "null" : loopKeyFn(loop),
17714
18411
  paramHead,
17715
18412
  paramUnwrap,
17716
18413
  indexParam: loop.index || "__idx",
17717
- mapPreambleWrapped: loop.mapPreamble ? wrapLoopParamAsAccessor(loop.mapPreamble, loop.param, loop.paramBindings) : "",
18414
+ mapPreambleWrapped: loop.preamble ? renderPreamble(loop.preamble, {
18415
+ transformJs: (t) => wrapLoopParamAsAccessor(t, loop.param, loop.paramBindings),
18416
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: loop.param, bindings: loop.paramBindings }], undefined, true)
18417
+ }) : "",
17718
18418
  template: loop.template,
17719
18419
  reactiveEffects: hasReactiveEffects ? buildReactiveEffectsPlan({
17720
18420
  attrs: loop.bindings.reactiveAttrs,
@@ -17726,6 +18426,7 @@ function buildBranchLoopPlan(loop, profileComponentName) {
17726
18426
  }) : null,
17727
18427
  eventDelegation: buildBranchLoopDelegationPlan(loop, cv, profileComponentName),
17728
18428
  childRefs: buildChildRefBindings(loop.bindings.refs, loop.param, loop.paramBindings),
18429
+ preambleRegions: buildPreambleRegionPlans(loop.preambleRegions, loop.param, loop.paramBindings),
17729
18430
  bodyIsMultiRoot: loop.bodyIsMultiRoot ?? false,
17730
18431
  profileLoopId: profileComponentName ? `${profileComponentName}#binding:${containerSlotId}` : undefined
17731
18432
  };
@@ -18373,6 +19074,22 @@ function emitLoopChildRefs(lines, refs, opts) {
18373
19074
  lines.push(`${indent}if (${varName}) ${emitRefCall(ref.callback, varName)} }`);
18374
19075
  }
18375
19076
  }
19077
+ function emitPreambleRegionEffects(lines, regions, mapPreambleWrapped, opts) {
19078
+ if (regions.length === 0)
19079
+ return;
19080
+ const { indent, elVar } = opts;
19081
+ for (const region of regions) {
19082
+ const v = varSlotId(region.slotId);
19083
+ lines.push(`${indent}{ let __last_${v}`);
19084
+ lines.push(`${indent}createEffect(() => {`);
19085
+ if (mapPreambleWrapped)
19086
+ lines.push(`${indent} ${mapPreambleWrapped}`);
19087
+ lines.push(`${indent} const __html_${v} = ${region.valueExpr}`);
19088
+ lines.push(`${indent} if (__last_${v} === undefined) { __last_${v} = __html_${v}; return }`);
19089
+ lines.push(`${indent} if (__html_${v} !== __last_${v}) { __last_${v} = __html_${v}; patchSlotRange(${elVar}, '${region.slotId}', __html_${v}) }`);
19090
+ lines.push(`${indent}}) }`);
19091
+ }
19092
+ }
18376
19093
  function stringifyLoop(lines, plan) {
18377
19094
  switch (plan.kind) {
18378
19095
  case "static":
@@ -18414,8 +19131,24 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
18414
19131
  childRefs,
18415
19132
  bodyIsMultiRoot,
18416
19133
  anchored,
18417
- anchorKeyExpr
19134
+ anchorKeyExpr,
19135
+ preambleRegions
18418
19136
  } = plan;
19137
+ if (plan.flatMapLeafItem) {
19138
+ const loopBfIdArg = plan.profileLoopId ? `, ${JSON.stringify(plan.profileLoopId)}` : "";
19139
+ lines.push(`${topIndent}mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (__bfD, ${indexParam}, __existing) => {`);
19140
+ lines.push(`${topIndent} let __el = __existing`);
19141
+ lines.push(`${topIndent} if (!__el) { const __tpl = document.createElement('template'); __tpl.innerHTML = __bfD().h; __el = __tpl.content.firstElementChild }`);
19142
+ lines.push(`${topIndent} let __last = __existing ? undefined : __bfD().h`);
19143
+ lines.push(`${topIndent} createEffect(() => {`);
19144
+ lines.push(`${topIndent} const __html = __bfD().h`);
19145
+ lines.push(`${topIndent} if (__last === undefined) { __last = __html; return }`);
19146
+ lines.push(`${topIndent} if (__html !== __last) { __last = __html; patchLeaf(__el, __html) }`);
19147
+ lines.push(`${topIndent} })`);
19148
+ lines.push(`${topIndent} return __el`);
19149
+ lines.push(`${topIndent}}, '${markerId}'${loopBfIdArg})`);
19150
+ return;
19151
+ }
18419
19152
  if (anchored) {
18420
19153
  stringifyAnchoredLoop(lines, plan, topIndent, anchorKeyExpr);
18421
19154
  return;
@@ -18426,7 +19159,7 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
18426
19159
  emitHoistedTemplateDecl(lines, topIndent, tplVar, hoistedTpl);
18427
19160
  }
18428
19161
  const loopBfId = plan.profileLoopId ? `, ${JSON.stringify(plan.profileLoopId)}` : "";
18429
- if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0) {
19162
+ if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0 && preambleRegions.length === 0) {
18430
19163
  const unwrapInline = paramUnwrap ? `${paramUnwrap} ` : "";
18431
19164
  const preamble = mapPreambleWrapped ? `${mapPreambleWrapped}; ` : "";
18432
19165
  const cloneExpr = hoistedTpl ? `return ${hoistedCloneExpr(tplVar, hoistedTpl)}` : emitTemplateCloneInline(template);
@@ -18474,6 +19207,7 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
18474
19207
  });
18475
19208
  }
18476
19209
  emitLoopChildRefs(lines, childRefs, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot, elementIndexBySlot: pathPlan?.elementIndexBySlot });
19210
+ emitPreambleRegionEffects(lines, preambleRegions, mapPreambleWrapped, { indent: bodyIndent, elVar: "__el" });
18477
19211
  lines.push(`${bodyIndent}return __el`);
18478
19212
  lines.push(`${topIndent}}, '${markerId}'${loopBfId})`);
18479
19213
  }
@@ -18784,6 +19518,12 @@ function indexBindingLine(handler, indexParam, indexExpr) {
18784
19518
  return null;
18785
19519
  return `const ${indexParam} = ${indexExpr}`;
18786
19520
  }
19521
+ function preambleLineForHandler(mapPreamble, declaredNames, handler) {
19522
+ if (!mapPreamble || declaredNames.length === 0)
19523
+ return null;
19524
+ const free = extractFreeIdentifiersFromText(handler);
19525
+ return declaredNames.some((name) => free.has(name)) ? mapPreamble : null;
19526
+ }
18787
19527
  function stringifyEventDelegation(lines, plan) {
18788
19528
  const { containerVar, events, itemLookup, profileComponentName } = plan;
18789
19529
  const eventsByName = new Map;
@@ -18804,7 +19544,7 @@ function stringifyEventDelegation(lines, plan) {
18804
19544
  for (const ev of evs) {
18805
19545
  const childVar = varSlotId(ev.childSlotId);
18806
19546
  lines.push(` const ${childVar}El = target.closest('[bf="${ev.childSlotId}"]')`);
18807
- lines.push(` if (${childVar}El) {`);
19547
+ lines.push(` if (${childVar}El && ${containerVar}.contains(${childVar}El)) {`);
18808
19548
  const handlerCall = withTurn(`(${ev.handler.trim()})(__bfEvt)`, profileComponentName, ev.childSlotId, ev.eventName);
18809
19549
  switch (itemLookup.kind) {
18810
19550
  case "keyed":
@@ -18829,7 +19569,8 @@ function stringifyEventDelegation(lines, plan) {
18829
19569
  }
18830
19570
  }
18831
19571
  function emitKeyedLookup(ls, ev, handlerCall, lookup) {
18832
- const { arrayExpr, param, keyWithItem, mapPreamble, hasBindings, indexParam } = lookup;
19572
+ const { arrayExpr, param, keyWithItem, mapPreamble, mapPreambleDeclaredNames, hasBindings, indexParam } = lookup;
19573
+ const preambleLine = preambleLineForHandler(mapPreamble, mapPreambleDeclaredNames, ev.handler);
18833
19574
  if (ev.nestedLoops.length === 0) {
18834
19575
  const idxLine2 = indexBindingLine(ev.handler, indexParam, `${arrayExpr}.findIndex(item => String(${keyWithItem}) === key)`);
18835
19576
  ls.push(` const li = ${varSlotId(ev.childSlotId)}El.closest('[${DATA_KEY}]')`);
@@ -18839,20 +19580,21 @@ function emitKeyedLookup(ls, ev, handlerCall, lookup) {
18839
19580
  ls.push(` const __bfLoopItem = ${arrayExpr}.find(item => String(${keyWithItem}) === key)`);
18840
19581
  ls.push(` if (__bfLoopItem) {`);
18841
19582
  ls.push(` const ${param} = __bfLoopItem`);
18842
- if (mapPreamble)
18843
- ls.push(` ${mapPreamble}`);
19583
+ if (preambleLine)
19584
+ ls.push(` ${preambleLine}`);
18844
19585
  if (idxLine2)
18845
19586
  ls.push(` ${idxLine2}`);
18846
- ls.push(` ${handlerCall}`);
19587
+ ls.push(` ;${handlerCall}`);
18847
19588
  ls.push(` }`);
18848
19589
  } else {
18849
19590
  ls.push(` const ${param} = ${arrayExpr}.find(item => String(${keyWithItem}) === key)`);
18850
- if (mapPreamble)
18851
- ls.push(` ${mapPreamble}`);
19591
+ ls.push(` if (${param}) {`);
19592
+ if (preambleLine)
19593
+ ls.push(` ${preambleLine}`);
18852
19594
  if (idxLine2)
18853
- ls.push(` if (${param}) { ${idxLine2}; ${handlerCall} }`);
18854
- else
18855
- ls.push(` if (${param}) ${handlerCall}`);
19595
+ ls.push(` ${idxLine2}`);
19596
+ ls.push(` ;${handlerCall}`);
19597
+ ls.push(` }`);
18856
19598
  }
18857
19599
  ls.push(` }`);
18858
19600
  return;
@@ -18879,16 +19621,18 @@ function emitKeyedLookup(ls, ev, handlerCall, lookup) {
18879
19621
  }
18880
19622
  const outerGuard = hasBindings ? "__bfLoopItem" : param;
18881
19623
  const allParams = [outerGuard, ...ev.nestedLoops.map((n) => n.param)];
18882
- if (mapPreamble)
18883
- ls.push(` ${mapPreamble}`);
18884
19624
  const idxLine = indexBindingLine(ev.handler, indexParam, `${arrayExpr}.findIndex(item => String(${keyWithItem}) === outerKey)`);
19625
+ ls.push(` if (${allParams.join(" && ")}) {`);
19626
+ if (preambleLine)
19627
+ ls.push(` ${preambleLine}`);
18885
19628
  if (idxLine)
18886
- ls.push(` if (${allParams.join(" && ")}) { ${idxLine}; ${handlerCall} }`);
18887
- else
18888
- ls.push(` if (${allParams.join(" && ")}) ${handlerCall}`);
19629
+ ls.push(` ${idxLine}`);
19630
+ ls.push(` ;${handlerCall}`);
19631
+ ls.push(` }`);
18889
19632
  }
18890
19633
  function emitDynamicIndexLookup(ls, ev, handlerCall, lookup) {
18891
- const { arrayExpr, param, mapPreamble, hasBindings, indexParam } = lookup;
19634
+ const { arrayExpr, param, mapPreamble, mapPreambleDeclaredNames, hasBindings, indexParam } = lookup;
19635
+ const preambleLine = preambleLineForHandler(mapPreamble, mapPreambleDeclaredNames, ev.handler);
18892
19636
  const idxLine = indexBindingLine(ev.handler, indexParam, "idx");
18893
19637
  ls.push(` const li = ${varSlotId(ev.childSlotId)}El.closest('li, [bf-i]')`);
18894
19638
  ls.push(` if (li && li.parentElement) {`);
@@ -18897,25 +19641,27 @@ function emitDynamicIndexLookup(ls, ev, handlerCall, lookup) {
18897
19641
  ls.push(` const __bfLoopItem = ${arrayExpr}[idx]`);
18898
19642
  ls.push(` if (__bfLoopItem) {`);
18899
19643
  ls.push(` const ${param} = __bfLoopItem`);
18900
- if (mapPreamble)
18901
- ls.push(` ${mapPreamble}`);
19644
+ if (preambleLine)
19645
+ ls.push(` ${preambleLine}`);
18902
19646
  if (idxLine)
18903
19647
  ls.push(` ${idxLine}`);
18904
- ls.push(` ${handlerCall}`);
19648
+ ls.push(` ;${handlerCall}`);
18905
19649
  ls.push(` }`);
18906
19650
  } else {
18907
19651
  ls.push(` const ${param} = ${arrayExpr}[idx]`);
18908
- if (mapPreamble)
18909
- ls.push(` ${mapPreamble}`);
19652
+ ls.push(` if (${param}) {`);
19653
+ if (preambleLine)
19654
+ ls.push(` ${preambleLine}`);
18910
19655
  if (idxLine)
18911
- ls.push(` if (${param}) { ${idxLine}; ${handlerCall} }`);
18912
- else
18913
- ls.push(` if (${param}) ${handlerCall}`);
19656
+ ls.push(` ${idxLine}`);
19657
+ ls.push(` ;${handlerCall}`);
19658
+ ls.push(` }`);
18914
19659
  }
18915
19660
  ls.push(` }`);
18916
19661
  }
18917
19662
  function emitStaticIndexLookup(ls, ev, handlerCall, lookup, containerVar) {
18918
- const { arrayExpr, param, mapPreamble, offset, indexParam } = lookup;
19663
+ const { arrayExpr, param, mapPreamble, mapPreambleDeclaredNames, offset, indexParam } = lookup;
19664
+ const preambleLine = preambleLineForHandler(mapPreamble, mapPreambleDeclaredNames, ev.handler);
18919
19665
  const idxLine = indexBindingLine(ev.handler, indexParam, "__idx");
18920
19666
  ls.push(` let __el = ${varSlotId(ev.childSlotId)}El`);
18921
19667
  ls.push(` while (__el.parentElement && __el.parentElement !== ${containerVar}) __el = __el.parentElement`);
@@ -18923,12 +19669,13 @@ function emitStaticIndexLookup(ls, ev, handlerCall, lookup, containerVar) {
18923
19669
  const idxOffset = buildLoopChildIndexSubtraction(offset ?? undefined);
18924
19670
  ls.push(` const __idx = Array.from(${containerVar}.children).indexOf(__el)${idxOffset}`);
18925
19671
  ls.push(` const ${param} = ${arrayExpr}[__idx]`);
18926
- if (mapPreamble)
18927
- ls.push(` ${mapPreamble}`);
19672
+ ls.push(` if (${param}) {`);
19673
+ if (preambleLine)
19674
+ ls.push(` ${preambleLine}`);
18928
19675
  if (idxLine)
18929
- ls.push(` if (${param}) { ${idxLine}; ${handlerCall} }`);
18930
- else
18931
- ls.push(` if (${param}) ${handlerCall}`);
19676
+ ls.push(` ${idxLine}`);
19677
+ ls.push(` ;${handlerCall}`);
19678
+ ls.push(` }`);
18932
19679
  ls.push(` }`);
18933
19680
  }
18934
19681
 
@@ -18961,12 +19708,29 @@ function emitPlain(lines, plan) {
18961
19708
  eventDelegation,
18962
19709
  childRefs,
18963
19710
  bodyIsMultiRoot,
18964
- profileLoopId
19711
+ profileLoopId,
19712
+ preambleRegions
18965
19713
  } = plan;
18966
19714
  const loopBfId = profileLoopId ? `, ${JSON.stringify(profileLoopId)}` : "";
18967
19715
  const unwrapInline = paramUnwrap ? `${paramUnwrap} ` : "";
18968
19716
  lines.push(` __disposers.push(createDisposableEffect(() => {`);
18969
- if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0) {
19717
+ if (plan.flatMapLeafItem) {
19718
+ lines.push(` if (${containerVar}) mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (__bfD, ${indexParam}, __existing) => {`);
19719
+ lines.push(` let __el = __existing`);
19720
+ lines.push(` if (!__el) { const __tpl = document.createElement('template'); __tpl.innerHTML = __bfD().h; __el = __tpl.content.firstElementChild }`);
19721
+ lines.push(` let __last = __existing ? undefined : __bfD().h`);
19722
+ lines.push(` createEffect(() => {`);
19723
+ lines.push(` const __html = __bfD().h`);
19724
+ lines.push(` if (__last === undefined) { __last = __html; return }`);
19725
+ lines.push(` if (__html !== __last) { __last = __html; patchLeaf(__el, __html) }`);
19726
+ lines.push(` })`);
19727
+ lines.push(` return __el`);
19728
+ lines.push(` }, '${markerId}'${loopBfId})`);
19729
+ lines.push(` }))`);
19730
+ stringifyEventDelegation(lines, eventDelegation);
19731
+ return;
19732
+ }
19733
+ if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0 && preambleRegions.length === 0) {
18970
19734
  const cloneExpr = emitTemplateCloneInline(template);
18971
19735
  if (mapPreambleWrapped) {
18972
19736
  lines.push(` if (${containerVar}) mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => { ${unwrapInline}if (__existing) return __existing; ${mapPreambleWrapped}; ${cloneExpr} }, '${markerId}'${loopBfId})`);
@@ -18991,6 +19755,7 @@ function emitPlain(lines, plan) {
18991
19755
  stringifyReactiveEffects(lines, reactiveEffects, { indent: " ", elVar: "__el", bodyIsMultiRoot });
18992
19756
  }
18993
19757
  emitLoopChildRefs(lines, childRefs, { indent: " ", elVar: "__el", bodyIsMultiRoot });
19758
+ emitPreambleRegionEffects(lines, preambleRegions, mapPreambleWrapped, { indent: " ", elVar: "__el" });
18994
19759
  lines.push(` return __el`);
18995
19760
  lines.push(` }, '${markerId}'${loopBfId})`);
18996
19761
  }
@@ -19130,6 +19895,7 @@ function buildComponentLoopPlan(elem, profileComponentName) {
19130
19895
  const hasChildConds = elem.bindings.conditionals.length > 0;
19131
19896
  return {
19132
19897
  kind: "component",
19898
+ rowConstruction: "dom-ops",
19133
19899
  containerVar: `_${varSlotId(elem.slotId)}`,
19134
19900
  markerId: elem.markerId,
19135
19901
  arrayExpr: buildChainedArrayExpr(elem),
@@ -19175,8 +19941,32 @@ function buildPlainLoopPlan(elem, profileComponentName) {
19175
19941
  const wrap = (expr) => wrapLoopParamAsAccessor(expr, elem.param, elem.paramBindings);
19176
19942
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(elem.param, elem.paramBindings);
19177
19943
  const hasReactive2 = elem.bindings.reactiveAttrs.length > 0 || elem.bindings.reactiveTexts.length > 0 || elem.bindings.conditionals.length > 0;
19944
+ if (elem.flatMapClient) {
19945
+ return {
19946
+ kind: "plain",
19947
+ rowConstruction: "string-template",
19948
+ containerVar: `_${varSlotId(elem.slotId)}`,
19949
+ markerId: elem.markerId,
19950
+ profileLoopId: profileComponentName ? `${profileComponentName}#binding:${elem.slotId}` : undefined,
19951
+ arrayExpr: `(${buildChainedArrayExpr(elem)}).flatMap(${elem.flatMapClient.params} => ${elem.flatMapClient.body})`,
19952
+ keyFn: elem.flatMapClient.keyed ? "(__bfD, __bfI) => String(__bfD.k ?? __bfI)" : "null",
19953
+ paramHead: "__bfD",
19954
+ paramUnwrap: "",
19955
+ indexParam: "__idx",
19956
+ mapPreambleWrapped: "",
19957
+ template: "",
19958
+ reactiveEffects: null,
19959
+ childRefs: [],
19960
+ bodyIsMultiRoot: false,
19961
+ anchored: false,
19962
+ anchorKeyExpr: "__idx",
19963
+ flatMapLeafItem: true,
19964
+ preambleRegions: []
19965
+ };
19966
+ }
19178
19967
  return {
19179
19968
  kind: "plain",
19969
+ rowConstruction: "string-template",
19180
19970
  containerVar: `_${varSlotId(elem.slotId)}`,
19181
19971
  markerId: elem.markerId,
19182
19972
  profileLoopId: profileComponentName ? `${profileComponentName}#binding:${elem.slotId}` : undefined,
@@ -19185,12 +19975,16 @@ function buildPlainLoopPlan(elem, profileComponentName) {
19185
19975
  paramHead,
19186
19976
  paramUnwrap,
19187
19977
  indexParam: elem.index || "__idx",
19188
- mapPreambleWrapped: elem.mapPreamble ? wrap(elem.mapPreamble) : "",
19978
+ mapPreambleWrapped: elem.preamble ? renderPreamble(elem.preamble, {
19979
+ transformJs: wrap,
19980
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: elem.param, bindings: elem.paramBindings }], undefined, true)
19981
+ }) : "",
19189
19982
  template: elem.template,
19190
19983
  skeletonTemplate: elem.skeletonTemplate,
19191
19984
  skeletonPaths: elem.skeletonPaths,
19192
19985
  reactiveEffects: hasReactive2 ? buildLoopReactiveEffectsPlan(elem, profileComponentName) : null,
19193
19986
  childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings),
19987
+ preambleRegions: buildPreambleRegionPlans(elem.preambleRegions, elem.param, elem.paramBindings),
19194
19988
  bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false,
19195
19989
  anchored: elem.bodyIsItemConditional ?? false,
19196
19990
  anchorKeyExpr: elem.key ? wrap(elem.key) : elem.index || "__idx"
@@ -19212,6 +20006,7 @@ function buildStaticLoopPlan(elem, unsafeLocalNames, profileComponentName) {
19212
20006
  const childIndexExpr = buildLoopChildIndexExpr(indexParam, elem.offset);
19213
20007
  return {
19214
20008
  kind: "static",
20009
+ rowConstruction: "string-template",
19215
20010
  containerVar: `_${varSlotId(elem.slotId)}`,
19216
20011
  arrayExpr: elem.array,
19217
20012
  param: elem.param,
@@ -19233,7 +20028,9 @@ function buildStaticLoopMaterialize(elem, unsafeLocalNames) {
19233
20028
  return null;
19234
20029
  return {
19235
20030
  itemTemplate: elem.staticItemTemplate,
19236
- mapPreamble: elem.mapPreamble ?? "",
20031
+ mapPreamble: elem.preamble ? renderPreamble(elem.preamble, {
20032
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: elem.param, bindings: elem.paramBindings }], undefined, true)
20033
+ }) : "",
19237
20034
  bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false
19238
20035
  };
19239
20036
  }
@@ -19262,6 +20059,7 @@ function emitLoopUpdates(lines, ctx, unsafeLocalNames) {
19262
20059
  unsafeLocalNames,
19263
20060
  profileComponentName: ctx.profile ? ctx.componentName : undefined
19264
20061
  });
20062
+ internalInvariant(!(elem.preamble && elem.preamble.builderNames.length > 0 && plan.rowConstruction === "dom-ops"), `loop variant '${plan.kind}' declares dom-ops row construction but received a JSX-bearing preamble — add a Phase-1 refusal (or wire renderPreamble support) for this shape`);
19265
20063
  stringifyLoop(lines, plan);
19266
20064
  emitLoopEventDelegation(lines, elem, plan.kind, ctx.profile ? ctx.componentName : undefined);
19267
20065
  }
@@ -20192,19 +20990,19 @@ function isJsxLike(expr) {
20192
20990
  function collectArrowParamNames(arrow) {
20193
20991
  const names = new Set;
20194
20992
  for (const p of arrow.parameters)
20195
- collectBindingNames(p.name, names);
20993
+ collectBindingNames2(p.name, names);
20196
20994
  return names;
20197
20995
  }
20198
- function collectBindingNames(name, out) {
20996
+ function collectBindingNames2(name, out) {
20199
20997
  const push = Array.isArray(out) ? (n) => out.push(n) : (n) => out.add(n);
20200
20998
  if (ts16.isIdentifier(name)) {
20201
20999
  push(name.text);
20202
21000
  } else if (ts16.isObjectBindingPattern(name)) {
20203
- name.elements.forEach((el) => collectBindingNames(el.name, out));
21001
+ name.elements.forEach((el) => collectBindingNames2(el.name, out));
20204
21002
  } else if (ts16.isArrayBindingPattern(name)) {
20205
21003
  name.elements.forEach((el) => {
20206
21004
  if (!ts16.isOmittedExpression(el))
20207
- collectBindingNames(el.name, out);
21005
+ collectBindingNames2(el.name, out);
20208
21006
  });
20209
21007
  }
20210
21008
  }
@@ -20213,12 +21011,12 @@ function collectFreeIdentifiers(arrow) {
20213
21011
  const bound = [];
20214
21012
  for (const p of arrow.parameters) {
20215
21013
  const names = [];
20216
- collectBindingNames(p.name, names);
21014
+ collectBindingNames2(p.name, names);
20217
21015
  bound.push(...names);
20218
21016
  }
20219
21017
  function pushBindings(name) {
20220
21018
  const names = [];
20221
- collectBindingNames(name, names);
21019
+ collectBindingNames2(name, names);
20222
21020
  bound.push(...names);
20223
21021
  return names;
20224
21022
  }
@@ -20345,7 +21143,7 @@ function collectModuleScopeNames(sourceFile) {
20345
21143
  names.add(stmt.name.text);
20346
21144
  else if (ts16.isVariableStatement(stmt)) {
20347
21145
  for (const decl of stmt.declarationList.declarations)
20348
- collectBindingNames(decl.name, names);
21146
+ collectBindingNames2(decl.name, names);
20349
21147
  } else if (ts16.isImportDeclaration(stmt) && stmt.importClause) {
20350
21148
  const ic = stmt.importClause;
20351
21149
  if (ic.name)
@@ -21364,8 +22162,13 @@ function walkNode(node, meta, bindings, matchers, errors, seen) {
21364
22162
  for (const child of nested.children)
21365
22163
  walkNode(child, meta, loopBindings, matchers, errors, seen);
21366
22164
  }
21367
- for (const frag of node.flatMapCallback?.fragments ?? []) {
21368
- walkNode(frag.ir, meta, loopBindings, matchers, errors, seen);
22165
+ for (const seg of node.flatMapCallback?.segments ?? []) {
22166
+ if (seg.kind === "jsx")
22167
+ walkNode(seg.ir, meta, loopBindings, matchers, errors, seen);
22168
+ }
22169
+ for (const seg of node.preamble?.segments ?? []) {
22170
+ if (seg.kind === "jsx")
22171
+ walkNode(seg.ir, meta, loopBindings, matchers, errors, seen);
21369
22172
  }
21370
22173
  break;
21371
22174
  }
@@ -21427,7 +22230,7 @@ function compileMultipleComponents(source, filePath, componentNames, options) {
21427
22230
  const entries = [];
21428
22231
  const program = options.program ?? (needsTypeBasedDetection(source) ? createProgramForFile(source, filePath)?.program : undefined);
21429
22232
  for (const componentName of componentNames) {
21430
- const ctx = analyzeComponent(source, filePath, componentName, program);
22233
+ const ctx = analyzeComponent(source, filePath, componentName, program, adapter.acceptsCallbackBody);
21431
22234
  if (!ctx.jsxReturn) {
21432
22235
  errors.push(...ctx.errors);
21433
22236
  continue;
@@ -21736,7 +22539,7 @@ function compileJSX(source, filePath, options) {
21736
22539
  if (componentNames.length > 1) {
21737
22540
  return compileMultipleComponents(compileSource, filePath, componentNames, options);
21738
22541
  }
21739
- const ctx = analyzeComponent(compileSource, filePath, undefined, options.program);
22542
+ const ctx = analyzeComponent(compileSource, filePath, undefined, options.program, options.adapter.acceptsCallbackBody);
21740
22543
  if (!ctx.jsxReturn) {
21741
22544
  errors.push(...ctx.errors);
21742
22545
  const exportedModuleSignals = ctx.signals.filter((s2) => s2.isModule && s2.isExported);
@@ -21936,6 +22739,7 @@ class BaseAdapter {
21936
22739
  import { BF_SCOPE as BF_SCOPE4, BF_SLOT, BF_COND } from "@barefootjs/shared";
21937
22740
  class JsxAdapter extends BaseAdapter {
21938
22741
  componentName = "";
22742
+ acceptsCallbackBody = () => true;
21939
22743
  formatImportSpecifiers(specifiers) {
21940
22744
  const defaultSpec = specifiers.find((s) => s.isDefault);
21941
22745
  const namespaceSpec = specifiers.find((s) => s.isNamespace);
@@ -22281,6 +23085,10 @@ export default ${this.componentName}` : "";
22281
23085
  const indexParam = loop.index ? `, ${loop.index}` : "";
22282
23086
  const children = this.renderChildren(loop.children);
22283
23087
  const safeChildren = children.startsWith("{") ? `<>${children}</>` : children;
23088
+ const preamble = loop.preamble?.ssrText;
23089
+ if (preamble) {
23090
+ return `{${loop.array}.map((${loop.param}${indexParam}) => { ${preamble} return ${safeChildren} })}`;
23091
+ }
22284
23092
  return `{${loop.array}.map((${loop.param}${indexParam}) => ${safeChildren})}`;
22285
23093
  }
22286
23094
  renderComponent(comp) {
@@ -22481,8 +23289,13 @@ function collectLoopBoundNames(ir) {
22481
23289
  for (const child of nested.children)
22482
23290
  visit3(child);
22483
23291
  }
22484
- for (const frag of node.flatMapCallback?.fragments ?? []) {
22485
- visit3(frag.ir);
23292
+ for (const seg of node.flatMapCallback?.segments ?? []) {
23293
+ if (seg.kind === "jsx")
23294
+ visit3(seg.ir);
23295
+ }
23296
+ for (const seg of node.preamble?.segments ?? []) {
23297
+ if (seg.kind === "jsx")
23298
+ visit3(seg.ir);
22486
23299
  }
22487
23300
  break;
22488
23301
  case "conditional":
@@ -23058,12 +23871,16 @@ function restNamesMisused(loop, names) {
23058
23871
  check(l.array);
23059
23872
  check(l.templateArray);
23060
23873
  check(l.key);
23061
- check(l.mapPreamble);
23062
- check(l.templateMapPreamble);
23874
+ if (l.preamble) {
23875
+ check(preambleAnalysisText(l.preamble));
23876
+ check(preambleAnalysisTemplateText(l.preamble));
23877
+ }
23063
23878
  if (l.flatMapCallback) {
23064
- check(l.flatMapCallback.body);
23065
- check(l.flatMapCallback.templateBody);
23066
- l.flatMapCallback.fragments.forEach((f) => visit3(f.ir));
23879
+ check(preambleAnalysisText(l.flatMapCallback));
23880
+ for (const seg of l.flatMapCallback.segments) {
23881
+ if (seg.kind === "jsx")
23882
+ visit3(seg.ir);
23883
+ }
23067
23884
  }
23068
23885
  l.children.forEach(visit3);
23069
23886
  };