@barefootjs/jsx 0.18.5 → 0.18.7

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 (88) hide show
  1. package/dist/adapters/dangerous-inner-html.d.ts +77 -0
  2. package/dist/adapters/dangerous-inner-html.d.ts.map +1 -0
  3. package/dist/adapters/loop-bound-names.d.ts +22 -0
  4. package/dist/adapters/loop-bound-names.d.ts.map +1 -0
  5. package/dist/adapters/parsed-expr-emitter.d.ts.map +1 -1
  6. package/dist/analyzer.d.ts.map +1 -1
  7. package/dist/index.d.ts +14 -4
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +977 -273
  10. package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
  11. package/dist/ir-to-client-js/control-flow/plan/build-inner-loop.d.ts.map +1 -1
  12. package/dist/ir-to-client-js/control-flow/plan/build-loop-child-arm.d.ts.map +1 -1
  13. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
  14. package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts +9 -0
  15. package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts.map +1 -1
  16. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +9 -0
  17. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
  18. package/dist/ir-to-client-js/control-flow/shared.d.ts +45 -3
  19. package/dist/ir-to-client-js/control-flow/shared.d.ts.map +1 -1
  20. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts.map +1 -1
  21. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts +1 -0
  22. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
  23. package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts +10 -0
  24. package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts.map +1 -1
  25. package/dist/ir-to-client-js/control-flow/stringify/skeleton-paths.d.ts +38 -0
  26. package/dist/ir-to-client-js/control-flow/stringify/skeleton-paths.d.ts.map +1 -0
  27. package/dist/ir-to-client-js/control-flow/stringify/template-parse.d.ts +19 -0
  28. package/dist/ir-to-client-js/control-flow/stringify/template-parse.d.ts.map +1 -1
  29. package/dist/ir-to-client-js/csr-substitute.d.ts +23 -0
  30. package/dist/ir-to-client-js/csr-substitute.d.ts.map +1 -1
  31. package/dist/ir-to-client-js/html-template.d.ts +42 -0
  32. package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
  33. package/dist/ir-to-client-js/imports.d.ts +2 -2
  34. package/dist/ir-to-client-js/imports.d.ts.map +1 -1
  35. package/dist/ir-to-client-js/plan/static-array-child-init.d.ts +20 -1
  36. package/dist/ir-to-client-js/plan/static-array-child-init.d.ts.map +1 -1
  37. package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
  38. package/dist/ir-to-client-js/stringify/static-array-child-init.d.ts +1 -1
  39. package/dist/ir-to-client-js/types.d.ts +21 -0
  40. package/dist/ir-to-client-js/types.d.ts.map +1 -1
  41. package/dist/jsx-to-ir.d.ts.map +1 -1
  42. package/dist/signal-init-eval.d.ts +82 -0
  43. package/dist/signal-init-eval.d.ts.map +1 -0
  44. package/dist/static-literal.d.ts +45 -0
  45. package/dist/static-literal.d.ts.map +1 -0
  46. package/package.json +2 -2
  47. package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +19 -13
  48. package/src/__tests__/create-selector.test.ts +110 -0
  49. package/src/__tests__/csr-template-loop-shadowing.test.ts +214 -0
  50. package/src/__tests__/dangerous-inner-html-resolver.test.ts +197 -0
  51. package/src/__tests__/inner-loop-svg-namespace.test.ts +245 -0
  52. package/src/__tests__/loop-bound-names.test.ts +102 -0
  53. package/src/__tests__/loop-plan-classification.test.ts +2 -0
  54. package/src/__tests__/map-function-reference.test.ts +303 -0
  55. package/src/__tests__/nested-loop-index-param.test.ts +285 -0
  56. package/src/__tests__/signal-init-eval.test.ts +138 -0
  57. package/src/__tests__/static-array-inner-loop-index-param.test.ts +180 -0
  58. package/src/__tests__/static-literal.test.ts +126 -0
  59. package/src/__tests__/string-concat-identifier.test.ts +55 -0
  60. package/src/adapters/dangerous-inner-html.ts +186 -0
  61. package/src/adapters/loop-bound-names.ts +80 -0
  62. package/src/adapters/parsed-expr-emitter.ts +9 -0
  63. package/src/analyzer.ts +6 -0
  64. package/src/index.ts +19 -3
  65. package/src/ir-to-client-js/collect-elements.ts +15 -3
  66. package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +21 -4
  67. package/src/ir-to-client-js/control-flow/plan/build-loop-child-arm.ts +13 -1
  68. package/src/ir-to-client-js/control-flow/plan/build-loop.ts +1 -0
  69. package/src/ir-to-client-js/control-flow/plan/loop-child-arm.ts +9 -0
  70. package/src/ir-to-client-js/control-flow/plan/loop.ts +9 -0
  71. package/src/ir-to-client-js/control-flow/shared.ts +103 -6
  72. package/src/ir-to-client-js/control-flow/stringify/inner-loop.ts +11 -2
  73. package/src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts +5 -0
  74. package/src/ir-to-client-js/control-flow/stringify/loop.ts +53 -10
  75. package/src/ir-to-client-js/control-flow/stringify/reactive-effects.ts +25 -4
  76. package/src/ir-to-client-js/control-flow/stringify/skeleton-paths.ts +70 -0
  77. package/src/ir-to-client-js/control-flow/stringify/template-parse.ts +25 -1
  78. package/src/ir-to-client-js/csr-substitute.ts +53 -0
  79. package/src/ir-to-client-js/html-template.ts +235 -2
  80. package/src/ir-to-client-js/imports.ts +1 -0
  81. package/src/ir-to-client-js/plan/build-static-array-child-init.ts +12 -1
  82. package/src/ir-to-client-js/plan/static-array-child-init.ts +20 -1
  83. package/src/ir-to-client-js/reactivity.ts +6 -0
  84. package/src/ir-to-client-js/stringify/static-array-child-init.ts +11 -6
  85. package/src/ir-to-client-js/types.ts +21 -0
  86. package/src/jsx-to-ir.ts +230 -39
  87. package/src/signal-init-eval.ts +165 -0
  88. package/src/static-literal.ts +128 -0
package/dist/index.js CHANGED
@@ -2942,6 +2942,29 @@ function extractFreeIdentifiersFromText(text) {
2942
2942
  const expr = ts4.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
2943
2943
  return extractFreeIdentifiersFromNode(expr);
2944
2944
  }
2945
+ function extractFreeIdentifiersFromStatementText(text) {
2946
+ if (!text || text.trim().length === 0)
2947
+ return new Set;
2948
+ const sf = ts4.createSourceFile("__free_ids_stmt__.ts", text, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TS);
2949
+ return extractFreeIdentifiersFromNode(sf);
2950
+ }
2951
+ function extractFreeIdentifiersFromTemplateText(template) {
2952
+ if (!template || template.length === 0)
2953
+ return new Set;
2954
+ const sf = ts4.createSourceFile("__free_ids_template__.ts", `(\`${template}\`);`, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TS);
2955
+ const stmt = sf.statements[0];
2956
+ if (!stmt || !ts4.isExpressionStatement(stmt))
2957
+ return new Set;
2958
+ const expr = ts4.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
2959
+ if (!ts4.isTemplateExpression(expr))
2960
+ return new Set;
2961
+ const ids = new Set;
2962
+ for (const span of expr.templateSpans) {
2963
+ for (const id of extractFreeIdentifiersFromNode(span.expression))
2964
+ ids.add(id);
2965
+ }
2966
+ return ids;
2967
+ }
2945
2968
  function extractMemoBodyExpr(computation) {
2946
2969
  const arrowMatch = computation.match(/^\(\)\s*=>\s*(.+)$/s);
2947
2970
  if (!arrowMatch)
@@ -3470,6 +3493,133 @@ function buildLoopSkeletonTemplate(node, safe) {
3470
3493
  return assertNever(node);
3471
3494
  }
3472
3495
  }
3496
+ var SKELETON_PATH_HAZARD_TAGS = new Set([
3497
+ "table",
3498
+ "thead",
3499
+ "tbody",
3500
+ "tfoot",
3501
+ "caption",
3502
+ "colgroup",
3503
+ "col",
3504
+ "select",
3505
+ "optgroup",
3506
+ "p",
3507
+ "pre",
3508
+ "textarea",
3509
+ "listing",
3510
+ "template",
3511
+ "math"
3512
+ ]);
3513
+ var SKELETON_PATH_FORCE_CLOSE_GROUPS = [
3514
+ new Set(["a"]),
3515
+ new Set(["button"]),
3516
+ new Set(["form"]),
3517
+ new Set(["option"]),
3518
+ new Set(["h1", "h2", "h3", "h4", "h5", "h6"]),
3519
+ new Set(["dd", "dt"]),
3520
+ new Set(["li"])
3521
+ ];
3522
+ function skeletonForceCloseGroup(tag) {
3523
+ return SKELETON_PATH_FORCE_CLOSE_GROUPS.findIndex((group) => group.has(tag));
3524
+ }
3525
+ function computeSkeletonSlotPaths(node, safe) {
3526
+ const state = { elementPaths: new Map, textMarkerPaths: new Map, bailed: false };
3527
+ walkSkeletonPathNode(node, [], safe, state, new Set);
3528
+ if (state.bailed)
3529
+ return null;
3530
+ return { elementPaths: state.elementPaths, textMarkerPaths: state.textMarkerPaths };
3531
+ }
3532
+ function walkSkeletonPathNode(node, path, safe, state, forceCloseAncestors) {
3533
+ if (state.bailed || node.type !== "element")
3534
+ return;
3535
+ if (SKELETON_PATH_HAZARD_TAGS.has(node.tag)) {
3536
+ state.bailed = true;
3537
+ return;
3538
+ }
3539
+ const groupIdx = skeletonForceCloseGroup(node.tag);
3540
+ if (groupIdx >= 0 && forceCloseAncestors.has(groupIdx)) {
3541
+ state.bailed = true;
3542
+ return;
3543
+ }
3544
+ const flatChildren = flattenSkeletonChildren(node.children);
3545
+ if (VOID_ELEMENTS.has(node.tag) && flatChildren.length > 0) {
3546
+ state.bailed = true;
3547
+ return;
3548
+ }
3549
+ if (node.tag === "tr" && hasForeignTableRowContent(flatChildren)) {
3550
+ state.bailed = true;
3551
+ return;
3552
+ }
3553
+ if (node.slotId)
3554
+ state.elementPaths.set(node.slotId, path);
3555
+ const nextAncestors = groupIdx >= 0 ? new Set([...forceCloseAncestors, groupIdx]) : forceCloseAncestors;
3556
+ walkSkeletonPathChildren(flatChildren, path, safe, state, nextAncestors);
3557
+ }
3558
+ function hasForeignTableRowContent(children) {
3559
+ for (const child of children) {
3560
+ if (child.type === "text") {
3561
+ if (child.value.trim() !== "")
3562
+ return true;
3563
+ continue;
3564
+ }
3565
+ if (child.type === "element" && child.tag !== "td" && child.tag !== "th") {
3566
+ return true;
3567
+ }
3568
+ }
3569
+ return false;
3570
+ }
3571
+ function flattenSkeletonChildren(children) {
3572
+ const out = [];
3573
+ for (const child of children) {
3574
+ if (child.type === "fragment") {
3575
+ out.push(...flattenSkeletonChildren(child.children));
3576
+ } else {
3577
+ out.push(child);
3578
+ }
3579
+ }
3580
+ return out;
3581
+ }
3582
+ function walkSkeletonPathChildren(children, parentPath, safe, state, forceCloseAncestors) {
3583
+ let idx = 0;
3584
+ let pendingText = false;
3585
+ for (const child of children) {
3586
+ if (state.bailed)
3587
+ return;
3588
+ switch (child.type) {
3589
+ case "text": {
3590
+ if (child.value === "")
3591
+ continue;
3592
+ if (!pendingText)
3593
+ idx += 1;
3594
+ pendingText = true;
3595
+ continue;
3596
+ }
3597
+ case "expression": {
3598
+ if (child.expr === "null" || child.expr === "undefined")
3599
+ continue;
3600
+ if (!child.slotId || !safe.reactiveTextSlotIds.has(child.slotId)) {
3601
+ state.bailed = true;
3602
+ return;
3603
+ }
3604
+ state.textMarkerPaths.set(child.slotId, [...parentPath, idx]);
3605
+ idx += 2;
3606
+ pendingText = false;
3607
+ continue;
3608
+ }
3609
+ case "element": {
3610
+ walkSkeletonPathNode(child, [...parentPath, idx], safe, state, forceCloseAncestors);
3611
+ idx += 1;
3612
+ pendingText = false;
3613
+ continue;
3614
+ }
3615
+ case "fragment":
3616
+ continue;
3617
+ default:
3618
+ state.bailed = true;
3619
+ return;
3620
+ }
3621
+ }
3622
+ }
3473
3623
  function irToPlaceholderTemplate(node, restSpreadNames, loopDepth = 0, loopParams) {
3474
3624
  const recurse = (n) => irToPlaceholderTemplate(n, restSpreadNames, loopDepth, loopParams);
3475
3625
  const wrapExpr = (expr) => wrapExprWithLoopParams(expr, loopParams);
@@ -4112,7 +4262,28 @@ function generateCsrTemplateWithOpts(node, opts) {
4112
4262
  return `\${renderChild('${nameForRegistryRef(node.name)}', ${propsExpr}${keyArg || (slotArg ? ", undefined" : "")}${slotArg})}`;
4113
4263
  }
4114
4264
  case "loop": {
4115
- let childTemplate = node.children.map(recurseInLoop).join("");
4265
+ const boundHere = new Set(opts.loopBoundNames ?? []);
4266
+ if (node.paramBindings && node.paramBindings.length > 0) {
4267
+ for (const b of node.paramBindings)
4268
+ boundHere.add(b.name);
4269
+ } else if (!node.param.startsWith("[") && !node.param.startsWith("{")) {
4270
+ boundHere.add(node.param);
4271
+ }
4272
+ if (node.index)
4273
+ boundHere.add(node.index);
4274
+ const childEnv = {
4275
+ ...env,
4276
+ substitutions: new Map([...env.substitutions].filter(([name]) => !boundHere.has(name)))
4277
+ };
4278
+ const recurseInLoopBody = (n) => generateCsrTemplateWithOpts(n, {
4279
+ ...opts,
4280
+ insideLoop: true,
4281
+ loopDepth: loopDepth + 1,
4282
+ inHoistedChildren: false,
4283
+ loopBoundNames: boundHere,
4284
+ csrEnv: childEnv
4285
+ });
4286
+ let childTemplate = node.children.map(recurseInLoopBody).join("");
4116
4287
  if (node.bodyIsItemConditional && node.key) {
4117
4288
  childTemplate = `${itemAnchorTemplate(node.key)}${childTemplate}`;
4118
4289
  }
@@ -4126,7 +4297,7 @@ function generateCsrTemplateWithOpts(node, opts) {
4126
4297
  if (node.flatMapCallback) {
4127
4298
  let body = node.flatMapCallback.templateBody ?? node.flatMapCallback.body;
4128
4299
  for (const frag of node.flatMapCallback.fragments) {
4129
- const renderedIr = recurseInLoop(frag.ir);
4300
+ const renderedIr = recurseInLoopBody(frag.ir);
4130
4301
  body = body.replace(frag.placeholder, `\`${renderedIr}\``);
4131
4302
  }
4132
4303
  body = applyPropsRewrite(body, propsObjectName ?? null);
@@ -5086,6 +5257,8 @@ function needsTypeBasedDetection(source) {
5086
5257
  return true;
5087
5258
  if (/\.map\s*\(/.test(source))
5088
5259
  return true;
5260
+ if (source.includes("createSelector"))
5261
+ return true;
5089
5262
  return false;
5090
5263
  }
5091
5264
  function findBrandPackageImportLoc(sourceFile, filePath) {
@@ -6062,6 +6235,7 @@ var CLIENT_EXPORTS = new Set([
6062
6235
  "createEffect",
6063
6236
  "createDisposableEffect",
6064
6237
  "createMemo",
6238
+ "createSelector",
6065
6239
  "createRoot",
6066
6240
  "onCleanup",
6067
6241
  "onMount",
@@ -8148,9 +8322,15 @@ function exprHasFunctionCalls(expr) {
8148
8322
  return found;
8149
8323
  }
8150
8324
  function rewriteBarePropRefs2(text, expr, ctx) {
8151
- const propNames = getDestructuredPropNames(ctx);
8325
+ let propNames = getDestructuredPropNames(ctx);
8152
8326
  if (!propNames)
8153
8327
  return;
8328
+ if (ctx.loopParams.size > 0) {
8329
+ const filtered = new Set([...propNames].filter((n) => !ctx.loopParams.has(n)));
8330
+ if (filtered.size === 0)
8331
+ return;
8332
+ propNames = filtered;
8333
+ }
8154
8334
  const extraPropRefs = collectBranchLocalPropRefsViaSubstitution(expr, ctx);
8155
8335
  return rewriteBarePropRefs(text, expr, propNames, extraPropRefs);
8156
8336
  }
@@ -8316,26 +8496,29 @@ function makeBindingEnv(ctx) {
8316
8496
  function parseValueExpr(trimmed) {
8317
8497
  return parseExpression(trimmed.startsWith("{") ? `(${trimmed})` : trimmed);
8318
8498
  }
8319
- function attachParsedExpressions(node) {
8499
+ var EMPTY_BOUND = new Set;
8500
+ function attachParsedExpressions(node, analyzer, bound = EMPTY_BOUND) {
8501
+ const parse2 = (trimmed) => resolveCallbackMethodFunctionReferences(parseExpression(trimmed), analyzer, bound);
8502
+ const parseValue = (trimmed) => resolveCallbackMethodFunctionReferences(parseValueExpr(trimmed), analyzer, bound);
8320
8503
  if (node.type === "expression") {
8321
8504
  const trimmed = node.expr.trim();
8322
8505
  if (trimmed)
8323
- node.parsed = parseExpression(trimmed);
8506
+ node.parsed = parse2(trimmed);
8324
8507
  } else if (node.type === "conditional" || node.type === "if-statement") {
8325
8508
  const trimmed = node.condition.trim();
8326
8509
  if (trimmed)
8327
- node.parsedCondition = parseExpression(trimmed);
8510
+ node.parsedCondition = parse2(trimmed);
8328
8511
  }
8329
8512
  if (node.type === "element") {
8330
8513
  for (const attr of node.attrs) {
8331
8514
  if (attr.value.kind === "expression") {
8332
8515
  const trimmed = attr.value.expr.trim();
8333
8516
  if (trimmed)
8334
- attr.value.parsed = parseValueExpr(trimmed);
8517
+ attr.value.parsed = parseValue(trimmed);
8335
8518
  } else if (attr.value.kind === "spread") {
8336
8519
  const trimmed = attr.value.expr.trim();
8337
8520
  if (trimmed)
8338
- attr.value.parsed = parseExpression(trimmed);
8521
+ attr.value.parsed = parse2(trimmed);
8339
8522
  }
8340
8523
  }
8341
8524
  } else if (node.type === "component") {
@@ -8343,14 +8526,14 @@ function attachParsedExpressions(node) {
8343
8526
  if (prop.value.kind === "expression") {
8344
8527
  const trimmed = prop.value.expr.trim();
8345
8528
  if (trimmed)
8346
- prop.value.parsed = parseValueExpr(trimmed);
8529
+ prop.value.parsed = parseValue(trimmed);
8347
8530
  }
8348
8531
  }
8349
8532
  } else if (node.type === "provider") {
8350
8533
  if (node.valueProp.value.kind === "expression") {
8351
8534
  const trimmed = node.valueProp.value.expr.trim();
8352
8535
  if (trimmed)
8353
- node.valueProp.value.parsed = parseValueExpr(trimmed);
8536
+ node.valueProp.value.parsed = parseValue(trimmed);
8354
8537
  }
8355
8538
  }
8356
8539
  switch (node.type) {
@@ -8359,47 +8542,51 @@ function attachParsedExpressions(node) {
8359
8542
  case "fragment":
8360
8543
  case "provider":
8361
8544
  for (const child of node.children)
8362
- attachParsedExpressions(child);
8545
+ attachParsedExpressions(child, analyzer, bound);
8363
8546
  break;
8364
8547
  case "async":
8365
- attachParsedExpressions(node.fallback);
8548
+ attachParsedExpressions(node.fallback, analyzer, bound);
8366
8549
  for (const child of node.children)
8367
- attachParsedExpressions(child);
8550
+ attachParsedExpressions(child, analyzer, bound);
8368
8551
  break;
8369
8552
  case "loop": {
8370
8553
  const trimmedArray = node.array.trim();
8371
8554
  if (trimmedArray)
8372
- node.arrayParsed = parseExpression(trimmedArray);
8555
+ node.arrayParsed = parse2(trimmedArray);
8556
+ const loopBound = new Set(bound);
8557
+ loopBound.add(node.param);
8558
+ if (node.index)
8559
+ loopBound.add(node.index);
8373
8560
  for (const child of node.children)
8374
- attachParsedExpressions(child);
8561
+ attachParsedExpressions(child, analyzer, loopBound);
8375
8562
  if (node.childComponent) {
8376
8563
  for (const child of node.childComponent.children)
8377
- attachParsedExpressions(child);
8564
+ attachParsedExpressions(child, analyzer, loopBound);
8378
8565
  }
8379
8566
  for (const nested of node.nestedComponents ?? []) {
8380
8567
  for (const child of nested.children)
8381
- attachParsedExpressions(child);
8568
+ attachParsedExpressions(child, analyzer, loopBound);
8382
8569
  }
8383
8570
  for (const frag of node.flatMapCallback?.fragments ?? []) {
8384
- attachParsedExpressions(frag.ir);
8571
+ attachParsedExpressions(frag.ir, analyzer, loopBound);
8385
8572
  }
8386
8573
  break;
8387
8574
  }
8388
8575
  case "conditional":
8389
- attachParsedExpressions(node.whenTrue);
8390
- attachParsedExpressions(node.whenFalse);
8576
+ attachParsedExpressions(node.whenTrue, analyzer, bound);
8577
+ attachParsedExpressions(node.whenFalse, analyzer, bound);
8391
8578
  break;
8392
8579
  case "if-statement":
8393
- attachParsedExpressions(node.consequent);
8580
+ attachParsedExpressions(node.consequent, analyzer, bound);
8394
8581
  if (node.alternate)
8395
- attachParsedExpressions(node.alternate);
8582
+ attachParsedExpressions(node.alternate, analyzer, bound);
8396
8583
  break;
8397
8584
  }
8398
8585
  }
8399
8586
  function jsxToIR(analyzer) {
8400
8587
  const root = buildIRRoot(analyzer);
8401
8588
  if (root)
8402
- attachParsedExpressions(root);
8589
+ attachParsedExpressions(root, analyzer);
8403
8590
  return root;
8404
8591
  }
8405
8592
  function buildIRRoot(analyzer) {
@@ -9486,8 +9673,23 @@ function extractSortComparator(callback, _method, ctx) {
9486
9673
  };
9487
9674
  }
9488
9675
  function resolveSortComparatorIdentifier(name, ctx) {
9489
- const constInfo = findLocalConst(name, ctx);
9490
- const fnInfo = findLocalFunction(name, ctx);
9676
+ const constInfo = findLocalConst(name, ctx.analyzer);
9677
+ const fnInfo = findLocalFunction(name, ctx.analyzer);
9678
+ if (constInfo && fnInfo)
9679
+ return null;
9680
+ if (constInfo) {
9681
+ const ast = parseConstInitializer(constInfo);
9682
+ return ast && (ts11.isArrowFunction(ast) || ts11.isFunctionExpression(ast)) ? ast : null;
9683
+ }
9684
+ if (fnInfo) {
9685
+ const ast = parseFunctionInfoAsExpr(fnInfo);
9686
+ return ast && (ts11.isArrowFunction(ast) || ts11.isFunctionExpression(ast)) ? ast : null;
9687
+ }
9688
+ return null;
9689
+ }
9690
+ function resolveCallbackMethodFunctionReferenceIdentifier(name, analyzer) {
9691
+ const constInfo = findLocalConst(name, analyzer);
9692
+ const fnInfo = findLocalFunction(name, analyzer);
9491
9693
  if (constInfo && fnInfo)
9492
9694
  return null;
9493
9695
  if (constInfo) {
@@ -9500,6 +9702,57 @@ function resolveSortComparatorIdentifier(name, ctx) {
9500
9702
  }
9501
9703
  return null;
9502
9704
  }
9705
+ function resolveCallbackMethodFunctionReferences(expr, analyzer, bound = EMPTY_BOUND) {
9706
+ function visit2(e, bound2) {
9707
+ switch (e.kind) {
9708
+ case "literal":
9709
+ case "identifier":
9710
+ case "regex":
9711
+ case "unsupported":
9712
+ return e;
9713
+ case "call": {
9714
+ const callee = visit2(e.callee, bound2);
9715
+ const args = e.args.map((a) => visit2(a, bound2));
9716
+ if (callee.kind === "member" && !callee.computed && CALLBACK_METHODS.has(callee.property) && args[0]?.kind === "identifier" && !bound2.has(args[0].name)) {
9717
+ const resolved = resolveCallbackMethodFunctionReferenceIdentifier(args[0].name, analyzer);
9718
+ const arrow = resolved ? tsNodeToParsedExpr(resolved) : null;
9719
+ if (arrow && arrow.kind === "arrow")
9720
+ args[0] = arrow;
9721
+ }
9722
+ return { ...e, callee, args };
9723
+ }
9724
+ case "member":
9725
+ return { ...e, object: visit2(e.object, bound2) };
9726
+ case "index-access":
9727
+ return { ...e, object: visit2(e.object, bound2), index: visit2(e.index, bound2) };
9728
+ case "binary":
9729
+ case "logical":
9730
+ return { ...e, left: visit2(e.left, bound2), right: visit2(e.right, bound2) };
9731
+ case "unary":
9732
+ return { ...e, argument: visit2(e.argument, bound2) };
9733
+ case "conditional":
9734
+ return { ...e, test: visit2(e.test, bound2), consequent: visit2(e.consequent, bound2), alternate: visit2(e.alternate, bound2) };
9735
+ case "template-literal":
9736
+ return { ...e, parts: e.parts.map((p) => p.type === "expression" ? { ...p, expr: visit2(p.expr, bound2) } : p) };
9737
+ case "array-literal":
9738
+ return { ...e, elements: e.elements.map((el) => visit2(el, bound2)) };
9739
+ case "array-method":
9740
+ return {
9741
+ ...e,
9742
+ object: visit2(e.object, bound2),
9743
+ args: e.args.map((a) => visit2(a, bound2)),
9744
+ ...e.method === "flat" && e.depthExpr ? { depthExpr: visit2(e.depthExpr, bound2) } : {}
9745
+ };
9746
+ case "object-literal":
9747
+ return { ...e, properties: e.properties.map((p) => ({ ...p, value: visit2(p.value, bound2) })) };
9748
+ case "arrow": {
9749
+ const inner = e.params.length === 0 ? bound2 : new Set([...bound2, ...e.params]);
9750
+ return { ...e, body: visit2(e.body, inner) };
9751
+ }
9752
+ }
9753
+ }
9754
+ return visit2(expr, bound);
9755
+ }
9503
9756
  function extractFilterPredicate(callback, ctx) {
9504
9757
  if (!ts11.isArrowFunction(callback))
9505
9758
  return { result: null };
@@ -9967,13 +10220,11 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
9967
10220
  setArray(innerSort.array);
9968
10221
  }
9969
10222
  } else {
9970
- array = ctx.getJS(filterInfo.array);
9971
- arrayExpr = filterInfo.array;
10223
+ setArray(filterInfo.array);
9972
10224
  }
9973
10225
  }
9974
10226
  } else {
9975
- array = ctx.getJS(chainSource);
9976
- arrayExpr = chainSource;
10227
+ setArray(chainSource);
9977
10228
  }
9978
10229
  const callback = node.arguments[0];
9979
10230
  let param = "item";
@@ -10523,7 +10774,7 @@ function parseTemplateLiteral(expr, ctx) {
10523
10774
  }
10524
10775
  function tryResolveTemplateSpanFromConst(expr, ctx) {
10525
10776
  if (ts11.isIdentifier(expr)) {
10526
- const constInfo = findLocalConst(expr.text, ctx);
10777
+ const constInfo = findLocalConst(expr.text, ctx.analyzer);
10527
10778
  if (!constInfo)
10528
10779
  return null;
10529
10780
  const ast = parseConstInitializer(constInfo);
@@ -10537,7 +10788,7 @@ function tryResolveTemplateSpanFromConst(expr, ctx) {
10537
10788
  if (ts11.isElementAccessExpression(expr)) {
10538
10789
  if (!ts11.isIdentifier(expr.expression))
10539
10790
  return null;
10540
- const constInfo = findLocalConst(expr.expression.text, ctx);
10791
+ const constInfo = findLocalConst(expr.expression.text, ctx.analyzer);
10541
10792
  if (!constInfo)
10542
10793
  return null;
10543
10794
  const ast = parseConstInitializer(constInfo);
@@ -10563,16 +10814,16 @@ function tryResolveTemplateSpanFromConst(expr, ctx) {
10563
10814
  }
10564
10815
  return null;
10565
10816
  }
10566
- function findLocalConst(name, ctx) {
10567
- const matches = ctx.analyzer.localConstants.filter((c) => c.name === name);
10817
+ function findLocalConst(name, analyzer) {
10818
+ const matches = analyzer.localConstants.filter((c) => c.name === name);
10568
10819
  if (matches.length === 0)
10569
10820
  return;
10570
10821
  const fnScoped = matches.filter((c) => !c.isModule);
10571
10822
  const pool = fnScoped.length > 0 ? fnScoped : matches;
10572
10823
  return pool[pool.length - 1];
10573
10824
  }
10574
- function findLocalFunction(name, ctx) {
10575
- const matches = ctx.analyzer.localFunctions.filter((f) => f.name === name);
10825
+ function findLocalFunction(name, analyzer) {
10826
+ const matches = analyzer.localFunctions.filter((f) => f.name === name);
10576
10827
  if (matches.length === 0)
10577
10828
  return;
10578
10829
  const fnScoped = matches.filter((f) => !f.isModule);
@@ -10614,7 +10865,9 @@ function hasDynamicTagBinding(name, sourceFile) {
10614
10865
  return found;
10615
10866
  }
10616
10867
  function tryResolveIdentifierAsTemplateLiteral(ident, ctx) {
10617
- const constInfo = findLocalConst(ident.text, ctx);
10868
+ if (ctx.loopParams.has(ident.text))
10869
+ return null;
10870
+ const constInfo = findLocalConst(ident.text, ctx.analyzer);
10618
10871
  if (!constInfo)
10619
10872
  return null;
10620
10873
  const ast = parseConstInitializer(constInfo);
@@ -10711,8 +10964,8 @@ function tryDesugarInterleaveTaggedTemplate(expr, ctx) {
10711
10964
  return rewritten ?? expr;
10712
10965
  }
10713
10966
  function resolveInterleaveTagIdentifier(name, ctx) {
10714
- const constInfo = findLocalConst(name, ctx);
10715
- const fnInfo = findLocalFunction(name, ctx);
10967
+ const constInfo = findLocalConst(name, ctx.analyzer);
10968
+ const fnInfo = findLocalFunction(name, ctx.analyzer);
10716
10969
  if (constInfo && fnInfo)
10717
10970
  return null;
10718
10971
  if (constInfo) {
@@ -11466,6 +11719,7 @@ function collectLoopChildEventsWithNesting(node, initialNestingStack = []) {
11466
11719
  depth: scope.nestingStack.length + 1,
11467
11720
  array: l.array,
11468
11721
  param: l.param,
11722
+ index: l.index,
11469
11723
  key: l.key,
11470
11724
  markerId: l.markerId,
11471
11725
  containerSlotId: scope.lastElementSlotId,
@@ -11556,76 +11810,227 @@ function collectLoopChildReactiveAttrs(node, ctx, loopParam, loopParamBindings)
11556
11810
  return attrs;
11557
11811
  }
11558
11812
 
11559
- // src/ir-to-client-js/collect-elements.ts
11560
- var EMPTY_RENDER_EXPRS = new Set(["null", "undefined", "false", "''", '""', "``"]);
11561
- function domElementCount(node) {
11562
- switch (node.type) {
11563
- case "element":
11564
- case "component":
11565
- case "provider":
11566
- case "async":
11567
- return 1;
11568
- case "text":
11569
- return 0;
11570
- case "expression":
11571
- return EMPTY_RENDER_EXPRS.has(node.expr.trim()) ? 0 : null;
11572
- case "loop":
11573
- if (node.bodyIsItemConditional || node.method === "flatMap")
11574
- return null;
11575
- return `(${buildLoopChainExpr({
11576
- base: node.array,
11577
- sortComparator: node.sortComparator,
11578
- filterPredicate: node.filterPredicate,
11579
- chainOrder: node.chainOrder
11580
- })}).length`;
11581
- case "conditional": {
11582
- const t = domElementCount(node.whenTrue);
11583
- const f = domElementCount(node.whenFalse);
11584
- if (t === null || f === null)
11585
- return null;
11586
- if (typeof t === "number" && typeof f === "number" && t === f)
11587
- return t;
11588
- return `(${node.condition} ? ${t} : ${f})`;
11589
- }
11590
- case "fragment":
11591
- return sumElementCounts(node.children);
11592
- default:
11593
- return null;
11813
+ // src/ir-to-client-js/control-flow/stringify/template-parse.ts
11814
+ var SVG_ROOT_TAGS = new Set([
11815
+ "svg",
11816
+ "path",
11817
+ "circle",
11818
+ "rect",
11819
+ "line",
11820
+ "polyline",
11821
+ "polygon",
11822
+ "ellipse",
11823
+ "text",
11824
+ "tspan",
11825
+ "textPath",
11826
+ "g",
11827
+ "defs",
11828
+ "use",
11829
+ "symbol",
11830
+ "switch",
11831
+ "clipPath",
11832
+ "mask",
11833
+ "marker",
11834
+ "pattern",
11835
+ "linearGradient",
11836
+ "radialGradient",
11837
+ "stop",
11838
+ "image",
11839
+ "foreignObject",
11840
+ "filter",
11841
+ "feBlend",
11842
+ "feColorMatrix",
11843
+ "feComposite",
11844
+ "feFlood",
11845
+ "feGaussianBlur",
11846
+ "feMerge",
11847
+ "feMergeNode",
11848
+ "feMorphology",
11849
+ "feOffset",
11850
+ "feTurbulence",
11851
+ "animate",
11852
+ "animateTransform",
11853
+ "animateMotion"
11854
+ ]);
11855
+ function templateRootIsSvg(template) {
11856
+ const stripped = stripLeadingNonContent(template);
11857
+ const m = stripped.match(/^<\s*([A-Za-z][A-Za-z0-9-]*)/);
11858
+ if (m) {
11859
+ const tag = m[1];
11860
+ if (SVG_ROOT_TAGS.has(tag))
11861
+ return true;
11862
+ return SVG_ROOT_TAGS.has(tag.toLowerCase());
11594
11863
  }
11864
+ const branches = extractConditionalBranchTemplates(stripped);
11865
+ if (branches === null || branches.length === 0)
11866
+ return false;
11867
+ return branches.every(templateRootIsSvg);
11595
11868
  }
11596
- function sumElementCounts(nodes) {
11597
- let staticCount = 0;
11598
- const dynamic = [];
11599
- for (const n of nodes) {
11600
- const c = domElementCount(n);
11601
- if (c === null)
11602
- return null;
11603
- if (typeof c === "number")
11604
- staticCount += c;
11605
- else
11606
- dynamic.push(c);
11869
+ function multiRootTemplateNeedsSvgWrap(template) {
11870
+ const m = stripLeadingNonContent(template).match(/^<\s*([A-Za-z][A-Za-z0-9-]*)/);
11871
+ if (m && m[1].toLowerCase() === "svg")
11872
+ return false;
11873
+ return templateRootIsSvg(template);
11874
+ }
11875
+ function stripLeadingNonContent(template) {
11876
+ let s = template.trimStart();
11877
+ while (s.startsWith("<!--")) {
11878
+ const end = s.indexOf("-->");
11879
+ if (end < 0)
11880
+ return s;
11881
+ s = s.slice(end + 3).trimStart();
11607
11882
  }
11608
- if (dynamic.length === 0)
11609
- return staticCount;
11610
- const parts = staticCount > 0 ? [String(staticCount), ...dynamic] : dynamic;
11611
- return parts.length === 1 ? parts[0] : `(${parts.join(" + ")})`;
11883
+ return s;
11612
11884
  }
11613
- function legacyElementCount(node) {
11614
- return node.type === "element" || node.type === "component" || node.type === "provider" || node.type === "async" || node.type === "text" || node.type === "expression" && !node.reactive || node.type === "conditional" ? 1 : 0;
11885
+ function extractConditionalBranchTemplates(template) {
11886
+ if (!template.startsWith("${"))
11887
+ return null;
11888
+ const exprEnd = findInterpolationEnd(template, 2);
11889
+ if (exprEnd < 0)
11890
+ return null;
11891
+ const trailing = stripLeadingNonContent(template.slice(exprEnd + 1));
11892
+ if (trailing.length > 0)
11893
+ return null;
11894
+ const expr = template.slice(2, exprEnd);
11895
+ return findTopLevelTemplateLiterals(expr);
11615
11896
  }
11616
- function computeLoopSiblingOffsets(root) {
11617
- const offsets = new Map;
11618
- const recordRun = (children, preceding) => {
11619
- for (const child of children) {
11620
- if (child.type === "loop") {
11621
- if (preceding.length > 0 && !offsets.has(child)) {
11622
- offsets.set(child, [...preceding]);
11623
- }
11624
- preceding.push(child);
11625
- } else if (child.type === "fragment" || child.type === "provider" || child.type === "async") {
11626
- recordRun(child.children, preceding);
11627
- } else {
11628
- preceding.push(child);
11897
+ function emitTemplateCloneInline(template) {
11898
+ if (templateRootIsSvg(template)) {
11899
+ return `const __tpl = document.createElement('template'); __tpl.innerHTML = \`<svg>${template}</svg>\`; return __tpl.content.firstElementChild.firstElementChild.cloneNode(true)`;
11900
+ }
11901
+ return `const __tpl = document.createElement('template'); __tpl.innerHTML = \`${template}\`; return __tpl.content.firstElementChild.cloneNode(true)`;
11902
+ }
11903
+ function emitHoistedTemplateDecl(lines, indent, tplVar, skeletonTemplate) {
11904
+ const isSvg = templateRootIsSvg(skeletonTemplate);
11905
+ const html = isSvg ? `<svg>${skeletonTemplate}</svg>` : skeletonTemplate;
11906
+ lines.push(`${indent}const ${tplVar} = document.createElement('template')`);
11907
+ lines.push(`${indent}${tplVar}.innerHTML = \`${html}\``);
11908
+ }
11909
+ function hoistedCloneExpr(tplVar, skeletonTemplate) {
11910
+ return templateRootIsSvg(skeletonTemplate) ? `${tplVar}.content.firstElementChild.firstElementChild.cloneNode(true)` : `${tplVar}.content.firstElementChild.cloneNode(true)`;
11911
+ }
11912
+ function emitTemplateCloneLines(template, indent) {
11913
+ if (templateRootIsSvg(template)) {
11914
+ return [
11915
+ `${indent}const __tpl = document.createElement('template')`,
11916
+ `${indent}__tpl.innerHTML = \`<svg>${template}</svg>\``,
11917
+ `${indent}return __tpl.content.firstElementChild.firstElementChild.cloneNode(true)`
11918
+ ];
11919
+ }
11920
+ return [
11921
+ `${indent}const __tpl = document.createElement('template')`,
11922
+ `${indent}__tpl.innerHTML = \`${template}\``,
11923
+ `${indent}return __tpl.content.firstElementChild.cloneNode(true)`
11924
+ ];
11925
+ }
11926
+ function emitLoopItemElementSetup(lines, opts) {
11927
+ const { template, bodyIsMultiRoot, indent, singleRootLayout } = opts;
11928
+ const innerIndent = indent + " ";
11929
+ if (bodyIsMultiRoot) {
11930
+ lines.push(`${indent}let __el, __extras`);
11931
+ lines.push(`${indent}if (__existing) {`);
11932
+ lines.push(`${innerIndent}__el = __existing`);
11933
+ lines.push(`${indent}} else {`);
11934
+ for (const ln of emitMultiRootTemplateCloneLines(template, innerIndent, "__el", "__extras")) {
11935
+ lines.push(ln);
11936
+ }
11937
+ lines.push(`${innerIndent}__el.__bfExtras = __extras`);
11938
+ lines.push(`${indent}}`);
11939
+ return;
11940
+ }
11941
+ if (singleRootLayout === "inline") {
11942
+ const cloneExpr = emitTemplateCloneInline(template);
11943
+ lines.push(`${indent}const __el = __existing ?? (() => { ${cloneExpr} })()`);
11944
+ return;
11945
+ }
11946
+ lines.push(`${indent}const __el = __existing ?? (() => {`);
11947
+ for (const ln of emitTemplateCloneLines(template, innerIndent))
11948
+ lines.push(ln);
11949
+ lines.push(`${indent}})()`);
11950
+ }
11951
+ function emitMultiRootTemplateCloneLines(template, indent, varEl, varExtras) {
11952
+ const isSvg = multiRootTemplateNeedsSvgWrap(template);
11953
+ const innerHtmlExpr = isSvg ? `\`<svg>${template}</svg>\`` : `\`${template}\``;
11954
+ const parentExpr = isSvg ? `__tpl.content.firstElementChild` : `__tpl.content`;
11955
+ return [
11956
+ `${indent}const __tpl = document.createElement('template')`,
11957
+ `${indent}__tpl.innerHTML = ${innerHtmlExpr}`,
11958
+ `${indent}${varEl} = ${parentExpr}.firstElementChild.cloneNode(true)`,
11959
+ `${indent}${varExtras} = []`,
11960
+ `${indent}{ let __sib = ${parentExpr}.firstElementChild.nextElementSibling; while (__sib) { ${varExtras}.push(__sib.cloneNode(true)); __sib = __sib.nextElementSibling } }`
11961
+ ];
11962
+ }
11963
+
11964
+ // src/ir-to-client-js/collect-elements.ts
11965
+ var EMPTY_RENDER_EXPRS = new Set(["null", "undefined", "false", "''", '""', "``"]);
11966
+ function domElementCount(node) {
11967
+ switch (node.type) {
11968
+ case "element":
11969
+ case "component":
11970
+ case "provider":
11971
+ case "async":
11972
+ return 1;
11973
+ case "text":
11974
+ return 0;
11975
+ case "expression":
11976
+ return EMPTY_RENDER_EXPRS.has(node.expr.trim()) ? 0 : null;
11977
+ case "loop":
11978
+ if (node.bodyIsItemConditional || node.method === "flatMap")
11979
+ return null;
11980
+ return `(${buildLoopChainExpr({
11981
+ base: node.array,
11982
+ sortComparator: node.sortComparator,
11983
+ filterPredicate: node.filterPredicate,
11984
+ chainOrder: node.chainOrder
11985
+ })}).length`;
11986
+ case "conditional": {
11987
+ const t = domElementCount(node.whenTrue);
11988
+ const f = domElementCount(node.whenFalse);
11989
+ if (t === null || f === null)
11990
+ return null;
11991
+ if (typeof t === "number" && typeof f === "number" && t === f)
11992
+ return t;
11993
+ return `(${node.condition} ? ${t} : ${f})`;
11994
+ }
11995
+ case "fragment":
11996
+ return sumElementCounts(node.children);
11997
+ default:
11998
+ return null;
11999
+ }
12000
+ }
12001
+ function sumElementCounts(nodes) {
12002
+ let staticCount = 0;
12003
+ const dynamic = [];
12004
+ for (const n of nodes) {
12005
+ const c = domElementCount(n);
12006
+ if (c === null)
12007
+ return null;
12008
+ if (typeof c === "number")
12009
+ staticCount += c;
12010
+ else
12011
+ dynamic.push(c);
12012
+ }
12013
+ if (dynamic.length === 0)
12014
+ return staticCount;
12015
+ const parts = staticCount > 0 ? [String(staticCount), ...dynamic] : dynamic;
12016
+ return parts.length === 1 ? parts[0] : `(${parts.join(" + ")})`;
12017
+ }
12018
+ function legacyElementCount(node) {
12019
+ return node.type === "element" || node.type === "component" || node.type === "provider" || node.type === "async" || node.type === "text" || node.type === "expression" && !node.reactive || node.type === "conditional" ? 1 : 0;
12020
+ }
12021
+ function computeLoopSiblingOffsets(root) {
12022
+ const offsets = new Map;
12023
+ const recordRun = (children, preceding) => {
12024
+ for (const child of children) {
12025
+ if (child.type === "loop") {
12026
+ if (preceding.length > 0 && !offsets.has(child)) {
12027
+ offsets.set(child, [...preceding]);
12028
+ }
12029
+ preceding.push(child);
12030
+ } else if (child.type === "fragment" || child.type === "provider" || child.type === "async") {
12031
+ recordRun(child.children, preceding);
12032
+ } else {
12033
+ preceding.push(child);
11629
12034
  }
11630
12035
  }
11631
12036
  };
@@ -11734,6 +12139,7 @@ function collectInnerLoops(nodes, siblingOffsets, outerLoopParam, ctx, options)
11734
12139
  arrayFreeIdentifiers: n.arrayFreeIdentifiers,
11735
12140
  param: n.param,
11736
12141
  paramBindings: n.paramBindings,
12142
+ index: n.index,
11737
12143
  key: n.key,
11738
12144
  markerId: n.markerId,
11739
12145
  bodyIsMultiRoot: n.bodyIsMultiRoot,
@@ -11931,6 +12337,7 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
11931
12337
  let template = "";
11932
12338
  let staticItemTemplate;
11933
12339
  let skeletonTemplate;
12340
+ let skeletonPaths;
11934
12341
  if (l.childComponent) {
11935
12342
  template = "";
11936
12343
  if (l.isStaticArray && l.children[0]) {
@@ -11942,10 +12349,14 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
11942
12349
  if (l.isStaticArray) {
11943
12350
  staticItemTemplate = useElementReconciliation ? irToPlaceholderTemplate(l.children[0], buildRestSpreadNames(ctx), 0) : irToHtmlTemplate(l.children[0], buildRestSpreadNames(ctx), 0);
11944
12351
  } else if (!useElementReconciliation && !l.bodyIsMultiRoot && !l.bodyIsItemConditional) {
11945
- skeletonTemplate = buildLoopSkeletonTemplate(l.children[0], {
12352
+ const skeletonSafeSlots = {
11946
12353
  reactiveAttrKeys: new Set(bindings.reactiveAttrs.map((a) => `${a.childSlotId}::${a.attrName}`)),
11947
12354
  reactiveTextSlotIds: new Set(bindings.reactiveTexts.map((t) => t.slotId))
11948
- }) ?? undefined;
12355
+ };
12356
+ skeletonTemplate = buildLoopSkeletonTemplate(l.children[0], skeletonSafeSlots) ?? undefined;
12357
+ if (skeletonTemplate && !templateRootIsSvg(skeletonTemplate)) {
12358
+ skeletonPaths = computeSkeletonSlotPaths(l.children[0], skeletonSafeSlots) ?? undefined;
12359
+ }
11949
12360
  }
11950
12361
  }
11951
12362
  ctx.loopElements.push({
@@ -11965,6 +12376,7 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
11965
12376
  template,
11966
12377
  staticItemTemplate,
11967
12378
  skeletonTemplate,
12379
+ skeletonPaths,
11968
12380
  childEventHandlers: childHandlers,
11969
12381
  bindings,
11970
12382
  childComponent: l.childComponent,
@@ -12755,6 +13167,7 @@ var RUNTIME_IMPORT_CANDIDATES = [
12755
13167
  "__slot",
12756
13168
  "__bfSlot",
12757
13169
  "__bfText",
13170
+ "tAfter",
12758
13171
  "beginTurn",
12759
13172
  "endTurn"
12760
13173
  ];
@@ -14625,9 +15038,63 @@ import { BF_SCOPE as BF_SCOPE2, BF_HOST, BF_AT } from "@barefootjs/shared";
14625
15038
  function loopKeyFn(loop) {
14626
15039
  if (loop.key === null)
14627
15040
  return "null";
14628
- const params = loop.kind === "nested" ? loop.param : `${loop.param}${loop.index ? `, ${loop.index}` : ""}`;
15041
+ const params = `${loop.param}${loop.index ? `, ${loop.index}` : ""}`;
14629
15042
  return `(${params}) => String(${loop.key})`;
14630
15043
  }
15044
+ function nestedLoopReferencesIndex(inner, comps, events) {
15045
+ const index = inner.index;
15046
+ if (!index)
15047
+ return false;
15048
+ const exprRefs = (text, precomputed) => {
15049
+ if (precomputed)
15050
+ return precomputed.has(index);
15051
+ if (!text)
15052
+ return false;
15053
+ return extractFreeIdentifiersFromText(text).has(index);
15054
+ };
15055
+ if (exprRefs(inner.key))
15056
+ return true;
15057
+ for (const t of inner.bindings.reactiveTexts) {
15058
+ if (exprRefs(t.expression, t.freeIdentifiers))
15059
+ return true;
15060
+ }
15061
+ for (const a of inner.bindings.reactiveAttrs) {
15062
+ if (exprRefs(a.expression, a.freeIdentifiers))
15063
+ return true;
15064
+ }
15065
+ if (inner.mapPreamble && extractFreeIdentifiersFromStatementText(inner.mapPreamble).has(index))
15066
+ return true;
15067
+ if (inner.template && extractFreeIdentifiersFromTemplateText(inner.template).has(index))
15068
+ return true;
15069
+ for (const ev of events) {
15070
+ if (exprRefs(ev.handler))
15071
+ return true;
15072
+ }
15073
+ for (const r of inner.bindings.refs) {
15074
+ if (exprRefs(r.callback))
15075
+ return true;
15076
+ }
15077
+ for (const c of comps) {
15078
+ for (const p of c.props) {
15079
+ if (exprRefs(attrValueToString(p.value)))
15080
+ return true;
15081
+ }
15082
+ if (c.children?.length) {
15083
+ const childrenExpr = irChildrenToJsExpr(c.children);
15084
+ if (childrenExpr && exprRefs(childrenExpr))
15085
+ return true;
15086
+ }
15087
+ }
15088
+ return false;
15089
+ }
15090
+ function nestedLoopIndexAlias(inner, syntheticIndexVar, paramHead, comps, events) {
15091
+ const index = inner.index;
15092
+ if (!index || index === paramHead)
15093
+ return null;
15094
+ if (!nestedLoopReferencesIndex(inner, comps, events))
15095
+ return null;
15096
+ return `const ${index} = ${syntheticIndexVar}`;
15097
+ }
14631
15098
  function buildChildRefBindings(refs, loopParam, loopParamBindings) {
14632
15099
  if (refs.length === 0)
14633
15100
  return [];
@@ -14808,6 +15275,7 @@ function buildOuterNestedPlan(elem, comp) {
14808
15275
  }
14809
15276
  function buildInnerLoopNestedPlan(elem, innerLoop, innerComps) {
14810
15277
  const outerIndexParam = elem.index || "__idx";
15278
+ const innerIndexParam = innerLoop.index || "__innerIdx";
14811
15279
  const comps = innerComps.map((comp) => ({
14812
15280
  componentName: comp.name,
14813
15281
  selector: buildCompSelector(comp),
@@ -14824,7 +15292,8 @@ function buildInnerLoopNestedPlan(elem, innerLoop, innerComps) {
14824
15292
  innerContainerSlotId: innerLoop.containerSlotId ?? null,
14825
15293
  innerArrayExpr: innerLoop.array,
14826
15294
  innerParam: innerLoop.param,
14827
- innerOffsetExpr: buildLoopChildIndexExpr("__innerIdx", innerLoop.offset),
15295
+ innerIndexParam,
15296
+ innerOffsetExpr: buildLoopChildIndexExpr(innerIndexParam, innerLoop.offset),
14828
15297
  innerPreludeStatements: innerLoop.mapPreamble ? [innerLoop.mapPreamble] : [],
14829
15298
  depth: innerLoop.depth,
14830
15299
  comps
@@ -14841,9 +15310,11 @@ function buildComponentRootedInnerLoopPlan(elem, innerLoop, innerComps) {
14841
15310
  containerVar: `_${varSlotId(elem.slotId)}`,
14842
15311
  outerArrayExpr: elem.array,
14843
15312
  outerParam: elem.param,
15313
+ outerIndexParam: elem.index,
14844
15314
  outerPreludeStatements: elem.mapPreamble ? [elem.mapPreamble] : [],
14845
15315
  innerArrayExpr: innerLoop.array,
14846
15316
  innerParam: innerLoop.param,
15317
+ innerIndexParam: innerLoop.index,
14847
15318
  innerPreludeStatements: innerLoop.mapPreamble ? [innerLoop.mapPreamble] : [],
14848
15319
  depth: innerLoop.depth,
14849
15320
  comps
@@ -14936,6 +15407,7 @@ function emitInnerLoopNested(lines, plan) {
14936
15407
  innerContainerSlotId,
14937
15408
  innerArrayExpr,
14938
15409
  innerParam,
15410
+ innerIndexParam,
14939
15411
  innerOffsetExpr,
14940
15412
  innerPreludeStatements,
14941
15413
  depth,
@@ -14954,7 +15426,7 @@ function emitInnerLoopNested(lines, plan) {
14954
15426
  } else {
14955
15427
  lines.push(` const __ic = __outerEl`);
14956
15428
  }
14957
- lines.push(` ${innerArrayExpr}.forEach((${innerParam}, __innerIdx) => {`);
15429
+ lines.push(` ${innerArrayExpr}.forEach((${innerParam}, ${innerIndexParam}) => {`);
14958
15430
  lines.push(` const __innerEl = __ic.children[${innerOffsetExpr}]`);
14959
15431
  lines.push(` if (!__innerEl) return`);
14960
15432
  for (const stmt of innerPreludeStatements) {
@@ -14975,9 +15447,11 @@ function emitComponentRootedInnerLoop(lines, plan) {
14975
15447
  containerVar,
14976
15448
  outerArrayExpr,
14977
15449
  outerParam,
15450
+ outerIndexParam,
14978
15451
  outerPreludeStatements,
14979
15452
  innerArrayExpr,
14980
15453
  innerParam,
15454
+ innerIndexParam,
14981
15455
  innerPreludeStatements,
14982
15456
  depth,
14983
15457
  comps
@@ -14991,11 +15465,11 @@ function emitComponentRootedInnerLoop(lines, plan) {
14991
15465
  lines.push(` const ${scopesVar(i)} = qsaChildScopes(${containerVar}, ${comp.selector})`);
14992
15466
  lines.push(` let ${cursorVar(i)} = 0`);
14993
15467
  });
14994
- lines.push(` ${outerArrayExpr}.forEach((${outerParam}) => {`);
15468
+ lines.push(` ${outerArrayExpr}.forEach((${outerParam}${outerIndexParam ? `, ${outerIndexParam}` : ""}) => {`);
14995
15469
  for (const stmt of outerPreludeStatements) {
14996
15470
  lines.push(` ${stmt}`);
14997
15471
  }
14998
- lines.push(` ${innerArrayExpr}.forEach((${innerParam}) => {`);
15472
+ lines.push(` ${innerArrayExpr}.forEach((${innerParam}${innerIndexParam ? `, ${innerIndexParam}` : ""}) => {`);
14999
15473
  for (const stmt of innerPreludeStatements) {
15000
15474
  lines.push(` ${stmt}`);
15001
15475
  }
@@ -15147,6 +15621,7 @@ function buildBranchInnerLoopsPlan(args) {
15147
15621
  ...ev,
15148
15622
  handler: wrapInner(ev.handler)
15149
15623
  }));
15624
+ const indexAlias = nestedLoopIndexAlias(inner, `__bidxbr_${i}`, paramHead, inner.childComponents ?? [], inner.bindings.events);
15150
15625
  const reactiveTexts = inner.bindings.reactiveTexts.map((text) => ({
15151
15626
  slotId: text.slotId,
15152
15627
  wrappedExpression: wrapBoth(text.expression),
@@ -15161,6 +15636,7 @@ function buildBranchInnerLoopsPlan(args) {
15161
15636
  keyFn: loopKeyFn(inner),
15162
15637
  paramHead,
15163
15638
  paramUnwrap,
15639
+ indexAlias,
15164
15640
  wrappedTemplate: inner.template,
15165
15641
  wrappedKey,
15166
15642
  keyDepth: 1,
@@ -15402,7 +15878,7 @@ function buildInnerLoopsPlan(args) {
15402
15878
  const containerExpr = inner.containerSlotId ? `qsa(${parentElVar}, '[bf="${inner.containerSlotId}"]')` : parentElVar;
15403
15879
  const refsParent = !!outerLoopParam && (inner.arrayFreeIdentifiers?.has(outerLoopParam) ?? false);
15404
15880
  const useReactive = refsParent && !!inner.template;
15405
- const emit = useReactive ? buildReactiveEmit(inner, level, wrapOuter) : buildStaticEmit(inner, level);
15881
+ const emit = useReactive ? buildReactiveEmit(inner, level, wrapOuter, uidSuffix) : buildStaticEmit(inner, level, uidSuffix);
15406
15882
  const arrayExpr = useReactive ? wrapOuter(inner.array) : inner.array;
15407
15883
  const childLevelsPlan = childLevels.length > 0 ? buildInnerLoopsPlan({
15408
15884
  levels: childLevels,
@@ -15428,7 +15904,7 @@ function buildInnerLoopsPlan(args) {
15428
15904
  }
15429
15905
  return plan;
15430
15906
  }
15431
- function buildReactiveEmit(inner, level, wrapOuter) {
15907
+ function buildReactiveEmit(inner, level, wrapOuter, uidSuffix) {
15432
15908
  const wrapInner = (expr) => wrapLoopParamAsAccessor(expr, inner.param, inner.paramBindings);
15433
15909
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(inner.param, inner.paramBindings);
15434
15910
  const wrappedKey = inner.key ? wrapLoopParamAsAccessor(inner.key, inner.param, inner.paramBindings) : null;
@@ -15475,6 +15951,9 @@ function buildReactiveEmit(inner, level, wrapOuter) {
15475
15951
  };
15476
15952
  });
15477
15953
  const preludeStatements = [];
15954
+ const indexAlias = nestedLoopIndexAlias(inner, `__innerIdx${uidSuffix}`, paramHead, level.comps, level.events);
15955
+ if (indexAlias)
15956
+ preludeStatements.push(indexAlias);
15478
15957
  if (paramUnwrap)
15479
15958
  preludeStatements.push(paramUnwrap);
15480
15959
  if (inner.mapPreamble)
@@ -15495,8 +15974,13 @@ function buildReactiveEmit(inner, level, wrapOuter) {
15495
15974
  childRefs
15496
15975
  };
15497
15976
  }
15498
- function buildStaticEmit(inner, level) {
15499
- const preludeStatements = inner.mapPreamble ? [inner.mapPreamble] : [];
15977
+ function buildStaticEmit(inner, level, uidSuffix) {
15978
+ const preludeStatements = [];
15979
+ const indexAlias = nestedLoopIndexAlias(inner, `__innerIdx${uidSuffix}`, inner.param, level.comps, level.events);
15980
+ if (indexAlias)
15981
+ preludeStatements.push(indexAlias);
15982
+ if (inner.mapPreamble)
15983
+ preludeStatements.push(inner.mapPreamble);
15500
15984
  return {
15501
15985
  mode: "static",
15502
15986
  rawKey: inner.key ?? null,
@@ -15999,151 +16483,6 @@ function emitReactiveChildProps(lines, ctx) {
15999
16483
  }
16000
16484
  }
16001
16485
 
16002
- // src/ir-to-client-js/control-flow/stringify/template-parse.ts
16003
- var SVG_ROOT_TAGS = new Set([
16004
- "svg",
16005
- "path",
16006
- "circle",
16007
- "rect",
16008
- "line",
16009
- "polyline",
16010
- "polygon",
16011
- "ellipse",
16012
- "text",
16013
- "tspan",
16014
- "textPath",
16015
- "g",
16016
- "defs",
16017
- "use",
16018
- "symbol",
16019
- "switch",
16020
- "clipPath",
16021
- "mask",
16022
- "marker",
16023
- "pattern",
16024
- "linearGradient",
16025
- "radialGradient",
16026
- "stop",
16027
- "image",
16028
- "foreignObject",
16029
- "filter",
16030
- "feBlend",
16031
- "feColorMatrix",
16032
- "feComposite",
16033
- "feFlood",
16034
- "feGaussianBlur",
16035
- "feMerge",
16036
- "feMergeNode",
16037
- "feMorphology",
16038
- "feOffset",
16039
- "feTurbulence",
16040
- "animate",
16041
- "animateTransform",
16042
- "animateMotion"
16043
- ]);
16044
- function templateRootIsSvg(template) {
16045
- const stripped = stripLeadingNonContent(template);
16046
- const m = stripped.match(/^<\s*([A-Za-z][A-Za-z0-9-]*)/);
16047
- if (m) {
16048
- const tag = m[1];
16049
- if (SVG_ROOT_TAGS.has(tag))
16050
- return true;
16051
- return SVG_ROOT_TAGS.has(tag.toLowerCase());
16052
- }
16053
- const branches = extractConditionalBranchTemplates(stripped);
16054
- if (branches === null || branches.length === 0)
16055
- return false;
16056
- return branches.every(templateRootIsSvg);
16057
- }
16058
- function stripLeadingNonContent(template) {
16059
- let s = template.trimStart();
16060
- while (s.startsWith("<!--")) {
16061
- const end = s.indexOf("-->");
16062
- if (end < 0)
16063
- return s;
16064
- s = s.slice(end + 3).trimStart();
16065
- }
16066
- return s;
16067
- }
16068
- function extractConditionalBranchTemplates(template) {
16069
- if (!template.startsWith("${"))
16070
- return null;
16071
- const exprEnd = findInterpolationEnd(template, 2);
16072
- if (exprEnd < 0)
16073
- return null;
16074
- const trailing = stripLeadingNonContent(template.slice(exprEnd + 1));
16075
- if (trailing.length > 0)
16076
- return null;
16077
- const expr = template.slice(2, exprEnd);
16078
- return findTopLevelTemplateLiterals(expr);
16079
- }
16080
- function emitTemplateCloneInline(template) {
16081
- if (templateRootIsSvg(template)) {
16082
- return `const __tpl = document.createElement('template'); __tpl.innerHTML = \`<svg>${template}</svg>\`; return __tpl.content.firstElementChild.firstElementChild.cloneNode(true)`;
16083
- }
16084
- return `const __tpl = document.createElement('template'); __tpl.innerHTML = \`${template}\`; return __tpl.content.firstElementChild.cloneNode(true)`;
16085
- }
16086
- function emitHoistedTemplateDecl(lines, indent, tplVar, skeletonTemplate) {
16087
- const isSvg = templateRootIsSvg(skeletonTemplate);
16088
- const html = isSvg ? `<svg>${skeletonTemplate}</svg>` : skeletonTemplate;
16089
- lines.push(`${indent}const ${tplVar} = document.createElement('template')`);
16090
- lines.push(`${indent}${tplVar}.innerHTML = \`${html}\``);
16091
- }
16092
- function hoistedCloneExpr(tplVar, skeletonTemplate) {
16093
- return templateRootIsSvg(skeletonTemplate) ? `${tplVar}.content.firstElementChild.firstElementChild.cloneNode(true)` : `${tplVar}.content.firstElementChild.cloneNode(true)`;
16094
- }
16095
- function emitTemplateCloneLines(template, indent) {
16096
- if (templateRootIsSvg(template)) {
16097
- return [
16098
- `${indent}const __tpl = document.createElement('template')`,
16099
- `${indent}__tpl.innerHTML = \`<svg>${template}</svg>\``,
16100
- `${indent}return __tpl.content.firstElementChild.firstElementChild.cloneNode(true)`
16101
- ];
16102
- }
16103
- return [
16104
- `${indent}const __tpl = document.createElement('template')`,
16105
- `${indent}__tpl.innerHTML = \`${template}\``,
16106
- `${indent}return __tpl.content.firstElementChild.cloneNode(true)`
16107
- ];
16108
- }
16109
- function emitLoopItemElementSetup(lines, opts) {
16110
- const { template, bodyIsMultiRoot, indent, singleRootLayout } = opts;
16111
- const innerIndent = indent + " ";
16112
- if (bodyIsMultiRoot) {
16113
- lines.push(`${indent}let __el, __extras`);
16114
- lines.push(`${indent}if (__existing) {`);
16115
- lines.push(`${innerIndent}__el = __existing`);
16116
- lines.push(`${indent}} else {`);
16117
- for (const ln of emitMultiRootTemplateCloneLines(template, innerIndent, "__el", "__extras")) {
16118
- lines.push(ln);
16119
- }
16120
- lines.push(`${innerIndent}__el.__bfExtras = __extras`);
16121
- lines.push(`${indent}}`);
16122
- return;
16123
- }
16124
- if (singleRootLayout === "inline") {
16125
- const cloneExpr = emitTemplateCloneInline(template);
16126
- lines.push(`${indent}const __el = __existing ?? (() => { ${cloneExpr} })()`);
16127
- return;
16128
- }
16129
- lines.push(`${indent}const __el = __existing ?? (() => {`);
16130
- for (const ln of emitTemplateCloneLines(template, innerIndent))
16131
- lines.push(ln);
16132
- lines.push(`${indent}})()`);
16133
- }
16134
- function emitMultiRootTemplateCloneLines(template, indent, varEl, varExtras) {
16135
- const isSvg = templateRootIsSvg(template);
16136
- const innerHtmlExpr = isSvg ? `\`<svg>${template}</svg>\`` : `\`${template}\``;
16137
- const parentExpr = isSvg ? `__tpl.content.firstElementChild` : `__tpl.content`;
16138
- return [
16139
- `${indent}const __tpl = document.createElement('template')`,
16140
- `${indent}__tpl.innerHTML = ${innerHtmlExpr}`,
16141
- `${indent}${varEl} = ${parentExpr}.firstElementChild.cloneNode(true)`,
16142
- `${indent}${varExtras} = []`,
16143
- `${indent}{ let __sib = ${parentExpr}.firstElementChild.nextElementSibling; while (__sib) { ${varExtras}.push(__sib.cloneNode(true)); __sib = __sib.nextElementSibling } }`
16144
- ];
16145
- }
16146
-
16147
16486
  // src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts
16148
16487
  function stringifyBranchEventBindings(lines, plan, indent) {
16149
16488
  for (const slot of plan) {
@@ -16165,6 +16504,9 @@ function stringifyBranchInnerLoops(lines, plan, indent, pc) {
16165
16504
  const uid = inner.uidSuffix;
16166
16505
  lines.push(`${indent}{ const __bic${uid} = ${inner.containerExpr}`);
16167
16506
  lines.push(`${indent}if (__bic${uid}) mapArray(() => ${inner.arrayExpr} || [], __bic${uid}, ${inner.keyFn}, (${inner.paramHead}, __bidx${uid}, __existing) => {`);
16507
+ if (inner.indexAlias) {
16508
+ lines.push(`${indent} ${inner.indexAlias}`);
16509
+ }
16168
16510
  if (inner.paramUnwrap) {
16169
16511
  lines.push(`${indent} ${inner.paramUnwrap}`);
16170
16512
  }
@@ -16229,13 +16571,15 @@ function stringifyLoopChildArm(lines, arm, armIndent, pc) {
16229
16571
 
16230
16572
  // src/ir-to-client-js/control-flow/stringify/reactive-effects.ts
16231
16573
  function stringifyReactiveEffects(lines, plan, opts) {
16232
- const { indent, elVar, bodyIsMultiRoot } = opts;
16574
+ const { indent, elVar, bodyIsMultiRoot, elementIndexBySlot, textIndexBySlot } = opts;
16233
16575
  const lookup = bodyIsMultiRoot ? "qsaItem" : "qsa";
16234
16576
  const pc = plan.profileComponentName;
16235
16577
  const bindingBfId = (slotId) => profileBindingId(pc, slotId);
16236
16578
  for (const slot of plan.attrSlots) {
16237
16579
  const varName = `__ra_${varSlotId(slot.slotId)}`;
16238
- lines.push(`${indent}{ const ${varName} = ${lookup}(${elVar}, '[bf="${slot.slotId}"]')`);
16580
+ const pIdx = elementIndexBySlot?.get(slot.slotId);
16581
+ const lookupExpr = pIdx !== undefined ? `__p ? __p[${pIdx}] : ${lookup}(${elVar}, '[bf="${slot.slotId}"]')` : `${lookup}(${elVar}, '[bf="${slot.slotId}"]')`;
16582
+ lines.push(`${indent}{ const ${varName} = ${lookupExpr}`);
16239
16583
  lines.push(`${indent}if (${varName}) {`);
16240
16584
  for (const attr of slot.attrs) {
16241
16585
  lines.push(`${indent} createEffect(() => {`);
@@ -16247,15 +16591,19 @@ function stringifyReactiveEffects(lines, plan, opts) {
16247
16591
  lines.push(`${indent}} }`);
16248
16592
  }
16249
16593
  for (const text of plan.outerTexts) {
16250
- emitOuterText(lines, indent, elVar, text, bindingBfId(text.slotId));
16594
+ emitOuterText(lines, indent, elVar, text, bindingBfId(text.slotId), textIndexBySlot?.get(text.slotId));
16251
16595
  }
16252
16596
  for (const cond of plan.conditionals) {
16253
16597
  emitOuterConditional(lines, indent, elVar, cond, pc);
16254
16598
  }
16255
16599
  }
16256
- function emitOuterText(lines, indent, elVar, text, bfId = "") {
16600
+ function emitOuterText(lines, indent, elVar, text, bfId = "", pIdx) {
16257
16601
  const varName = `__rt_${varSlotId(text.slotId)}`;
16258
- lines.push(`${indent}{ const [${varName}] = $t(${elVar}, '${text.slotId}')`);
16602
+ if (pIdx !== undefined) {
16603
+ lines.push(`${indent}{ const ${varName} = __p ? tAfter(__p[${pIdx}]) : $t(${elVar}, '${text.slotId}')[0]`);
16604
+ } else {
16605
+ lines.push(`${indent}{ const [${varName}] = $t(${elVar}, '${text.slotId}')`);
16606
+ }
16259
16607
  lines.push(`${indent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }${bfId}) }`);
16260
16608
  }
16261
16609
  function emitOuterConditional(lines, indent, elVar, cond, pc) {
@@ -16287,6 +16635,41 @@ function emitArmText(lines, indent, text, pc) {
16287
16635
  lines.push(`${indent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }${profileBindingId(pc, text.slotId)}) }`);
16288
16636
  }
16289
16637
 
16638
+ // src/ir-to-client-js/control-flow/stringify/skeleton-paths.ts
16639
+ function pathExpr(base, path) {
16640
+ let expr = base;
16641
+ for (const idx of path) {
16642
+ expr += ".firstChild";
16643
+ if (idx > 0)
16644
+ expr += ".nextSibling".repeat(idx);
16645
+ }
16646
+ return expr;
16647
+ }
16648
+ function buildSkeletonPathPlan(skeletonPaths, elVar, opts) {
16649
+ const arrayElems = [];
16650
+ const elementIndexBySlot = new Map;
16651
+ const textIndexBySlot = new Map;
16652
+ for (const slotId of opts.elementSlotIds) {
16653
+ if (elementIndexBySlot.has(slotId))
16654
+ continue;
16655
+ const path = skeletonPaths.elementPaths.get(slotId);
16656
+ if (!path)
16657
+ continue;
16658
+ elementIndexBySlot.set(slotId, arrayElems.length);
16659
+ arrayElems.push(pathExpr(elVar, path));
16660
+ }
16661
+ for (const slotId of opts.textSlotIds) {
16662
+ if (textIndexBySlot.has(slotId))
16663
+ continue;
16664
+ const path = skeletonPaths.textMarkerPaths.get(slotId);
16665
+ if (!path)
16666
+ continue;
16667
+ textIndexBySlot.set(slotId, arrayElems.length);
16668
+ arrayElems.push(pathExpr(elVar, path));
16669
+ }
16670
+ return { arrayElems, elementIndexBySlot, textIndexBySlot };
16671
+ }
16672
+
16290
16673
  // src/ir-to-client-js/control-flow/stringify/component-loop.ts
16291
16674
  function stringifyComponentLoop(lines, plan) {
16292
16675
  const {
@@ -16346,11 +16729,13 @@ function emitNestedInit(lines, indent, parentVar, nc) {
16346
16729
  function emitLoopChildRefs(lines, refs, opts) {
16347
16730
  if (refs.length === 0)
16348
16731
  return;
16349
- const { indent, elVar, bodyIsMultiRoot } = opts;
16732
+ const { indent, elVar, bodyIsMultiRoot, elementIndexBySlot } = opts;
16350
16733
  const lookup = bodyIsMultiRoot ? "qsaItem" : "qsa";
16351
16734
  for (const ref of refs) {
16352
16735
  const varName = `__rf_${varSlotId(ref.childSlotId)}`;
16353
- lines.push(`${indent}{ const ${varName} = ${lookup}(${elVar}, '[bf="${ref.childSlotId}"]')`);
16736
+ const pIdx = elementIndexBySlot?.get(ref.childSlotId);
16737
+ const lookupExpr = pIdx !== undefined ? `__p ? __p[${pIdx}] : ${lookup}(${elVar}, '[bf="${ref.childSlotId}"]')` : `${lookup}(${elVar}, '[bf="${ref.childSlotId}"]')`;
16738
+ lines.push(`${indent}{ const ${varName} = ${lookupExpr}`);
16354
16739
  lines.push(`${indent}if (${varName}) ${emitRefCall(ref.callback, varName)} }`);
16355
16740
  }
16356
16741
  }
@@ -16430,10 +16815,31 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
16430
16815
  singleRootLayout: "inline"
16431
16816
  });
16432
16817
  }
16818
+ let pathPlan = null;
16819
+ if (hoistedTpl && plan.skeletonPaths) {
16820
+ const elementSlotIds = [
16821
+ ...reactiveEffects?.attrSlots.map((s) => s.slotId) ?? [],
16822
+ ...childRefs.map((r) => r.childSlotId)
16823
+ ];
16824
+ const textSlotIds = reactiveEffects?.outerTexts.map((t) => t.slotId) ?? [];
16825
+ if (elementSlotIds.length > 0 || textSlotIds.length > 0) {
16826
+ const built = buildSkeletonPathPlan(plan.skeletonPaths, "__el", { elementSlotIds, textSlotIds });
16827
+ if (built.arrayElems.length > 0) {
16828
+ pathPlan = built;
16829
+ lines.push(`${bodyIndent}const __p = __existing ? null : [${built.arrayElems.join(", ")}]`);
16830
+ }
16831
+ }
16832
+ }
16433
16833
  if (reactiveEffects !== null) {
16434
- stringifyReactiveEffects(lines, reactiveEffects, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot });
16834
+ stringifyReactiveEffects(lines, reactiveEffects, {
16835
+ indent: bodyIndent,
16836
+ elVar: "__el",
16837
+ bodyIsMultiRoot,
16838
+ elementIndexBySlot: pathPlan?.elementIndexBySlot,
16839
+ textIndexBySlot: pathPlan?.textIndexBySlot
16840
+ });
16435
16841
  }
16436
- emitLoopChildRefs(lines, childRefs, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot });
16842
+ emitLoopChildRefs(lines, childRefs, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot, elementIndexBySlot: pathPlan?.elementIndexBySlot });
16437
16843
  lines.push(`${bodyIndent}return __el`);
16438
16844
  lines.push(`${topIndent}}, '${markerId}'${loopBfId})`);
16439
16845
  }
@@ -16500,12 +16906,14 @@ function stringifyStaticLoop(lines, plan) {
16500
16906
  lines.push(` let __iterEl = ${containerVar}.children[${childIndexExpr}]`);
16501
16907
  if (csrMaterialize) {
16502
16908
  lines.push(` if (!__iterEl) {`);
16909
+ const isSvg = csrMaterialize.bodyIsMultiRoot ? multiRootTemplateNeedsSvgWrap(csrMaterialize.itemTemplate) : templateRootIsSvg(csrMaterialize.itemTemplate);
16910
+ const itemHtml = isSvg ? `<svg>${csrMaterialize.itemTemplate}</svg>` : csrMaterialize.itemTemplate;
16503
16911
  if (csrMaterialize.bodyIsMultiRoot) {
16504
16912
  lines.push(` const __mtpl = document.createElement('template')`);
16505
- lines.push(` __mtpl.innerHTML = \`${csrMaterialize.itemTemplate}\``);
16913
+ lines.push(` __mtpl.innerHTML = \`${itemHtml}\``);
16506
16914
  lines.push(` const __anchor = ${containerVar}.children[${childIndexExpr}] ?? null`);
16507
16915
  lines.push(` let __first = null`);
16508
- lines.push(` let __sib = __mtpl.content.firstElementChild`);
16916
+ lines.push(` let __sib = __mtpl.content${isSvg ? ".firstElementChild" : ""}.firstElementChild`);
16509
16917
  lines.push(` while (__sib) {`);
16510
16918
  lines.push(` const __next = __sib.nextElementSibling`);
16511
16919
  lines.push(` const __cloned = __sib.cloneNode(true)`);
@@ -16516,8 +16924,8 @@ function stringifyStaticLoop(lines, plan) {
16516
16924
  lines.push(` __iterEl = __first`);
16517
16925
  } else {
16518
16926
  lines.push(` const __tpl = document.createElement('template')`);
16519
- lines.push(` __tpl.innerHTML = \`${csrMaterialize.itemTemplate}\``);
16520
- lines.push(` const __cloned = __tpl.content.firstElementChild`);
16927
+ lines.push(` __tpl.innerHTML = \`${itemHtml}\``);
16928
+ lines.push(` const __cloned = __tpl.content${isSvg ? ".firstElementChild" : ""}.firstElementChild`);
16521
16929
  lines.push(` if (__cloned) {`);
16522
16930
  lines.push(` const __anchor = ${containerVar}.children[${childIndexExpr}] ?? null`);
16523
16931
  lines.push(` ${containerVar}.insertBefore(__cloned, __anchor)`);
@@ -16584,7 +16992,10 @@ function emitReactive(lines, inner, indent, pc) {
16584
16992
  lines.push(`${innerIndent} __innerEl${uid}.__bfExtras = __innerExtras${uid}`);
16585
16993
  lines.push(`${indent} }`);
16586
16994
  } else {
16587
- lines.push(`${indent} let __innerEl${uid} = __existing ?? (() => { const __t = document.createElement('template'); __t.innerHTML = \`${emit.wrappedTemplate}\`; return __t.content.firstElementChild.cloneNode(true) })()`);
16995
+ const isSvg = templateRootIsSvg(emit.wrappedTemplate);
16996
+ const innerHtml = isSvg ? `<svg>${emit.wrappedTemplate}</svg>` : emit.wrappedTemplate;
16997
+ const childPath = isSvg ? ".firstElementChild.firstElementChild" : ".firstElementChild";
16998
+ lines.push(`${indent} let __innerEl${uid} = __existing ?? (() => { const __t = document.createElement('template'); __t.innerHTML = \`${innerHtml}\`; return __t.content${childPath}.cloneNode(true) })()`);
16588
16999
  }
16589
17000
  if (emit.wrappedKey) {
16590
17001
  lines.push(`${indent} __innerEl${uid}.setAttribute('${keyAttrName(inner.keyDepth)}', String(${emit.wrappedKey}))`);
@@ -17143,6 +17554,7 @@ function buildPlainLoopPlan(elem, profileComponentName) {
17143
17554
  mapPreambleWrapped: elem.mapPreamble ? wrap(elem.mapPreamble) : "",
17144
17555
  template: elem.template,
17145
17556
  skeletonTemplate: elem.skeletonTemplate,
17557
+ skeletonPaths: elem.skeletonPaths,
17146
17558
  reactiveEffects: hasReactive2 ? buildLoopReactiveEffectsPlan(elem, profileComponentName) : null,
17147
17559
  childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings),
17148
17560
  bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false,
@@ -20116,6 +20528,8 @@ function isStringTypedOperand(expr, isStringName) {
20116
20528
  if (expr.kind === "member" && expr.object.kind === "identifier" && expr.object.name === "props") {
20117
20529
  return isStringName(expr.property);
20118
20530
  }
20531
+ if (expr.kind === "identifier")
20532
+ return isStringName(expr.name);
20119
20533
  if (expr.kind === "binary" && expr.op === "+") {
20120
20534
  return isStringTypedOperand(expr.left, isStringName) || isStringTypedOperand(expr.right, isStringName);
20121
20535
  }
@@ -20175,6 +20589,221 @@ function emitParsedExpr(expr, emitter) {
20175
20589
  }
20176
20590
  }
20177
20591
  }
20592
+ // src/adapters/loop-bound-names.ts
20593
+ function collectLoopBoundNames(ir) {
20594
+ const names = new Set;
20595
+ const visit3 = (node) => {
20596
+ if (!node)
20597
+ return;
20598
+ switch (node.type) {
20599
+ case "element":
20600
+ case "component":
20601
+ case "fragment":
20602
+ case "provider":
20603
+ for (const child of node.children)
20604
+ visit3(child);
20605
+ break;
20606
+ case "async":
20607
+ visit3(node.fallback);
20608
+ for (const child of node.children)
20609
+ visit3(child);
20610
+ break;
20611
+ case "loop":
20612
+ names.add(node.param);
20613
+ if (node.index)
20614
+ names.add(node.index);
20615
+ for (const binding of node.paramBindings ?? [])
20616
+ names.add(binding.name);
20617
+ if (node.filterPredicate)
20618
+ names.add(node.filterPredicate.param);
20619
+ for (const child of node.children)
20620
+ visit3(child);
20621
+ if (node.childComponent) {
20622
+ for (const child of node.childComponent.children)
20623
+ visit3(child);
20624
+ }
20625
+ for (const nested of node.nestedComponents ?? []) {
20626
+ for (const child of nested.children)
20627
+ visit3(child);
20628
+ }
20629
+ for (const frag of node.flatMapCallback?.fragments ?? []) {
20630
+ visit3(frag.ir);
20631
+ }
20632
+ break;
20633
+ case "conditional":
20634
+ visit3(node.whenTrue);
20635
+ visit3(node.whenFalse);
20636
+ break;
20637
+ case "if-statement":
20638
+ visit3(node.consequent);
20639
+ if (node.alternate)
20640
+ visit3(node.alternate);
20641
+ break;
20642
+ case "text":
20643
+ case "expression":
20644
+ case "slot":
20645
+ break;
20646
+ }
20647
+ };
20648
+ visit3(ir.root);
20649
+ return names;
20650
+ }
20651
+ // src/signal-init-eval.ts
20652
+ var BLOCKED_GLOBALS = [
20653
+ "globalThis",
20654
+ "window",
20655
+ "document",
20656
+ "Date",
20657
+ "Math",
20658
+ "crypto",
20659
+ "performance",
20660
+ "fetch",
20661
+ "setTimeout",
20662
+ "setInterval",
20663
+ "require",
20664
+ "process",
20665
+ "Function"
20666
+ ];
20667
+ function isTransportable(value, ancestors = new Set) {
20668
+ if (value === null)
20669
+ return true;
20670
+ const t = typeof value;
20671
+ if (t === "boolean" || t === "number" || t === "string")
20672
+ return true;
20673
+ if (t !== "object")
20674
+ return false;
20675
+ if (ancestors.has(value))
20676
+ return false;
20677
+ ancestors.add(value);
20678
+ try {
20679
+ if (Array.isArray(value)) {
20680
+ if (Object.keys(value).length !== value.length)
20681
+ return false;
20682
+ return value.every((el) => el !== undefined && isTransportable(el, ancestors));
20683
+ }
20684
+ const proto = Object.getPrototypeOf(value);
20685
+ if (proto !== Object.prototype && proto !== null)
20686
+ return false;
20687
+ return Object.values(value).every((v) => isTransportable(v, ancestors));
20688
+ } finally {
20689
+ ancestors.delete(value);
20690
+ }
20691
+ }
20692
+ function tryEvaluateSignalInit(expr, props) {
20693
+ const src = expr.trim();
20694
+ if (src === "")
20695
+ return { ok: false };
20696
+ let fn;
20697
+ try {
20698
+ fn = new Function("props", ...BLOCKED_GLOBALS, `'use strict'; return (
20699
+ ${src}
20700
+ );`);
20701
+ } catch {
20702
+ return { ok: false };
20703
+ }
20704
+ try {
20705
+ const value = fn(props ?? {});
20706
+ if (value === undefined)
20707
+ return { ok: true, value: undefined };
20708
+ return isTransportable(value) ? { ok: true, value } : { ok: false };
20709
+ } catch {
20710
+ return { ok: false };
20711
+ }
20712
+ }
20713
+ function evaluateSignalInit(expr, props) {
20714
+ const result = tryEvaluateSignalInit(expr, props);
20715
+ return result.ok && result.value !== undefined ? result.value : null;
20716
+ }
20717
+ // src/static-literal.ts
20718
+ function evaluateStaticLiteral(expr, bindings) {
20719
+ switch (expr.kind) {
20720
+ case "literal":
20721
+ return { value: expr.value };
20722
+ case "template-literal": {
20723
+ let out = "";
20724
+ for (const part of expr.parts) {
20725
+ if (part.type === "string") {
20726
+ out += part.value;
20727
+ continue;
20728
+ }
20729
+ const resolved = evaluateStaticLiteral(part.expr, bindings);
20730
+ if (!resolved)
20731
+ return null;
20732
+ out += String(resolved.value);
20733
+ }
20734
+ return { value: out };
20735
+ }
20736
+ case "array-literal": {
20737
+ const values = [];
20738
+ for (const element of expr.elements) {
20739
+ const resolved = evaluateStaticLiteral(element, bindings);
20740
+ if (!resolved)
20741
+ return null;
20742
+ values.push(resolved.value);
20743
+ }
20744
+ return { value: values };
20745
+ }
20746
+ case "object-literal": {
20747
+ const out = {};
20748
+ for (const prop of expr.properties) {
20749
+ const resolved = evaluateStaticLiteral(prop.value, bindings);
20750
+ if (!resolved)
20751
+ return null;
20752
+ out[prop.key] = resolved.value;
20753
+ }
20754
+ return { value: out };
20755
+ }
20756
+ case "unary": {
20757
+ const resolved = evaluateStaticLiteral(expr.argument, bindings);
20758
+ if (!resolved)
20759
+ return null;
20760
+ if (expr.op === "-")
20761
+ return typeof resolved.value === "number" ? { value: -resolved.value } : null;
20762
+ if (expr.op === "+")
20763
+ return typeof resolved.value === "number" ? { value: +resolved.value } : null;
20764
+ if (expr.op === "!")
20765
+ return { value: !resolved.value };
20766
+ return null;
20767
+ }
20768
+ case "identifier":
20769
+ return bindings?.has(expr.name) ? { value: bindings.get(expr.name) } : null;
20770
+ case "member": {
20771
+ const base = evaluateStaticLiteral(expr.object, bindings);
20772
+ if (!base || base.value === null || typeof base.value !== "object")
20773
+ return null;
20774
+ return { value: base.value[expr.property] };
20775
+ }
20776
+ case "index-access": {
20777
+ const base = evaluateStaticLiteral(expr.object, bindings);
20778
+ const index = evaluateStaticLiteral(expr.index, bindings);
20779
+ if (!base || !index || !Array.isArray(base.value) || typeof index.value !== "number")
20780
+ return null;
20781
+ return { value: base.value[index.value] };
20782
+ }
20783
+ default:
20784
+ return null;
20785
+ }
20786
+ }
20787
+ function isFullyStaticLiteral(expr) {
20788
+ return evaluateStaticLiteral(expr) !== null;
20789
+ }
20790
+ function resolveStaticLoopSource(arrayParsed, localConstants, opts) {
20791
+ if (!arrayParsed)
20792
+ return null;
20793
+ let target = arrayParsed;
20794
+ if (arrayParsed.kind === "identifier") {
20795
+ if (opts?.isNameShadowed?.(arrayParsed.name))
20796
+ return null;
20797
+ const local = localConstants?.find((c) => c.name === arrayParsed.name);
20798
+ if (!local || local.isModule || !local.parsed)
20799
+ return null;
20800
+ target = local.parsed;
20801
+ }
20802
+ const resolved = evaluateStaticLiteral(target);
20803
+ if (!resolved || !Array.isArray(resolved.value))
20804
+ return null;
20805
+ return resolved.value;
20806
+ }
20178
20807
  // src/query-href-lowering.ts
20179
20808
  function matchQueryHrefCall(callee, args, localNames) {
20180
20809
  if (callee.kind !== "identifier" || !localNames.has(callee.name))
@@ -20310,6 +20939,71 @@ function emitAttrValue(value, emitter, name) {
20310
20939
  }
20311
20940
  }
20312
20941
  }
20942
+ // src/adapters/dangerous-inner-html.ts
20943
+ var DANGEROUS_INNER_HTML_ATTR = "dangerouslySetInnerHTML";
20944
+ function isDangerousInnerHtmlAttr(attr) {
20945
+ return attr.name === DANGEROUS_INNER_HTML_ATTR;
20946
+ }
20947
+ function resolveDangerousInnerHtml(element) {
20948
+ const attr = element.attrs.find(isDangerousInnerHtmlAttr);
20949
+ if (!attr)
20950
+ return null;
20951
+ if (attr.clientOnly)
20952
+ return null;
20953
+ if (attr.value.kind !== "expression") {
20954
+ return { kind: "dynamic", expr: "", loc: attr.loc };
20955
+ }
20956
+ const parsed = attr.value.parsed ?? parseExpression(attr.value.expr.trim());
20957
+ const html = staticHtmlLiteral(parsed);
20958
+ if (html !== null)
20959
+ return { kind: "static", html };
20960
+ return { kind: "dynamic", expr: attr.value.expr, loc: attr.loc };
20961
+ }
20962
+ function staticHtmlLiteral(parsed) {
20963
+ if (parsed.kind !== "object-literal")
20964
+ return null;
20965
+ if (parsed.properties.length !== 1)
20966
+ return null;
20967
+ const [prop] = parsed.properties;
20968
+ if (prop.shorthand)
20969
+ return null;
20970
+ if (prop.key !== "__html")
20971
+ return null;
20972
+ if (prop.value.kind !== "literal" || prop.value.literalType !== "string")
20973
+ return null;
20974
+ return prop.value.value;
20975
+ }
20976
+ var TEMPLATE_METACHAR_PATTERNS = {
20977
+ blade: /\{\{|\{!!|<\?|@\w|<\/?\s*x[-:]/,
20978
+ erb: /<%/,
20979
+ "go-template": /\{\{/,
20980
+ jinja: /\{\{|\{%|\{#/,
20981
+ minijinja: /\{\{|\{%|\{#/,
20982
+ mojolicious: /<%|^\s*%/m,
20983
+ twig: /\{\{|\{%|\{#/,
20984
+ xslate: /<:|^\s*:/m
20985
+ };
20986
+ function dangerousInnerHtmlMetacharViolation(html, adapterId) {
20987
+ const pattern = TEMPLATE_METACHAR_PATTERNS[adapterId];
20988
+ if (!pattern) {
20989
+ return `no template-metacharacter guard is defined for adapter '${adapterId}' — refusing rather than splicing unguarded`;
20990
+ }
20991
+ if (!pattern.test(html))
20992
+ return null;
20993
+ return `the literal HTML contains a sequence ${adapterId}'s own template compiler would interpret (not inert text once spliced into the template)`;
20994
+ }
20995
+ function dangerousInnerHtmlDiagnostic(expr, loc, reason) {
20996
+ const detail = reason ? ` — ${reason}.` : "";
20997
+ return {
20998
+ code: "BF101",
20999
+ severity: "error",
21000
+ message: `dangerouslySetInnerHTML requires a compile-time string literal __html value on template adapters (e.g. { __html: '...' })${expr ? `: ${expr.trim()}` : ""}${detail}`,
21001
+ loc,
21002
+ suggestion: {
21003
+ message: "Dynamic or signal-derived HTML for dangerouslySetInnerHTML is only supported on Hono/CSR today (tracked separately: https://github.com/piconic-ai/barefootjs/issues/2215). Use an inline string literal, or defer it to the client with /* @client */ (e.g. dangerouslySetInnerHTML={/* @client */ { __html: expr }}) so hydration sets it instead of SSR."
21004
+ }
21005
+ };
21006
+ }
20313
21007
  // src/combine-client-js.ts
20314
21008
  import ts19 from "typescript";
20315
21009
  var CHILD_PLACEHOLDER_RE = /import '\/\* @bf-child:(\w+) \*\/'/g;
@@ -23151,6 +23845,7 @@ function profileToJSON(profile) {
23151
23845
  registerBuiltinLoweringPlugins();
23152
23846
  export {
23153
23847
  tsNodeToParsedExpr,
23848
+ tryEvaluateSignalInit,
23154
23849
  traceUpdatePath,
23155
23850
  testAdapter,
23156
23851
  stringifyParsedExpr,
@@ -23158,7 +23853,9 @@ export {
23158
23853
  serializeParsedExpr,
23159
23854
  searchParamsLocalNames,
23160
23855
  rewriteImportsForTemplate,
23856
+ resolveStaticLoopSource,
23161
23857
  resolveSetters,
23858
+ resolveDangerousInnerHtml,
23162
23859
  resetCompilerCounters,
23163
23860
  renderImportMapHtml,
23164
23861
  registerLoweringPlugin,
@@ -23193,6 +23890,8 @@ export {
23193
23890
  isStringConcatBinary,
23194
23891
  isLowerableObjectRestDestructure,
23195
23892
  isLowerableLoopDestructure,
23893
+ isFullyStaticLiteral,
23894
+ isDangerousInnerHtmlAttr,
23196
23895
  isBooleanAttr,
23197
23896
  importsSearchParams,
23198
23897
  identifierPath,
@@ -23233,6 +23932,8 @@ export {
23233
23932
  extractFunctionParams,
23234
23933
  extractArrowBodyExpression,
23235
23934
  exprToString,
23935
+ evaluateStaticLiteral,
23936
+ evaluateSignalInit,
23236
23937
  evaluateProfileGates,
23237
23938
  evalStringArrayJoin,
23238
23939
  envSignalReaderFor,
@@ -23245,6 +23946,8 @@ export {
23245
23946
  diffStaticBudget,
23246
23947
  diffProfiles,
23247
23948
  describeFallback,
23949
+ dangerousInnerHtmlMetacharViolation,
23950
+ dangerousInnerHtmlDiagnostic,
23248
23951
  createProgramForFile,
23249
23952
  createProgramForCorpus,
23250
23953
  createError,
@@ -23253,6 +23956,7 @@ export {
23253
23956
  compileJSX,
23254
23957
  combineParentChildClientJs,
23255
23958
  collectModuleStringConsts,
23959
+ collectLoopBoundNames,
23256
23960
  collectContextConsumers,
23257
23961
  buildWhyUpdate,
23258
23962
  buildStaticBudget,