@barefootjs/jsx 0.26.3 → 0.26.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/index.js +433 -55
  2. package/dist/ir-to-client-js/build-references.d.ts.map +1 -1
  3. package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
  4. package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts +12 -1
  5. package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts.map +1 -1
  6. package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts.map +1 -1
  7. package/dist/ir-to-client-js/control-flow/plan/build-event-delegation.d.ts.map +1 -1
  8. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
  9. package/dist/ir-to-client-js/control-flow/plan/event-delegation.d.ts +16 -1
  10. package/dist/ir-to-client-js/control-flow/plan/event-delegation.d.ts.map +1 -1
  11. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +27 -0
  12. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
  13. package/dist/ir-to-client-js/control-flow/shared.d.ts +12 -1
  14. package/dist/ir-to-client-js/control-flow/shared.d.ts.map +1 -1
  15. package/dist/ir-to-client-js/control-flow/stringify/event-delegation.d.ts.map +1 -1
  16. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts +19 -0
  17. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
  18. package/dist/ir-to-client-js/html-template.d.ts +49 -1
  19. package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
  20. package/dist/ir-to-client-js/imports.d.ts +2 -2
  21. package/dist/ir-to-client-js/imports.d.ts.map +1 -1
  22. package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
  23. package/dist/ir-to-client-js/types.d.ts +26 -1
  24. package/dist/ir-to-client-js/types.d.ts.map +1 -1
  25. package/dist/jsx-to-ir.d.ts.map +1 -1
  26. package/dist/types.d.ts +47 -0
  27. package/dist/types.d.ts.map +1 -1
  28. package/package.json +2 -2
  29. package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +187 -37
  30. package/src/__tests__/client-js-generation.test.ts +8 -2
  31. package/src/__tests__/compiler-runtime-contract.test.ts +4 -4
  32. package/src/__tests__/delegated-handler-preamble.test.ts +153 -0
  33. package/src/__tests__/flatmap-segments.test.ts +182 -0
  34. package/src/__tests__/map-body-no-silent-divergence.test.ts +45 -0
  35. package/src/__tests__/preamble-region-patch.test.ts +150 -0
  36. package/src/__tests__/static-loop-csr-materialize.test.ts +3 -2
  37. package/src/ir-to-client-js/build-references.ts +16 -0
  38. package/src/ir-to-client-js/collect-elements.ts +66 -10
  39. package/src/ir-to-client-js/control-flow/plan/branch-loop.ts +12 -1
  40. package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +11 -3
  41. package/src/ir-to-client-js/control-flow/plan/build-event-delegation.ts +19 -3
  42. package/src/ir-to-client-js/control-flow/plan/build-loop.ts +36 -0
  43. package/src/ir-to-client-js/control-flow/plan/event-delegation.ts +16 -1
  44. package/src/ir-to-client-js/control-flow/plan/loop.ts +28 -0
  45. package/src/ir-to-client-js/control-flow/shared.ts +26 -1
  46. package/src/ir-to-client-js/control-flow/stringify/branch-loop.ts +25 -3
  47. package/src/ir-to-client-js/control-flow/stringify/event-delegation.ts +67 -19
  48. package/src/ir-to-client-js/control-flow/stringify/loop.ts +65 -1
  49. package/src/ir-to-client-js/html-template.ts +115 -6
  50. package/src/ir-to-client-js/imports.ts +1 -1
  51. package/src/ir-to-client-js/reactivity.ts +6 -0
  52. package/src/ir-to-client-js/types.ts +24 -0
  53. package/src/jsx-to-ir.ts +374 -8
  54. package/src/types.ts +49 -0
package/dist/index.js CHANGED
@@ -3252,7 +3252,7 @@ function templateAttrExpr(attrName, valExpr, presenceOrUndefined) {
3252
3252
  return `\${((v) => v != null ? 'style="' + ${escapeAttrValueExpr("v")} + '"' : '')(styleToCss(${valExpr}))}`;
3253
3253
  }
3254
3254
  if (attrName === "data-key" || attrName.startsWith("data-key-")) {
3255
- return `${attrName}="\${${valExpr}}"`;
3255
+ return `${attrName}="\${${escapeAttrValueExpr(valExpr)}}"`;
3256
3256
  }
3257
3257
  return `\${(${valExpr}) != null ? '${attrName}="' + ${escapeAttrValueExpr(valExpr)} + '"' : ''}`;
3258
3258
  }
@@ -3387,12 +3387,57 @@ function renderPreamble(preamble, opts) {
3387
3387
  if (seg.kind === "js") {
3388
3388
  const text = opts.textVariant === "template" ? seg.templateText ?? seg.text : seg.text;
3389
3389
  out += opts.transformJs ? opts.transformJs(text) : text;
3390
+ } else if (opts.rawLeaf) {
3391
+ out += opts.renderLeaf(escapeLeafTextExpressions(seg.ir));
3390
3392
  } else {
3391
3393
  out += "`" + opts.renderLeaf(escapeLeafTextExpressions(seg.ir)) + "`";
3392
3394
  }
3393
3395
  }
3394
3396
  return out;
3395
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
+ }
3396
3441
  function escapeLeafTextExpressions(ir) {
3397
3442
  switch (ir.type) {
3398
3443
  case "element":
@@ -3527,7 +3572,7 @@ function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, bran
3527
3572
  let mapExpr;
3528
3573
  if (node.flatMapCallback) {
3529
3574
  const body = renderPreamble(node.flatMapCallback, {
3530
- renderLeaf: (ir) => irToHtmlTemplate(ir, restSpreadNames, loopDepth + 1, loopParams, branchSlotsVar, insideLoop)
3575
+ renderLeaf: (ir) => irToHtmlTemplate(stripLeafKeyAttr(ir), restSpreadNames, loopDepth + 1, loopParams, branchSlotsVar, insideLoop)
3531
3576
  });
3532
3577
  mapExpr = `\${${wrappedArray}.flatMap(${node.flatMapCallback.params} => ${body}).join('')}`;
3533
3578
  } else if (node.preamble) {
@@ -3817,7 +3862,7 @@ function irToPlaceholderTemplate(node, restSpreadNames, loopDepth = 0, loopParam
3817
3862
  let mapExpr;
3818
3863
  if (node.flatMapCallback) {
3819
3864
  const body = renderPreamble(node.flatMapCallback, {
3820
- renderLeaf: (ir) => irToPlaceholderTemplate(ir, restSpreadNames, loopDepth + 1, loopParams)
3865
+ renderLeaf: (ir) => irToPlaceholderTemplate(stripLeafKeyAttr(ir), restSpreadNames, loopDepth + 1, loopParams)
3821
3866
  });
3822
3867
  mapExpr = `\${${wrappedArray}.flatMap(${node.flatMapCallback.params} => ${body}).join('')}`;
3823
3868
  } else if (node.preamble) {
@@ -4443,7 +4488,7 @@ function generateCsrTemplateWithOpts(node, opts) {
4443
4488
  const body = renderPreamble(node.flatMapCallback, {
4444
4489
  textVariant: "template",
4445
4490
  transformJs: (t) => applyPropsRewrite(t, propsObjectName ?? null),
4446
- renderLeaf: (ir) => recurseInLoopBody(ir)
4491
+ renderLeaf: (ir) => recurseInLoopBody(stripLeafKeyAttr(ir))
4447
4492
  });
4448
4493
  mapExpr = `\${${iterArrayExpr}.flatMap(${node.flatMapCallback.params} => ${body}).join('')}`;
4449
4494
  } else if (node.preamble) {
@@ -11710,6 +11755,87 @@ function checkLoopKey(callback, ctx, isNested) {
11710
11755
  return;
11711
11756
  }
11712
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
+ }
11713
11839
  function loopBodyIsMultiRoot(children) {
11714
11840
  const real = children.filter((c) => !(c.type === "text" && typeof c.value === "string" && !c.value.trim()));
11715
11841
  if (real.length === 0)
@@ -11755,6 +11881,7 @@ function extractItemConditionalKey(cond) {
11755
11881
  }
11756
11882
  function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
11757
11883
  const isNested = ctx.loopParams.size > 0;
11884
+ const diagCountAtEntry = ctx.analyzer.errors.length;
11758
11885
  const depth = ctx.loopDepth;
11759
11886
  const propAccess = node.expression;
11760
11887
  const mapSource = propAccess.expression;
@@ -12041,12 +12168,34 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
12041
12168
  }
12042
12169
  }
12043
12170
  }
12044
- if (method === "flatMap" && children.length === 0) {
12171
+ if (method === "flatMap" && children.length === 0 && !flatMapProjectionCall(body)) {
12045
12172
  flatMapCallback = buildFlatMapCallback(callback, body, ctx);
12046
12173
  }
12047
12174
  } else {
12048
12175
  tryTransformRenderableBody(body);
12049
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
+ }
12050
12199
  if (paramBindings) {
12051
12200
  for (const b of paramBindings)
12052
12201
  ctx.loopParams.delete(b.name);
@@ -12058,6 +12207,16 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
12058
12207
  ctx.loopDepth--;
12059
12208
  }
12060
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
+ }
12061
12220
  return null;
12062
12221
  }
12063
12222
  if (ts11.isArrowFunction(node.arguments[0]) && children.length > 0) {
@@ -12110,6 +12269,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
12110
12269
  const hasCalls = exprHasFunctionCalls(arrayExpr);
12111
12270
  const isDirectPropArray = method !== "flatMap" && isArrayExprDirectPropRef(arrayExpr, ctx);
12112
12271
  const isStaticArray = !isSignalOrMemoArray(array, ctx) && !isDirectPropArray && !hasCalls && !objectIteration;
12272
+ const preambleRegions = preamble && !isStaticArray ? collectPreambleRegions(children, new Set(preamble.declaredNames), ctx) : undefined;
12113
12273
  const nestedComponents = collectNestedComponents(children).filter((c) => c.name !== childComponent?.name);
12114
12274
  return {
12115
12275
  type: "loop",
@@ -12140,6 +12300,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
12140
12300
  depth,
12141
12301
  clientOnly: isClientOnly || undefined,
12142
12302
  preamble,
12303
+ preambleRegions: preambleRegions && preambleRegions.length > 0 ? preambleRegions : undefined,
12143
12304
  paramType,
12144
12305
  indexType,
12145
12306
  paramBindings,
@@ -12202,6 +12363,28 @@ function buildFlatMapCallback(callback, body, ctx) {
12202
12363
  }));
12203
12364
  return;
12204
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
+ }
12387
+ }
12205
12388
  const pieces = reconstructAsSegments(body, ctx.sourceFile, ctx.analyzer.typeExcludeRanges, leafSpans);
12206
12389
  const segments = pieces.map((piece) => {
12207
12390
  if ("marker" in piece)
@@ -12216,6 +12399,25 @@ function buildFlatMapCallback(callback, body, ctx) {
12216
12399
  rawBody: tsxSourceText(body.getText(ctx.sourceFile))
12217
12400
  };
12218
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
+ }
12219
12421
  function preambleFragmentNeedsWiring(ir) {
12220
12422
  switch (ir.type) {
12221
12423
  case "component":
@@ -12258,6 +12460,40 @@ function flagArrayChildExpressions(nodes, declared) {
12258
12460
  }
12259
12461
  }
12260
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
+ }
12261
12497
  function collectBindingNames(name, out) {
12262
12498
  if (ts11.isIdentifier(name)) {
12263
12499
  out.add(name.text);
@@ -13635,6 +13871,8 @@ function collectLoopChildReactiveTexts(node, ctx, loopParam, loopParamBindings,
13635
13871
  expression: ({ node: n, scope: insideConditional }) => {
13636
13872
  if (!n.slotId)
13637
13873
  return;
13874
+ if (n.preambleRegion)
13875
+ return;
13638
13876
  const originFreeIds = freeIdsFromRefs(n.origin?.freeRefs);
13639
13877
  const expanded = expandConstantForReactivity(n.expr, ctx, originFreeIds);
13640
13878
  const reactive = classifyReactivity(expanded.expr, ctx, loopParam, loopParamBindings, expanded.freeIds).kind !== "none" || decideWrapFromAstFlags(n).wrap;
@@ -14190,10 +14428,13 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
14190
14428
  loop: ({ node: l, scope: inCond }) => {
14191
14429
  if (!l.slotId || inCond)
14192
14430
  return;
14431
+ const projectionInner = l.method === "flatMap" && l.children.length === 1 && l.children[0].type === "loop" ? l.children[0] : undefined;
14193
14432
  const childHandlers = [];
14194
- const bindings = collectLoopChildBindings(l.children, ctx, siblingOffsets, l.param, l.paramBindings);
14195
- for (const child of l.children) {
14196
- 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
+ }
14197
14438
  }
14198
14439
  if (l.childComponent) {
14199
14440
  for (const prop of l.childComponent.props) {
@@ -14204,7 +14445,7 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
14204
14445
  }
14205
14446
  }
14206
14447
  }
14207
- const { useElementReconciliation, innerLoops } = decideLoopRendering(l, siblingOffsets, ctx);
14448
+ const { useElementReconciliation, innerLoops } = projectionInner ? { useElementReconciliation: false, innerLoops: undefined } : decideLoopRendering(l, siblingOffsets, ctx);
14208
14449
  let template = "";
14209
14450
  let staticItemTemplate;
14210
14451
  let skeletonTemplate;
@@ -14214,7 +14455,7 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
14214
14455
  if (l.isStaticArray && l.children[0]) {
14215
14456
  staticItemTemplate = irToHtmlTemplate(l.children[0], buildRestSpreadNames(ctx), 0, undefined, undefined, true);
14216
14457
  }
14217
- } else if (l.children[0]) {
14458
+ } else if (l.children[0] && !projectionInner) {
14218
14459
  const loopParamSpec = [{ param: l.param, bindings: l.paramBindings }];
14219
14460
  template = useElementReconciliation ? irToPlaceholderTemplate(l.children[0], buildRestSpreadNames(ctx), 0, loopParamSpec) : irToHtmlTemplate(l.children[0], buildRestSpreadNames(ctx), 0, loopParamSpec);
14220
14461
  if (l.isStaticArray) {
@@ -14266,7 +14507,17 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
14266
14507
  raw: l.sortComparator.raw
14267
14508
  } : undefined,
14268
14509
  chainOrder: l.chainOrder,
14269
- preamble: l.preamble
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
14270
14521
  });
14271
14522
  },
14272
14523
  component: ({ node: c, descend, descendJsxChildren }) => {
@@ -14423,15 +14674,18 @@ function collectBranchLoops(node, ctx, siblingOffsets) {
14423
14674
  const containerSlot = parentSlotId ?? n.slotId;
14424
14675
  if (!containerSlot)
14425
14676
  return;
14426
- 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);
14427
14679
  let childTemplate;
14428
14680
  const branchLoopParamSpec = [{ param: n.param, bindings: n.paramBindings }];
14429
- if (useElementReconciliation && n.children[0]) {
14681
+ if (projectionInner) {
14682
+ childTemplate = "";
14683
+ } else if (useElementReconciliation && n.children[0]) {
14430
14684
  childTemplate = irToPlaceholderTemplate(n.children[0], restNames, 0, branchLoopParamSpec);
14431
14685
  } else {
14432
14686
  childTemplate = n.children.map((c) => irToHtmlTemplate(c, undefined, 0, branchLoopParamSpec)).join("");
14433
14687
  }
14434
- 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();
14435
14689
  loops.push({
14436
14690
  kind: "branch",
14437
14691
  array: n.array,
@@ -14448,6 +14702,7 @@ function collectBranchLoops(node, ctx, siblingOffsets) {
14448
14702
  template: childTemplate,
14449
14703
  containerSlotId: containerSlot,
14450
14704
  preamble: n.preamble,
14705
+ preambleRegions: n.preambleRegions,
14451
14706
  nestedComponents: useElementReconciliation ? n.nestedComponents : undefined,
14452
14707
  bindings: branchBindings,
14453
14708
  innerLoops: useElementReconciliation ? innerLoopsCollected : undefined,
@@ -14461,7 +14716,16 @@ function collectBranchLoops(node, ctx, siblingOffsets) {
14461
14716
  paramB: n.sortComparator.paramB,
14462
14717
  raw: n.sortComparator.raw
14463
14718
  } : undefined,
14464
- 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
14465
14729
  });
14466
14730
  }
14467
14731
  });
@@ -14836,6 +15100,13 @@ function buildReferencesGraph(ctx, irRoot) {
14836
15100
  addExprEdges(ROOT_SOURCE, l.sortComparator.raw, "template-closure");
14837
15101
  if (l.preamble)
14838
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
+ }
14839
15110
  descend();
14840
15111
  if (l.childComponent)
14841
15112
  walkChildComponent(l.childComponent);
@@ -15013,6 +15284,8 @@ var RUNTIME_IMPORT_CANDIDATES = [
15013
15284
  "getLoopNodes",
15014
15285
  "mapArray",
15015
15286
  "mapArrayAnchored",
15287
+ "patchLeaf",
15288
+ "patchSlotRange",
15016
15289
  "createDisposableEffect",
15017
15290
  "createComponent",
15018
15291
  "renderChild",
@@ -17015,6 +17288,15 @@ function destructureLoopParam(param, paramBindings) {
17015
17288
  }
17016
17289
  return { head: param, unwrap: "" };
17017
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
+ }
17018
17300
  function buildComponentPropsExpr2(comp, loopParam, loopParamBindings) {
17019
17301
  const wrap = loopParam ? (expr) => wrapLoopParamAsAccessor(expr, loopParam, loopParamBindings) : (expr) => expr;
17020
17302
  const entries = comp.props.map((p) => {
@@ -18029,8 +18311,9 @@ function buildDynamicLoopDelegationPlan(elem, profileComponentName) {
18029
18311
  key: elem.key,
18030
18312
  index: elem.index,
18031
18313
  mapPreamble: elem.preamble ? renderPreamble(elem.preamble, {
18032
- renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: elem.param, bindings: elem.paramBindings }], undefined, true)
18033
- }) : null
18314
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, undefined, undefined, true)
18315
+ }) : null,
18316
+ mapPreambleDeclaredNames: elem.preamble?.declaredNames ?? []
18034
18317
  })
18035
18318
  };
18036
18319
  }
@@ -18047,8 +18330,9 @@ function buildBranchLoopDelegationPlan(loop, cv, profileComponentName) {
18047
18330
  key: loop.key,
18048
18331
  index: loop.index,
18049
18332
  mapPreamble: loop.preamble ? renderPreamble(loop.preamble, {
18050
- renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: loop.param, bindings: loop.paramBindings }], undefined, true)
18051
- }) : null
18333
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, undefined, undefined, true)
18334
+ }) : null,
18335
+ mapPreambleDeclaredNames: loop.preamble?.declaredNames ?? []
18052
18336
  })
18053
18337
  };
18054
18338
  }
@@ -18063,8 +18347,9 @@ function buildStaticArrayDelegationPlan(elem, profileComponentName) {
18063
18347
  arrayExpr: buildChainedArrayExpr(elem),
18064
18348
  param: elem.param,
18065
18349
  mapPreamble: elem.preamble ? renderPreamble(elem.preamble, {
18066
- renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: elem.param, bindings: elem.paramBindings }], undefined, true)
18350
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, undefined, undefined, true)
18067
18351
  }) : null,
18352
+ mapPreambleDeclaredNames: elem.preamble?.declaredNames ?? [],
18068
18353
  offset: elem.offset ?? null,
18069
18354
  indexParam: elem.index ?? null
18070
18355
  }
@@ -18081,6 +18366,7 @@ function buildKeyedOrIndexLookup(args) {
18081
18366
  paramBindings: args.paramBindings,
18082
18367
  keyWithItem,
18083
18368
  mapPreamble: args.mapPreamble,
18369
+ mapPreambleDeclaredNames: args.mapPreambleDeclaredNames,
18084
18370
  hasBindings,
18085
18371
  indexParam: args.index
18086
18372
  };
@@ -18090,6 +18376,7 @@ function buildKeyedOrIndexLookup(args) {
18090
18376
  arrayExpr: args.array,
18091
18377
  param: args.param,
18092
18378
  mapPreamble: args.mapPreamble,
18379
+ mapPreambleDeclaredNames: args.mapPreambleDeclaredNames,
18093
18380
  hasBindings,
18094
18381
  indexParam: args.index
18095
18382
  };
@@ -18111,14 +18398,16 @@ function buildBranchLoopPlan(loop, profileComponentName) {
18111
18398
  }
18112
18399
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(loop.param, loop.paramBindings);
18113
18400
  const hasReactiveEffects = loop.bindings.reactiveAttrs.length > 0 || loop.bindings.reactiveTexts.length > 0 || loop.bindings.conditionals.length > 0;
18401
+ const fm = loop.flatMapClient;
18114
18402
  const plan = {
18115
18403
  kind: "plain",
18116
18404
  rowConstruction: "string-template",
18117
18405
  containerSlotId,
18118
18406
  containerVar,
18119
18407
  markerId: loop.markerId,
18120
- arrayExpr: buildChainedArrayExpr(loop),
18121
- 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),
18122
18411
  paramHead,
18123
18412
  paramUnwrap,
18124
18413
  indexParam: loop.index || "__idx",
@@ -18137,6 +18426,7 @@ function buildBranchLoopPlan(loop, profileComponentName) {
18137
18426
  }) : null,
18138
18427
  eventDelegation: buildBranchLoopDelegationPlan(loop, cv, profileComponentName),
18139
18428
  childRefs: buildChildRefBindings(loop.bindings.refs, loop.param, loop.paramBindings),
18429
+ preambleRegions: buildPreambleRegionPlans(loop.preambleRegions, loop.param, loop.paramBindings),
18140
18430
  bodyIsMultiRoot: loop.bodyIsMultiRoot ?? false,
18141
18431
  profileLoopId: profileComponentName ? `${profileComponentName}#binding:${containerSlotId}` : undefined
18142
18432
  };
@@ -18784,6 +19074,22 @@ function emitLoopChildRefs(lines, refs, opts) {
18784
19074
  lines.push(`${indent}if (${varName}) ${emitRefCall(ref.callback, varName)} }`);
18785
19075
  }
18786
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
+ }
18787
19093
  function stringifyLoop(lines, plan) {
18788
19094
  switch (plan.kind) {
18789
19095
  case "static":
@@ -18825,8 +19131,24 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
18825
19131
  childRefs,
18826
19132
  bodyIsMultiRoot,
18827
19133
  anchored,
18828
- anchorKeyExpr
19134
+ anchorKeyExpr,
19135
+ preambleRegions
18829
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
+ }
18830
19152
  if (anchored) {
18831
19153
  stringifyAnchoredLoop(lines, plan, topIndent, anchorKeyExpr);
18832
19154
  return;
@@ -18837,7 +19159,7 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
18837
19159
  emitHoistedTemplateDecl(lines, topIndent, tplVar, hoistedTpl);
18838
19160
  }
18839
19161
  const loopBfId = plan.profileLoopId ? `, ${JSON.stringify(plan.profileLoopId)}` : "";
18840
- if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0) {
19162
+ if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0 && preambleRegions.length === 0) {
18841
19163
  const unwrapInline = paramUnwrap ? `${paramUnwrap} ` : "";
18842
19164
  const preamble = mapPreambleWrapped ? `${mapPreambleWrapped}; ` : "";
18843
19165
  const cloneExpr = hoistedTpl ? `return ${hoistedCloneExpr(tplVar, hoistedTpl)}` : emitTemplateCloneInline(template);
@@ -18885,6 +19207,7 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
18885
19207
  });
18886
19208
  }
18887
19209
  emitLoopChildRefs(lines, childRefs, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot, elementIndexBySlot: pathPlan?.elementIndexBySlot });
19210
+ emitPreambleRegionEffects(lines, preambleRegions, mapPreambleWrapped, { indent: bodyIndent, elVar: "__el" });
18888
19211
  lines.push(`${bodyIndent}return __el`);
18889
19212
  lines.push(`${topIndent}}, '${markerId}'${loopBfId})`);
18890
19213
  }
@@ -19195,6 +19518,12 @@ function indexBindingLine(handler, indexParam, indexExpr) {
19195
19518
  return null;
19196
19519
  return `const ${indexParam} = ${indexExpr}`;
19197
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
+ }
19198
19527
  function stringifyEventDelegation(lines, plan) {
19199
19528
  const { containerVar, events, itemLookup, profileComponentName } = plan;
19200
19529
  const eventsByName = new Map;
@@ -19240,7 +19569,8 @@ function stringifyEventDelegation(lines, plan) {
19240
19569
  }
19241
19570
  }
19242
19571
  function emitKeyedLookup(ls, ev, handlerCall, lookup) {
19243
- 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);
19244
19574
  if (ev.nestedLoops.length === 0) {
19245
19575
  const idxLine2 = indexBindingLine(ev.handler, indexParam, `${arrayExpr}.findIndex(item => String(${keyWithItem}) === key)`);
19246
19576
  ls.push(` const li = ${varSlotId(ev.childSlotId)}El.closest('[${DATA_KEY}]')`);
@@ -19250,20 +19580,21 @@ function emitKeyedLookup(ls, ev, handlerCall, lookup) {
19250
19580
  ls.push(` const __bfLoopItem = ${arrayExpr}.find(item => String(${keyWithItem}) === key)`);
19251
19581
  ls.push(` if (__bfLoopItem) {`);
19252
19582
  ls.push(` const ${param} = __bfLoopItem`);
19253
- if (mapPreamble)
19254
- ls.push(` ${mapPreamble}`);
19583
+ if (preambleLine)
19584
+ ls.push(` ${preambleLine}`);
19255
19585
  if (idxLine2)
19256
19586
  ls.push(` ${idxLine2}`);
19257
- ls.push(` ${handlerCall}`);
19587
+ ls.push(` ;${handlerCall}`);
19258
19588
  ls.push(` }`);
19259
19589
  } else {
19260
19590
  ls.push(` const ${param} = ${arrayExpr}.find(item => String(${keyWithItem}) === key)`);
19261
- if (mapPreamble)
19262
- ls.push(` ${mapPreamble}`);
19591
+ ls.push(` if (${param}) {`);
19592
+ if (preambleLine)
19593
+ ls.push(` ${preambleLine}`);
19263
19594
  if (idxLine2)
19264
- ls.push(` if (${param}) { ${idxLine2}; ${handlerCall} }`);
19265
- else
19266
- ls.push(` if (${param}) ${handlerCall}`);
19595
+ ls.push(` ${idxLine2}`);
19596
+ ls.push(` ;${handlerCall}`);
19597
+ ls.push(` }`);
19267
19598
  }
19268
19599
  ls.push(` }`);
19269
19600
  return;
@@ -19290,16 +19621,18 @@ function emitKeyedLookup(ls, ev, handlerCall, lookup) {
19290
19621
  }
19291
19622
  const outerGuard = hasBindings ? "__bfLoopItem" : param;
19292
19623
  const allParams = [outerGuard, ...ev.nestedLoops.map((n) => n.param)];
19293
- if (mapPreamble)
19294
- ls.push(` ${mapPreamble}`);
19295
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}`);
19296
19628
  if (idxLine)
19297
- ls.push(` if (${allParams.join(" && ")}) { ${idxLine}; ${handlerCall} }`);
19298
- else
19299
- ls.push(` if (${allParams.join(" && ")}) ${handlerCall}`);
19629
+ ls.push(` ${idxLine}`);
19630
+ ls.push(` ;${handlerCall}`);
19631
+ ls.push(` }`);
19300
19632
  }
19301
19633
  function emitDynamicIndexLookup(ls, ev, handlerCall, lookup) {
19302
- const { arrayExpr, param, mapPreamble, hasBindings, indexParam } = lookup;
19634
+ const { arrayExpr, param, mapPreamble, mapPreambleDeclaredNames, hasBindings, indexParam } = lookup;
19635
+ const preambleLine = preambleLineForHandler(mapPreamble, mapPreambleDeclaredNames, ev.handler);
19303
19636
  const idxLine = indexBindingLine(ev.handler, indexParam, "idx");
19304
19637
  ls.push(` const li = ${varSlotId(ev.childSlotId)}El.closest('li, [bf-i]')`);
19305
19638
  ls.push(` if (li && li.parentElement) {`);
@@ -19308,25 +19641,27 @@ function emitDynamicIndexLookup(ls, ev, handlerCall, lookup) {
19308
19641
  ls.push(` const __bfLoopItem = ${arrayExpr}[idx]`);
19309
19642
  ls.push(` if (__bfLoopItem) {`);
19310
19643
  ls.push(` const ${param} = __bfLoopItem`);
19311
- if (mapPreamble)
19312
- ls.push(` ${mapPreamble}`);
19644
+ if (preambleLine)
19645
+ ls.push(` ${preambleLine}`);
19313
19646
  if (idxLine)
19314
19647
  ls.push(` ${idxLine}`);
19315
- ls.push(` ${handlerCall}`);
19648
+ ls.push(` ;${handlerCall}`);
19316
19649
  ls.push(` }`);
19317
19650
  } else {
19318
19651
  ls.push(` const ${param} = ${arrayExpr}[idx]`);
19319
- if (mapPreamble)
19320
- ls.push(` ${mapPreamble}`);
19652
+ ls.push(` if (${param}) {`);
19653
+ if (preambleLine)
19654
+ ls.push(` ${preambleLine}`);
19321
19655
  if (idxLine)
19322
- ls.push(` if (${param}) { ${idxLine}; ${handlerCall} }`);
19323
- else
19324
- ls.push(` if (${param}) ${handlerCall}`);
19656
+ ls.push(` ${idxLine}`);
19657
+ ls.push(` ;${handlerCall}`);
19658
+ ls.push(` }`);
19325
19659
  }
19326
19660
  ls.push(` }`);
19327
19661
  }
19328
19662
  function emitStaticIndexLookup(ls, ev, handlerCall, lookup, containerVar) {
19329
- const { arrayExpr, param, mapPreamble, offset, indexParam } = lookup;
19663
+ const { arrayExpr, param, mapPreamble, mapPreambleDeclaredNames, offset, indexParam } = lookup;
19664
+ const preambleLine = preambleLineForHandler(mapPreamble, mapPreambleDeclaredNames, ev.handler);
19330
19665
  const idxLine = indexBindingLine(ev.handler, indexParam, "__idx");
19331
19666
  ls.push(` let __el = ${varSlotId(ev.childSlotId)}El`);
19332
19667
  ls.push(` while (__el.parentElement && __el.parentElement !== ${containerVar}) __el = __el.parentElement`);
@@ -19334,12 +19669,13 @@ function emitStaticIndexLookup(ls, ev, handlerCall, lookup, containerVar) {
19334
19669
  const idxOffset = buildLoopChildIndexSubtraction(offset ?? undefined);
19335
19670
  ls.push(` const __idx = Array.from(${containerVar}.children).indexOf(__el)${idxOffset}`);
19336
19671
  ls.push(` const ${param} = ${arrayExpr}[__idx]`);
19337
- if (mapPreamble)
19338
- ls.push(` ${mapPreamble}`);
19672
+ ls.push(` if (${param}) {`);
19673
+ if (preambleLine)
19674
+ ls.push(` ${preambleLine}`);
19339
19675
  if (idxLine)
19340
- ls.push(` if (${param}) { ${idxLine}; ${handlerCall} }`);
19341
- else
19342
- ls.push(` if (${param}) ${handlerCall}`);
19676
+ ls.push(` ${idxLine}`);
19677
+ ls.push(` ;${handlerCall}`);
19678
+ ls.push(` }`);
19343
19679
  ls.push(` }`);
19344
19680
  }
19345
19681
 
@@ -19372,12 +19708,29 @@ function emitPlain(lines, plan) {
19372
19708
  eventDelegation,
19373
19709
  childRefs,
19374
19710
  bodyIsMultiRoot,
19375
- profileLoopId
19711
+ profileLoopId,
19712
+ preambleRegions
19376
19713
  } = plan;
19377
19714
  const loopBfId = profileLoopId ? `, ${JSON.stringify(profileLoopId)}` : "";
19378
19715
  const unwrapInline = paramUnwrap ? `${paramUnwrap} ` : "";
19379
19716
  lines.push(` __disposers.push(createDisposableEffect(() => {`);
19380
- 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) {
19381
19734
  const cloneExpr = emitTemplateCloneInline(template);
19382
19735
  if (mapPreambleWrapped) {
19383
19736
  lines.push(` if (${containerVar}) mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => { ${unwrapInline}if (__existing) return __existing; ${mapPreambleWrapped}; ${cloneExpr} }, '${markerId}'${loopBfId})`);
@@ -19402,6 +19755,7 @@ function emitPlain(lines, plan) {
19402
19755
  stringifyReactiveEffects(lines, reactiveEffects, { indent: " ", elVar: "__el", bodyIsMultiRoot });
19403
19756
  }
19404
19757
  emitLoopChildRefs(lines, childRefs, { indent: " ", elVar: "__el", bodyIsMultiRoot });
19758
+ emitPreambleRegionEffects(lines, preambleRegions, mapPreambleWrapped, { indent: " ", elVar: "__el" });
19405
19759
  lines.push(` return __el`);
19406
19760
  lines.push(` }, '${markerId}'${loopBfId})`);
19407
19761
  }
@@ -19587,6 +19941,29 @@ function buildPlainLoopPlan(elem, profileComponentName) {
19587
19941
  const wrap = (expr) => wrapLoopParamAsAccessor(expr, elem.param, elem.paramBindings);
19588
19942
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(elem.param, elem.paramBindings);
19589
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
+ }
19590
19967
  return {
19591
19968
  kind: "plain",
19592
19969
  rowConstruction: "string-template",
@@ -19607,6 +19984,7 @@ function buildPlainLoopPlan(elem, profileComponentName) {
19607
19984
  skeletonPaths: elem.skeletonPaths,
19608
19985
  reactiveEffects: hasReactive2 ? buildLoopReactiveEffectsPlan(elem, profileComponentName) : null,
19609
19986
  childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings),
19987
+ preambleRegions: buildPreambleRegionPlans(elem.preambleRegions, elem.param, elem.paramBindings),
19610
19988
  bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false,
19611
19989
  anchored: elem.bodyIsItemConditional ?? false,
19612
19990
  anchorKeyExpr: elem.key ? wrap(elem.key) : elem.index || "__idx"