@barefootjs/jsx 0.18.5 → 0.19.0

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 (96) 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/expression-parser.d.ts +8 -0
  8. package/dist/expression-parser.d.ts.map +1 -1
  9. package/dist/index.d.ts +15 -4
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1002 -284
  12. package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
  13. package/dist/ir-to-client-js/control-flow/plan/build-inner-loop.d.ts.map +1 -1
  14. package/dist/ir-to-client-js/control-flow/plan/build-loop-child-arm.d.ts.map +1 -1
  15. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
  16. package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts +9 -0
  17. package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts.map +1 -1
  18. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +9 -0
  19. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
  20. package/dist/ir-to-client-js/control-flow/shared.d.ts +45 -3
  21. package/dist/ir-to-client-js/control-flow/shared.d.ts.map +1 -1
  22. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts.map +1 -1
  23. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts +1 -0
  24. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
  25. package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts +10 -0
  26. package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts.map +1 -1
  27. package/dist/ir-to-client-js/control-flow/stringify/skeleton-paths.d.ts +38 -0
  28. package/dist/ir-to-client-js/control-flow/stringify/skeleton-paths.d.ts.map +1 -0
  29. package/dist/ir-to-client-js/control-flow/stringify/template-parse.d.ts +19 -0
  30. package/dist/ir-to-client-js/control-flow/stringify/template-parse.d.ts.map +1 -1
  31. package/dist/ir-to-client-js/csr-substitute.d.ts +23 -0
  32. package/dist/ir-to-client-js/csr-substitute.d.ts.map +1 -1
  33. package/dist/ir-to-client-js/html-template.d.ts +42 -0
  34. package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
  35. package/dist/ir-to-client-js/imports.d.ts +2 -2
  36. package/dist/ir-to-client-js/imports.d.ts.map +1 -1
  37. package/dist/ir-to-client-js/phases/props-extraction.d.ts +0 -1
  38. package/dist/ir-to-client-js/phases/props-extraction.d.ts.map +1 -1
  39. package/dist/ir-to-client-js/plan/static-array-child-init.d.ts +20 -1
  40. package/dist/ir-to-client-js/plan/static-array-child-init.d.ts.map +1 -1
  41. package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
  42. package/dist/ir-to-client-js/stringify/static-array-child-init.d.ts +1 -1
  43. package/dist/ir-to-client-js/types.d.ts +21 -0
  44. package/dist/ir-to-client-js/types.d.ts.map +1 -1
  45. package/dist/jsx-to-ir.d.ts.map +1 -1
  46. package/dist/signal-init-eval.d.ts +82 -0
  47. package/dist/signal-init-eval.d.ts.map +1 -0
  48. package/dist/static-literal.d.ts +45 -0
  49. package/dist/static-literal.d.ts.map +1 -0
  50. package/package.json +2 -2
  51. package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +19 -13
  52. package/src/__tests__/client-js-generation.test.ts +40 -0
  53. package/src/__tests__/create-selector.test.ts +110 -0
  54. package/src/__tests__/csr-template-loop-shadowing.test.ts +214 -0
  55. package/src/__tests__/dangerous-inner-html-resolver.test.ts +197 -0
  56. package/src/__tests__/inner-loop-svg-namespace.test.ts +245 -0
  57. package/src/__tests__/loop-bound-names.test.ts +102 -0
  58. package/src/__tests__/loop-plan-classification.test.ts +2 -0
  59. package/src/__tests__/map-function-reference.test.ts +303 -0
  60. package/src/__tests__/nested-loop-index-param.test.ts +285 -0
  61. package/src/__tests__/props-destructuring.test.ts +61 -8
  62. package/src/__tests__/signal-init-eval.test.ts +138 -0
  63. package/src/__tests__/static-array-inner-loop-index-param.test.ts +180 -0
  64. package/src/__tests__/static-literal.test.ts +126 -0
  65. package/src/__tests__/string-concat-identifier.test.ts +55 -0
  66. package/src/adapters/dangerous-inner-html.ts +186 -0
  67. package/src/adapters/loop-bound-names.ts +80 -0
  68. package/src/adapters/parsed-expr-emitter.ts +9 -0
  69. package/src/analyzer.ts +36 -22
  70. package/src/expression-parser.ts +33 -0
  71. package/src/index.ts +20 -3
  72. package/src/ir-to-client-js/collect-elements.ts +15 -3
  73. package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +21 -4
  74. package/src/ir-to-client-js/control-flow/plan/build-loop-child-arm.ts +13 -1
  75. package/src/ir-to-client-js/control-flow/plan/build-loop.ts +1 -0
  76. package/src/ir-to-client-js/control-flow/plan/loop-child-arm.ts +9 -0
  77. package/src/ir-to-client-js/control-flow/plan/loop.ts +9 -0
  78. package/src/ir-to-client-js/control-flow/shared.ts +103 -6
  79. package/src/ir-to-client-js/control-flow/stringify/inner-loop.ts +11 -2
  80. package/src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts +5 -0
  81. package/src/ir-to-client-js/control-flow/stringify/loop.ts +53 -10
  82. package/src/ir-to-client-js/control-flow/stringify/reactive-effects.ts +25 -4
  83. package/src/ir-to-client-js/control-flow/stringify/skeleton-paths.ts +70 -0
  84. package/src/ir-to-client-js/control-flow/stringify/template-parse.ts +25 -1
  85. package/src/ir-to-client-js/csr-substitute.ts +53 -0
  86. package/src/ir-to-client-js/html-template.ts +235 -2
  87. package/src/ir-to-client-js/imports.ts +1 -0
  88. package/src/ir-to-client-js/phases/props-extraction.ts +5 -9
  89. package/src/ir-to-client-js/plan/build-static-array-child-init.ts +12 -1
  90. package/src/ir-to-client-js/plan/static-array-child-init.ts +20 -1
  91. package/src/ir-to-client-js/reactivity.ts +6 -0
  92. package/src/ir-to-client-js/stringify/static-array-child-init.ts +11 -6
  93. package/src/ir-to-client-js/types.ts +21 -0
  94. package/src/jsx-to-ir.ts +230 -39
  95. package/src/signal-init-eval.ts +165 -0
  96. package/src/static-literal.ts +128 -0
package/dist/index.js CHANGED
@@ -197,6 +197,24 @@ import ts8 from "typescript";
197
197
 
198
198
  // src/expression-parser.ts
199
199
  import ts2 from "typescript";
200
+ var PARSED_EXPR_KINDS = [
201
+ "identifier",
202
+ "literal",
203
+ "call",
204
+ "member",
205
+ "index-access",
206
+ "binary",
207
+ "unary",
208
+ "conditional",
209
+ "logical",
210
+ "template-literal",
211
+ "arrow",
212
+ "regex",
213
+ "array-literal",
214
+ "object-literal",
215
+ "array-method",
216
+ "unsupported"
217
+ ];
200
218
  var UNSUPPORTED_METHODS = new Set([
201
219
  "filter",
202
220
  "map",
@@ -2942,6 +2960,29 @@ function extractFreeIdentifiersFromText(text) {
2942
2960
  const expr = ts4.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
2943
2961
  return extractFreeIdentifiersFromNode(expr);
2944
2962
  }
2963
+ function extractFreeIdentifiersFromStatementText(text) {
2964
+ if (!text || text.trim().length === 0)
2965
+ return new Set;
2966
+ const sf = ts4.createSourceFile("__free_ids_stmt__.ts", text, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TS);
2967
+ return extractFreeIdentifiersFromNode(sf);
2968
+ }
2969
+ function extractFreeIdentifiersFromTemplateText(template) {
2970
+ if (!template || template.length === 0)
2971
+ return new Set;
2972
+ const sf = ts4.createSourceFile("__free_ids_template__.ts", `(\`${template}\`);`, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TS);
2973
+ const stmt = sf.statements[0];
2974
+ if (!stmt || !ts4.isExpressionStatement(stmt))
2975
+ return new Set;
2976
+ const expr = ts4.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
2977
+ if (!ts4.isTemplateExpression(expr))
2978
+ return new Set;
2979
+ const ids = new Set;
2980
+ for (const span of expr.templateSpans) {
2981
+ for (const id of extractFreeIdentifiersFromNode(span.expression))
2982
+ ids.add(id);
2983
+ }
2984
+ return ids;
2985
+ }
2945
2986
  function extractMemoBodyExpr(computation) {
2946
2987
  const arrowMatch = computation.match(/^\(\)\s*=>\s*(.+)$/s);
2947
2988
  if (!arrowMatch)
@@ -3470,6 +3511,133 @@ function buildLoopSkeletonTemplate(node, safe) {
3470
3511
  return assertNever(node);
3471
3512
  }
3472
3513
  }
3514
+ var SKELETON_PATH_HAZARD_TAGS = new Set([
3515
+ "table",
3516
+ "thead",
3517
+ "tbody",
3518
+ "tfoot",
3519
+ "caption",
3520
+ "colgroup",
3521
+ "col",
3522
+ "select",
3523
+ "optgroup",
3524
+ "p",
3525
+ "pre",
3526
+ "textarea",
3527
+ "listing",
3528
+ "template",
3529
+ "math"
3530
+ ]);
3531
+ var SKELETON_PATH_FORCE_CLOSE_GROUPS = [
3532
+ new Set(["a"]),
3533
+ new Set(["button"]),
3534
+ new Set(["form"]),
3535
+ new Set(["option"]),
3536
+ new Set(["h1", "h2", "h3", "h4", "h5", "h6"]),
3537
+ new Set(["dd", "dt"]),
3538
+ new Set(["li"])
3539
+ ];
3540
+ function skeletonForceCloseGroup(tag) {
3541
+ return SKELETON_PATH_FORCE_CLOSE_GROUPS.findIndex((group) => group.has(tag));
3542
+ }
3543
+ function computeSkeletonSlotPaths(node, safe) {
3544
+ const state = { elementPaths: new Map, textMarkerPaths: new Map, bailed: false };
3545
+ walkSkeletonPathNode(node, [], safe, state, new Set);
3546
+ if (state.bailed)
3547
+ return null;
3548
+ return { elementPaths: state.elementPaths, textMarkerPaths: state.textMarkerPaths };
3549
+ }
3550
+ function walkSkeletonPathNode(node, path, safe, state, forceCloseAncestors) {
3551
+ if (state.bailed || node.type !== "element")
3552
+ return;
3553
+ if (SKELETON_PATH_HAZARD_TAGS.has(node.tag)) {
3554
+ state.bailed = true;
3555
+ return;
3556
+ }
3557
+ const groupIdx = skeletonForceCloseGroup(node.tag);
3558
+ if (groupIdx >= 0 && forceCloseAncestors.has(groupIdx)) {
3559
+ state.bailed = true;
3560
+ return;
3561
+ }
3562
+ const flatChildren = flattenSkeletonChildren(node.children);
3563
+ if (VOID_ELEMENTS.has(node.tag) && flatChildren.length > 0) {
3564
+ state.bailed = true;
3565
+ return;
3566
+ }
3567
+ if (node.tag === "tr" && hasForeignTableRowContent(flatChildren)) {
3568
+ state.bailed = true;
3569
+ return;
3570
+ }
3571
+ if (node.slotId)
3572
+ state.elementPaths.set(node.slotId, path);
3573
+ const nextAncestors = groupIdx >= 0 ? new Set([...forceCloseAncestors, groupIdx]) : forceCloseAncestors;
3574
+ walkSkeletonPathChildren(flatChildren, path, safe, state, nextAncestors);
3575
+ }
3576
+ function hasForeignTableRowContent(children) {
3577
+ for (const child of children) {
3578
+ if (child.type === "text") {
3579
+ if (child.value.trim() !== "")
3580
+ return true;
3581
+ continue;
3582
+ }
3583
+ if (child.type === "element" && child.tag !== "td" && child.tag !== "th") {
3584
+ return true;
3585
+ }
3586
+ }
3587
+ return false;
3588
+ }
3589
+ function flattenSkeletonChildren(children) {
3590
+ const out = [];
3591
+ for (const child of children) {
3592
+ if (child.type === "fragment") {
3593
+ out.push(...flattenSkeletonChildren(child.children));
3594
+ } else {
3595
+ out.push(child);
3596
+ }
3597
+ }
3598
+ return out;
3599
+ }
3600
+ function walkSkeletonPathChildren(children, parentPath, safe, state, forceCloseAncestors) {
3601
+ let idx = 0;
3602
+ let pendingText = false;
3603
+ for (const child of children) {
3604
+ if (state.bailed)
3605
+ return;
3606
+ switch (child.type) {
3607
+ case "text": {
3608
+ if (child.value === "")
3609
+ continue;
3610
+ if (!pendingText)
3611
+ idx += 1;
3612
+ pendingText = true;
3613
+ continue;
3614
+ }
3615
+ case "expression": {
3616
+ if (child.expr === "null" || child.expr === "undefined")
3617
+ continue;
3618
+ if (!child.slotId || !safe.reactiveTextSlotIds.has(child.slotId)) {
3619
+ state.bailed = true;
3620
+ return;
3621
+ }
3622
+ state.textMarkerPaths.set(child.slotId, [...parentPath, idx]);
3623
+ idx += 2;
3624
+ pendingText = false;
3625
+ continue;
3626
+ }
3627
+ case "element": {
3628
+ walkSkeletonPathNode(child, [...parentPath, idx], safe, state, forceCloseAncestors);
3629
+ idx += 1;
3630
+ pendingText = false;
3631
+ continue;
3632
+ }
3633
+ case "fragment":
3634
+ continue;
3635
+ default:
3636
+ state.bailed = true;
3637
+ return;
3638
+ }
3639
+ }
3640
+ }
3473
3641
  function irToPlaceholderTemplate(node, restSpreadNames, loopDepth = 0, loopParams) {
3474
3642
  const recurse = (n) => irToPlaceholderTemplate(n, restSpreadNames, loopDepth, loopParams);
3475
3643
  const wrapExpr = (expr) => wrapExprWithLoopParams(expr, loopParams);
@@ -4112,7 +4280,28 @@ function generateCsrTemplateWithOpts(node, opts) {
4112
4280
  return `\${renderChild('${nameForRegistryRef(node.name)}', ${propsExpr}${keyArg || (slotArg ? ", undefined" : "")}${slotArg})}`;
4113
4281
  }
4114
4282
  case "loop": {
4115
- let childTemplate = node.children.map(recurseInLoop).join("");
4283
+ const boundHere = new Set(opts.loopBoundNames ?? []);
4284
+ if (node.paramBindings && node.paramBindings.length > 0) {
4285
+ for (const b of node.paramBindings)
4286
+ boundHere.add(b.name);
4287
+ } else if (!node.param.startsWith("[") && !node.param.startsWith("{")) {
4288
+ boundHere.add(node.param);
4289
+ }
4290
+ if (node.index)
4291
+ boundHere.add(node.index);
4292
+ const childEnv = {
4293
+ ...env,
4294
+ substitutions: new Map([...env.substitutions].filter(([name]) => !boundHere.has(name)))
4295
+ };
4296
+ const recurseInLoopBody = (n) => generateCsrTemplateWithOpts(n, {
4297
+ ...opts,
4298
+ insideLoop: true,
4299
+ loopDepth: loopDepth + 1,
4300
+ inHoistedChildren: false,
4301
+ loopBoundNames: boundHere,
4302
+ csrEnv: childEnv
4303
+ });
4304
+ let childTemplate = node.children.map(recurseInLoopBody).join("");
4116
4305
  if (node.bodyIsItemConditional && node.key) {
4117
4306
  childTemplate = `${itemAnchorTemplate(node.key)}${childTemplate}`;
4118
4307
  }
@@ -4126,7 +4315,7 @@ function generateCsrTemplateWithOpts(node, opts) {
4126
4315
  if (node.flatMapCallback) {
4127
4316
  let body = node.flatMapCallback.templateBody ?? node.flatMapCallback.body;
4128
4317
  for (const frag of node.flatMapCallback.fragments) {
4129
- const renderedIr = recurseInLoop(frag.ir);
4318
+ const renderedIr = recurseInLoopBody(frag.ir);
4130
4319
  body = body.replace(frag.placeholder, `\`${renderedIr}\``);
4131
4320
  }
4132
4321
  body = applyPropsRewrite(body, propsObjectName ?? null);
@@ -5086,6 +5275,8 @@ function needsTypeBasedDetection(source) {
5086
5275
  return true;
5087
5276
  if (/\.map\s*\(/.test(source))
5088
5277
  return true;
5278
+ if (source.includes("createSelector"))
5279
+ return true;
5089
5280
  return false;
5090
5281
  }
5091
5282
  function findBrandPackageImportLoc(sourceFile, filePath) {
@@ -6062,6 +6253,7 @@ var CLIENT_EXPORTS = new Set([
6062
6253
  "createEffect",
6063
6254
  "createDisposableEffect",
6064
6255
  "createMemo",
6256
+ "createSelector",
6065
6257
  "createRoot",
6066
6258
  "onCleanup",
6067
6259
  "onMount",
@@ -6845,12 +7037,13 @@ function extractProps(param, ctx) {
6845
7037
  continue;
6846
7038
  }
6847
7039
  const sourcePropName = element.propertyName && ts8.isIdentifier(element.propertyName) ? element.propertyName.text : localName;
6848
- const resolvedType = memberTypes?.get(sourcePropName) ?? { kind: "unknown", raw: "unknown" };
7040
+ const member = memberTypes?.get(sourcePropName);
7041
+ const resolvedType = member?.type ?? { kind: "unknown", raw: "unknown" };
6849
7042
  const defaultContainsArrow = element.initializer ? nodeContainsArrow(element.initializer) : false;
6850
7043
  ctx.propsParams.push({
6851
7044
  name: localName,
6852
7045
  type: resolvedType,
6853
- optional: !!element.initializer,
7046
+ optional: !!member?.optional || !!element.initializer,
6854
7047
  defaultValue,
6855
7048
  defaultContainsArrow: defaultContainsArrow || undefined
6856
7049
  });
@@ -6915,11 +7108,12 @@ function collectMemberTypes(typeNode, ctx) {
6915
7108
  const fromMembers = (members) => {
6916
7109
  const map = new Map;
6917
7110
  for (const member of members) {
6918
- if (ts8.isPropertySignature(member) && member.name && member.type && !member.questionToken) {
6919
- const info = typeNodeToTypeInfo(member.type, ctx.sourceFile);
6920
- if (info && isResolvablePrimitive(info)) {
6921
- map.set(member.name.getText(ctx.sourceFile), info);
6922
- }
7111
+ if (ts8.isPropertySignature(member) && member.name) {
7112
+ const info = member.type ? typeNodeToTypeInfo(member.type, ctx.sourceFile) : null;
7113
+ map.set(member.name.getText(ctx.sourceFile), {
7114
+ type: info && isResolvablePrimitive(info) ? info : null,
7115
+ optional: !!member.questionToken
7116
+ });
6923
7117
  }
6924
7118
  }
6925
7119
  return map;
@@ -8148,9 +8342,15 @@ function exprHasFunctionCalls(expr) {
8148
8342
  return found;
8149
8343
  }
8150
8344
  function rewriteBarePropRefs2(text, expr, ctx) {
8151
- const propNames = getDestructuredPropNames(ctx);
8345
+ let propNames = getDestructuredPropNames(ctx);
8152
8346
  if (!propNames)
8153
8347
  return;
8348
+ if (ctx.loopParams.size > 0) {
8349
+ const filtered = new Set([...propNames].filter((n) => !ctx.loopParams.has(n)));
8350
+ if (filtered.size === 0)
8351
+ return;
8352
+ propNames = filtered;
8353
+ }
8154
8354
  const extraPropRefs = collectBranchLocalPropRefsViaSubstitution(expr, ctx);
8155
8355
  return rewriteBarePropRefs(text, expr, propNames, extraPropRefs);
8156
8356
  }
@@ -8316,26 +8516,29 @@ function makeBindingEnv(ctx) {
8316
8516
  function parseValueExpr(trimmed) {
8317
8517
  return parseExpression(trimmed.startsWith("{") ? `(${trimmed})` : trimmed);
8318
8518
  }
8319
- function attachParsedExpressions(node) {
8519
+ var EMPTY_BOUND = new Set;
8520
+ function attachParsedExpressions(node, analyzer, bound = EMPTY_BOUND) {
8521
+ const parse2 = (trimmed) => resolveCallbackMethodFunctionReferences(parseExpression(trimmed), analyzer, bound);
8522
+ const parseValue = (trimmed) => resolveCallbackMethodFunctionReferences(parseValueExpr(trimmed), analyzer, bound);
8320
8523
  if (node.type === "expression") {
8321
8524
  const trimmed = node.expr.trim();
8322
8525
  if (trimmed)
8323
- node.parsed = parseExpression(trimmed);
8526
+ node.parsed = parse2(trimmed);
8324
8527
  } else if (node.type === "conditional" || node.type === "if-statement") {
8325
8528
  const trimmed = node.condition.trim();
8326
8529
  if (trimmed)
8327
- node.parsedCondition = parseExpression(trimmed);
8530
+ node.parsedCondition = parse2(trimmed);
8328
8531
  }
8329
8532
  if (node.type === "element") {
8330
8533
  for (const attr of node.attrs) {
8331
8534
  if (attr.value.kind === "expression") {
8332
8535
  const trimmed = attr.value.expr.trim();
8333
8536
  if (trimmed)
8334
- attr.value.parsed = parseValueExpr(trimmed);
8537
+ attr.value.parsed = parseValue(trimmed);
8335
8538
  } else if (attr.value.kind === "spread") {
8336
8539
  const trimmed = attr.value.expr.trim();
8337
8540
  if (trimmed)
8338
- attr.value.parsed = parseExpression(trimmed);
8541
+ attr.value.parsed = parse2(trimmed);
8339
8542
  }
8340
8543
  }
8341
8544
  } else if (node.type === "component") {
@@ -8343,14 +8546,14 @@ function attachParsedExpressions(node) {
8343
8546
  if (prop.value.kind === "expression") {
8344
8547
  const trimmed = prop.value.expr.trim();
8345
8548
  if (trimmed)
8346
- prop.value.parsed = parseValueExpr(trimmed);
8549
+ prop.value.parsed = parseValue(trimmed);
8347
8550
  }
8348
8551
  }
8349
8552
  } else if (node.type === "provider") {
8350
8553
  if (node.valueProp.value.kind === "expression") {
8351
8554
  const trimmed = node.valueProp.value.expr.trim();
8352
8555
  if (trimmed)
8353
- node.valueProp.value.parsed = parseValueExpr(trimmed);
8556
+ node.valueProp.value.parsed = parseValue(trimmed);
8354
8557
  }
8355
8558
  }
8356
8559
  switch (node.type) {
@@ -8359,47 +8562,51 @@ function attachParsedExpressions(node) {
8359
8562
  case "fragment":
8360
8563
  case "provider":
8361
8564
  for (const child of node.children)
8362
- attachParsedExpressions(child);
8565
+ attachParsedExpressions(child, analyzer, bound);
8363
8566
  break;
8364
8567
  case "async":
8365
- attachParsedExpressions(node.fallback);
8568
+ attachParsedExpressions(node.fallback, analyzer, bound);
8366
8569
  for (const child of node.children)
8367
- attachParsedExpressions(child);
8570
+ attachParsedExpressions(child, analyzer, bound);
8368
8571
  break;
8369
8572
  case "loop": {
8370
8573
  const trimmedArray = node.array.trim();
8371
8574
  if (trimmedArray)
8372
- node.arrayParsed = parseExpression(trimmedArray);
8575
+ node.arrayParsed = parse2(trimmedArray);
8576
+ const loopBound = new Set(bound);
8577
+ loopBound.add(node.param);
8578
+ if (node.index)
8579
+ loopBound.add(node.index);
8373
8580
  for (const child of node.children)
8374
- attachParsedExpressions(child);
8581
+ attachParsedExpressions(child, analyzer, loopBound);
8375
8582
  if (node.childComponent) {
8376
8583
  for (const child of node.childComponent.children)
8377
- attachParsedExpressions(child);
8584
+ attachParsedExpressions(child, analyzer, loopBound);
8378
8585
  }
8379
8586
  for (const nested of node.nestedComponents ?? []) {
8380
8587
  for (const child of nested.children)
8381
- attachParsedExpressions(child);
8588
+ attachParsedExpressions(child, analyzer, loopBound);
8382
8589
  }
8383
8590
  for (const frag of node.flatMapCallback?.fragments ?? []) {
8384
- attachParsedExpressions(frag.ir);
8591
+ attachParsedExpressions(frag.ir, analyzer, loopBound);
8385
8592
  }
8386
8593
  break;
8387
8594
  }
8388
8595
  case "conditional":
8389
- attachParsedExpressions(node.whenTrue);
8390
- attachParsedExpressions(node.whenFalse);
8596
+ attachParsedExpressions(node.whenTrue, analyzer, bound);
8597
+ attachParsedExpressions(node.whenFalse, analyzer, bound);
8391
8598
  break;
8392
8599
  case "if-statement":
8393
- attachParsedExpressions(node.consequent);
8600
+ attachParsedExpressions(node.consequent, analyzer, bound);
8394
8601
  if (node.alternate)
8395
- attachParsedExpressions(node.alternate);
8602
+ attachParsedExpressions(node.alternate, analyzer, bound);
8396
8603
  break;
8397
8604
  }
8398
8605
  }
8399
8606
  function jsxToIR(analyzer) {
8400
8607
  const root = buildIRRoot(analyzer);
8401
8608
  if (root)
8402
- attachParsedExpressions(root);
8609
+ attachParsedExpressions(root, analyzer);
8403
8610
  return root;
8404
8611
  }
8405
8612
  function buildIRRoot(analyzer) {
@@ -9486,8 +9693,8 @@ function extractSortComparator(callback, _method, ctx) {
9486
9693
  };
9487
9694
  }
9488
9695
  function resolveSortComparatorIdentifier(name, ctx) {
9489
- const constInfo = findLocalConst(name, ctx);
9490
- const fnInfo = findLocalFunction(name, ctx);
9696
+ const constInfo = findLocalConst(name, ctx.analyzer);
9697
+ const fnInfo = findLocalFunction(name, ctx.analyzer);
9491
9698
  if (constInfo && fnInfo)
9492
9699
  return null;
9493
9700
  if (constInfo) {
@@ -9500,6 +9707,72 @@ function resolveSortComparatorIdentifier(name, ctx) {
9500
9707
  }
9501
9708
  return null;
9502
9709
  }
9710
+ function resolveCallbackMethodFunctionReferenceIdentifier(name, analyzer) {
9711
+ const constInfo = findLocalConst(name, analyzer);
9712
+ const fnInfo = findLocalFunction(name, analyzer);
9713
+ if (constInfo && fnInfo)
9714
+ return null;
9715
+ if (constInfo) {
9716
+ const ast = parseConstInitializer(constInfo);
9717
+ return ast && (ts11.isArrowFunction(ast) || ts11.isFunctionExpression(ast)) ? ast : null;
9718
+ }
9719
+ if (fnInfo) {
9720
+ const ast = parseFunctionInfoAsExpr(fnInfo);
9721
+ return ast && (ts11.isArrowFunction(ast) || ts11.isFunctionExpression(ast)) ? ast : null;
9722
+ }
9723
+ return null;
9724
+ }
9725
+ function resolveCallbackMethodFunctionReferences(expr, analyzer, bound = EMPTY_BOUND) {
9726
+ function visit2(e, bound2) {
9727
+ switch (e.kind) {
9728
+ case "literal":
9729
+ case "identifier":
9730
+ case "regex":
9731
+ case "unsupported":
9732
+ return e;
9733
+ case "call": {
9734
+ const callee = visit2(e.callee, bound2);
9735
+ const args = e.args.map((a) => visit2(a, bound2));
9736
+ if (callee.kind === "member" && !callee.computed && CALLBACK_METHODS.has(callee.property) && args[0]?.kind === "identifier" && !bound2.has(args[0].name)) {
9737
+ const resolved = resolveCallbackMethodFunctionReferenceIdentifier(args[0].name, analyzer);
9738
+ const arrow = resolved ? tsNodeToParsedExpr(resolved) : null;
9739
+ if (arrow && arrow.kind === "arrow")
9740
+ args[0] = arrow;
9741
+ }
9742
+ return { ...e, callee, args };
9743
+ }
9744
+ case "member":
9745
+ return { ...e, object: visit2(e.object, bound2) };
9746
+ case "index-access":
9747
+ return { ...e, object: visit2(e.object, bound2), index: visit2(e.index, bound2) };
9748
+ case "binary":
9749
+ case "logical":
9750
+ return { ...e, left: visit2(e.left, bound2), right: visit2(e.right, bound2) };
9751
+ case "unary":
9752
+ return { ...e, argument: visit2(e.argument, bound2) };
9753
+ case "conditional":
9754
+ return { ...e, test: visit2(e.test, bound2), consequent: visit2(e.consequent, bound2), alternate: visit2(e.alternate, bound2) };
9755
+ case "template-literal":
9756
+ return { ...e, parts: e.parts.map((p) => p.type === "expression" ? { ...p, expr: visit2(p.expr, bound2) } : p) };
9757
+ case "array-literal":
9758
+ return { ...e, elements: e.elements.map((el) => visit2(el, bound2)) };
9759
+ case "array-method":
9760
+ return {
9761
+ ...e,
9762
+ object: visit2(e.object, bound2),
9763
+ args: e.args.map((a) => visit2(a, bound2)),
9764
+ ...e.method === "flat" && e.depthExpr ? { depthExpr: visit2(e.depthExpr, bound2) } : {}
9765
+ };
9766
+ case "object-literal":
9767
+ return { ...e, properties: e.properties.map((p) => ({ ...p, value: visit2(p.value, bound2) })) };
9768
+ case "arrow": {
9769
+ const inner = e.params.length === 0 ? bound2 : new Set([...bound2, ...e.params]);
9770
+ return { ...e, body: visit2(e.body, inner) };
9771
+ }
9772
+ }
9773
+ }
9774
+ return visit2(expr, bound);
9775
+ }
9503
9776
  function extractFilterPredicate(callback, ctx) {
9504
9777
  if (!ts11.isArrowFunction(callback))
9505
9778
  return { result: null };
@@ -9967,13 +10240,11 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
9967
10240
  setArray(innerSort.array);
9968
10241
  }
9969
10242
  } else {
9970
- array = ctx.getJS(filterInfo.array);
9971
- arrayExpr = filterInfo.array;
10243
+ setArray(filterInfo.array);
9972
10244
  }
9973
10245
  }
9974
10246
  } else {
9975
- array = ctx.getJS(chainSource);
9976
- arrayExpr = chainSource;
10247
+ setArray(chainSource);
9977
10248
  }
9978
10249
  const callback = node.arguments[0];
9979
10250
  let param = "item";
@@ -10523,7 +10794,7 @@ function parseTemplateLiteral(expr, ctx) {
10523
10794
  }
10524
10795
  function tryResolveTemplateSpanFromConst(expr, ctx) {
10525
10796
  if (ts11.isIdentifier(expr)) {
10526
- const constInfo = findLocalConst(expr.text, ctx);
10797
+ const constInfo = findLocalConst(expr.text, ctx.analyzer);
10527
10798
  if (!constInfo)
10528
10799
  return null;
10529
10800
  const ast = parseConstInitializer(constInfo);
@@ -10537,7 +10808,7 @@ function tryResolveTemplateSpanFromConst(expr, ctx) {
10537
10808
  if (ts11.isElementAccessExpression(expr)) {
10538
10809
  if (!ts11.isIdentifier(expr.expression))
10539
10810
  return null;
10540
- const constInfo = findLocalConst(expr.expression.text, ctx);
10811
+ const constInfo = findLocalConst(expr.expression.text, ctx.analyzer);
10541
10812
  if (!constInfo)
10542
10813
  return null;
10543
10814
  const ast = parseConstInitializer(constInfo);
@@ -10563,16 +10834,16 @@ function tryResolveTemplateSpanFromConst(expr, ctx) {
10563
10834
  }
10564
10835
  return null;
10565
10836
  }
10566
- function findLocalConst(name, ctx) {
10567
- const matches = ctx.analyzer.localConstants.filter((c) => c.name === name);
10837
+ function findLocalConst(name, analyzer) {
10838
+ const matches = analyzer.localConstants.filter((c) => c.name === name);
10568
10839
  if (matches.length === 0)
10569
10840
  return;
10570
10841
  const fnScoped = matches.filter((c) => !c.isModule);
10571
10842
  const pool = fnScoped.length > 0 ? fnScoped : matches;
10572
10843
  return pool[pool.length - 1];
10573
10844
  }
10574
- function findLocalFunction(name, ctx) {
10575
- const matches = ctx.analyzer.localFunctions.filter((f) => f.name === name);
10845
+ function findLocalFunction(name, analyzer) {
10846
+ const matches = analyzer.localFunctions.filter((f) => f.name === name);
10576
10847
  if (matches.length === 0)
10577
10848
  return;
10578
10849
  const fnScoped = matches.filter((f) => !f.isModule);
@@ -10614,7 +10885,9 @@ function hasDynamicTagBinding(name, sourceFile) {
10614
10885
  return found;
10615
10886
  }
10616
10887
  function tryResolveIdentifierAsTemplateLiteral(ident, ctx) {
10617
- const constInfo = findLocalConst(ident.text, ctx);
10888
+ if (ctx.loopParams.has(ident.text))
10889
+ return null;
10890
+ const constInfo = findLocalConst(ident.text, ctx.analyzer);
10618
10891
  if (!constInfo)
10619
10892
  return null;
10620
10893
  const ast = parseConstInitializer(constInfo);
@@ -10711,8 +10984,8 @@ function tryDesugarInterleaveTaggedTemplate(expr, ctx) {
10711
10984
  return rewritten ?? expr;
10712
10985
  }
10713
10986
  function resolveInterleaveTagIdentifier(name, ctx) {
10714
- const constInfo = findLocalConst(name, ctx);
10715
- const fnInfo = findLocalFunction(name, ctx);
10987
+ const constInfo = findLocalConst(name, ctx.analyzer);
10988
+ const fnInfo = findLocalFunction(name, ctx.analyzer);
10716
10989
  if (constInfo && fnInfo)
10717
10990
  return null;
10718
10991
  if (constInfo) {
@@ -11466,6 +11739,7 @@ function collectLoopChildEventsWithNesting(node, initialNestingStack = []) {
11466
11739
  depth: scope.nestingStack.length + 1,
11467
11740
  array: l.array,
11468
11741
  param: l.param,
11742
+ index: l.index,
11469
11743
  key: l.key,
11470
11744
  markerId: l.markerId,
11471
11745
  containerSlotId: scope.lastElementSlotId,
@@ -11556,73 +11830,224 @@ function collectLoopChildReactiveAttrs(node, ctx, loopParam, loopParamBindings)
11556
11830
  return attrs;
11557
11831
  }
11558
11832
 
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;
11833
+ // src/ir-to-client-js/control-flow/stringify/template-parse.ts
11834
+ var SVG_ROOT_TAGS = new Set([
11835
+ "svg",
11836
+ "path",
11837
+ "circle",
11838
+ "rect",
11839
+ "line",
11840
+ "polyline",
11841
+ "polygon",
11842
+ "ellipse",
11843
+ "text",
11844
+ "tspan",
11845
+ "textPath",
11846
+ "g",
11847
+ "defs",
11848
+ "use",
11849
+ "symbol",
11850
+ "switch",
11851
+ "clipPath",
11852
+ "mask",
11853
+ "marker",
11854
+ "pattern",
11855
+ "linearGradient",
11856
+ "radialGradient",
11857
+ "stop",
11858
+ "image",
11859
+ "foreignObject",
11860
+ "filter",
11861
+ "feBlend",
11862
+ "feColorMatrix",
11863
+ "feComposite",
11864
+ "feFlood",
11865
+ "feGaussianBlur",
11866
+ "feMerge",
11867
+ "feMergeNode",
11868
+ "feMorphology",
11869
+ "feOffset",
11870
+ "feTurbulence",
11871
+ "animate",
11872
+ "animateTransform",
11873
+ "animateMotion"
11874
+ ]);
11875
+ function templateRootIsSvg(template) {
11876
+ const stripped = stripLeadingNonContent(template);
11877
+ const m = stripped.match(/^<\s*([A-Za-z][A-Za-z0-9-]*)/);
11878
+ if (m) {
11879
+ const tag = m[1];
11880
+ if (SVG_ROOT_TAGS.has(tag))
11881
+ return true;
11882
+ return SVG_ROOT_TAGS.has(tag.toLowerCase());
11594
11883
  }
11884
+ const branches = extractConditionalBranchTemplates(stripped);
11885
+ if (branches === null || branches.length === 0)
11886
+ return false;
11887
+ return branches.every(templateRootIsSvg);
11595
11888
  }
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);
11889
+ function multiRootTemplateNeedsSvgWrap(template) {
11890
+ const m = stripLeadingNonContent(template).match(/^<\s*([A-Za-z][A-Za-z0-9-]*)/);
11891
+ if (m && m[1].toLowerCase() === "svg")
11892
+ return false;
11893
+ return templateRootIsSvg(template);
11894
+ }
11895
+ function stripLeadingNonContent(template) {
11896
+ let s = template.trimStart();
11897
+ while (s.startsWith("<!--")) {
11898
+ const end = s.indexOf("-->");
11899
+ if (end < 0)
11900
+ return s;
11901
+ s = s.slice(end + 3).trimStart();
11607
11902
  }
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(" + ")})`;
11903
+ return s;
11612
11904
  }
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;
11905
+ function extractConditionalBranchTemplates(template) {
11906
+ if (!template.startsWith("${"))
11907
+ return null;
11908
+ const exprEnd = findInterpolationEnd(template, 2);
11909
+ if (exprEnd < 0)
11910
+ return null;
11911
+ const trailing = stripLeadingNonContent(template.slice(exprEnd + 1));
11912
+ if (trailing.length > 0)
11913
+ return null;
11914
+ const expr = template.slice(2, exprEnd);
11915
+ return findTopLevelTemplateLiterals(expr);
11615
11916
  }
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") {
11917
+ function emitTemplateCloneInline(template) {
11918
+ if (templateRootIsSvg(template)) {
11919
+ return `const __tpl = document.createElement('template'); __tpl.innerHTML = \`<svg>${template}</svg>\`; return __tpl.content.firstElementChild.firstElementChild.cloneNode(true)`;
11920
+ }
11921
+ return `const __tpl = document.createElement('template'); __tpl.innerHTML = \`${template}\`; return __tpl.content.firstElementChild.cloneNode(true)`;
11922
+ }
11923
+ function emitHoistedTemplateDecl(lines, indent, tplVar, skeletonTemplate) {
11924
+ const isSvg = templateRootIsSvg(skeletonTemplate);
11925
+ const html = isSvg ? `<svg>${skeletonTemplate}</svg>` : skeletonTemplate;
11926
+ lines.push(`${indent}const ${tplVar} = document.createElement('template')`);
11927
+ lines.push(`${indent}${tplVar}.innerHTML = \`${html}\``);
11928
+ }
11929
+ function hoistedCloneExpr(tplVar, skeletonTemplate) {
11930
+ return templateRootIsSvg(skeletonTemplate) ? `${tplVar}.content.firstElementChild.firstElementChild.cloneNode(true)` : `${tplVar}.content.firstElementChild.cloneNode(true)`;
11931
+ }
11932
+ function emitTemplateCloneLines(template, indent) {
11933
+ if (templateRootIsSvg(template)) {
11934
+ return [
11935
+ `${indent}const __tpl = document.createElement('template')`,
11936
+ `${indent}__tpl.innerHTML = \`<svg>${template}</svg>\``,
11937
+ `${indent}return __tpl.content.firstElementChild.firstElementChild.cloneNode(true)`
11938
+ ];
11939
+ }
11940
+ return [
11941
+ `${indent}const __tpl = document.createElement('template')`,
11942
+ `${indent}__tpl.innerHTML = \`${template}\``,
11943
+ `${indent}return __tpl.content.firstElementChild.cloneNode(true)`
11944
+ ];
11945
+ }
11946
+ function emitLoopItemElementSetup(lines, opts) {
11947
+ const { template, bodyIsMultiRoot, indent, singleRootLayout } = opts;
11948
+ const innerIndent = indent + " ";
11949
+ if (bodyIsMultiRoot) {
11950
+ lines.push(`${indent}let __el, __extras`);
11951
+ lines.push(`${indent}if (__existing) {`);
11952
+ lines.push(`${innerIndent}__el = __existing`);
11953
+ lines.push(`${indent}} else {`);
11954
+ for (const ln of emitMultiRootTemplateCloneLines(template, innerIndent, "__el", "__extras")) {
11955
+ lines.push(ln);
11956
+ }
11957
+ lines.push(`${innerIndent}__el.__bfExtras = __extras`);
11958
+ lines.push(`${indent}}`);
11959
+ return;
11960
+ }
11961
+ if (singleRootLayout === "inline") {
11962
+ const cloneExpr = emitTemplateCloneInline(template);
11963
+ lines.push(`${indent}const __el = __existing ?? (() => { ${cloneExpr} })()`);
11964
+ return;
11965
+ }
11966
+ lines.push(`${indent}const __el = __existing ?? (() => {`);
11967
+ for (const ln of emitTemplateCloneLines(template, innerIndent))
11968
+ lines.push(ln);
11969
+ lines.push(`${indent}})()`);
11970
+ }
11971
+ function emitMultiRootTemplateCloneLines(template, indent, varEl, varExtras) {
11972
+ const isSvg = multiRootTemplateNeedsSvgWrap(template);
11973
+ const innerHtmlExpr = isSvg ? `\`<svg>${template}</svg>\`` : `\`${template}\``;
11974
+ const parentExpr = isSvg ? `__tpl.content.firstElementChild` : `__tpl.content`;
11975
+ return [
11976
+ `${indent}const __tpl = document.createElement('template')`,
11977
+ `${indent}__tpl.innerHTML = ${innerHtmlExpr}`,
11978
+ `${indent}${varEl} = ${parentExpr}.firstElementChild.cloneNode(true)`,
11979
+ `${indent}${varExtras} = []`,
11980
+ `${indent}{ let __sib = ${parentExpr}.firstElementChild.nextElementSibling; while (__sib) { ${varExtras}.push(__sib.cloneNode(true)); __sib = __sib.nextElementSibling } }`
11981
+ ];
11982
+ }
11983
+
11984
+ // src/ir-to-client-js/collect-elements.ts
11985
+ var EMPTY_RENDER_EXPRS = new Set(["null", "undefined", "false", "''", '""', "``"]);
11986
+ function domElementCount(node) {
11987
+ switch (node.type) {
11988
+ case "element":
11989
+ case "component":
11990
+ case "provider":
11991
+ case "async":
11992
+ return 1;
11993
+ case "text":
11994
+ return 0;
11995
+ case "expression":
11996
+ return EMPTY_RENDER_EXPRS.has(node.expr.trim()) ? 0 : null;
11997
+ case "loop":
11998
+ if (node.bodyIsItemConditional || node.method === "flatMap")
11999
+ return null;
12000
+ return `(${buildLoopChainExpr({
12001
+ base: node.array,
12002
+ sortComparator: node.sortComparator,
12003
+ filterPredicate: node.filterPredicate,
12004
+ chainOrder: node.chainOrder
12005
+ })}).length`;
12006
+ case "conditional": {
12007
+ const t = domElementCount(node.whenTrue);
12008
+ const f = domElementCount(node.whenFalse);
12009
+ if (t === null || f === null)
12010
+ return null;
12011
+ if (typeof t === "number" && typeof f === "number" && t === f)
12012
+ return t;
12013
+ return `(${node.condition} ? ${t} : ${f})`;
12014
+ }
12015
+ case "fragment":
12016
+ return sumElementCounts(node.children);
12017
+ default:
12018
+ return null;
12019
+ }
12020
+ }
12021
+ function sumElementCounts(nodes) {
12022
+ let staticCount = 0;
12023
+ const dynamic = [];
12024
+ for (const n of nodes) {
12025
+ const c = domElementCount(n);
12026
+ if (c === null)
12027
+ return null;
12028
+ if (typeof c === "number")
12029
+ staticCount += c;
12030
+ else
12031
+ dynamic.push(c);
12032
+ }
12033
+ if (dynamic.length === 0)
12034
+ return staticCount;
12035
+ const parts = staticCount > 0 ? [String(staticCount), ...dynamic] : dynamic;
12036
+ return parts.length === 1 ? parts[0] : `(${parts.join(" + ")})`;
12037
+ }
12038
+ function legacyElementCount(node) {
12039
+ 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;
12040
+ }
12041
+ function computeLoopSiblingOffsets(root) {
12042
+ const offsets = new Map;
12043
+ const recordRun = (children, preceding) => {
12044
+ for (const child of children) {
12045
+ if (child.type === "loop") {
12046
+ if (preceding.length > 0 && !offsets.has(child)) {
12047
+ offsets.set(child, [...preceding]);
12048
+ }
12049
+ preceding.push(child);
12050
+ } else if (child.type === "fragment" || child.type === "provider" || child.type === "async") {
11626
12051
  recordRun(child.children, preceding);
11627
12052
  } else {
11628
12053
  preceding.push(child);
@@ -11734,6 +12159,7 @@ function collectInnerLoops(nodes, siblingOffsets, outerLoopParam, ctx, options)
11734
12159
  arrayFreeIdentifiers: n.arrayFreeIdentifiers,
11735
12160
  param: n.param,
11736
12161
  paramBindings: n.paramBindings,
12162
+ index: n.index,
11737
12163
  key: n.key,
11738
12164
  markerId: n.markerId,
11739
12165
  bodyIsMultiRoot: n.bodyIsMultiRoot,
@@ -11931,6 +12357,7 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
11931
12357
  let template = "";
11932
12358
  let staticItemTemplate;
11933
12359
  let skeletonTemplate;
12360
+ let skeletonPaths;
11934
12361
  if (l.childComponent) {
11935
12362
  template = "";
11936
12363
  if (l.isStaticArray && l.children[0]) {
@@ -11942,10 +12369,14 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
11942
12369
  if (l.isStaticArray) {
11943
12370
  staticItemTemplate = useElementReconciliation ? irToPlaceholderTemplate(l.children[0], buildRestSpreadNames(ctx), 0) : irToHtmlTemplate(l.children[0], buildRestSpreadNames(ctx), 0);
11944
12371
  } else if (!useElementReconciliation && !l.bodyIsMultiRoot && !l.bodyIsItemConditional) {
11945
- skeletonTemplate = buildLoopSkeletonTemplate(l.children[0], {
12372
+ const skeletonSafeSlots = {
11946
12373
  reactiveAttrKeys: new Set(bindings.reactiveAttrs.map((a) => `${a.childSlotId}::${a.attrName}`)),
11947
12374
  reactiveTextSlotIds: new Set(bindings.reactiveTexts.map((t) => t.slotId))
11948
- }) ?? undefined;
12375
+ };
12376
+ skeletonTemplate = buildLoopSkeletonTemplate(l.children[0], skeletonSafeSlots) ?? undefined;
12377
+ if (skeletonTemplate && !templateRootIsSvg(skeletonTemplate)) {
12378
+ skeletonPaths = computeSkeletonSlotPaths(l.children[0], skeletonSafeSlots) ?? undefined;
12379
+ }
11949
12380
  }
11950
12381
  }
11951
12382
  ctx.loopElements.push({
@@ -11965,6 +12396,7 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
11965
12396
  template,
11966
12397
  staticItemTemplate,
11967
12398
  skeletonTemplate,
12399
+ skeletonPaths,
11968
12400
  childEventHandlers: childHandlers,
11969
12401
  bindings,
11970
12402
  childComponent: l.childComponent,
@@ -12755,6 +13187,7 @@ var RUNTIME_IMPORT_CANDIDATES = [
12755
13187
  "__slot",
12756
13188
  "__bfSlot",
12757
13189
  "__bfText",
13190
+ "tAfter",
12758
13191
  "beginTurn",
12759
13192
  "endTurn"
12760
13193
  ];
@@ -14571,13 +15004,6 @@ function emitPropsExtraction(lines, ctx, neededProps, propUsage) {
14571
15004
  lines.push(` const ${propName} = ${PROPS_PARAM}.${propName} ?? []`);
14572
15005
  } else if (propHasPropertyAccess(usage) && !propsUsedAsConditions.has(propName)) {
14573
15006
  lines.push(` const ${propName} = ${PROPS_PARAM}.${propName} ?? {}`);
14574
- } else if (prop?.optional && prop?.type) {
14575
- const inferredDefault = inferDefaultValue(prop.type);
14576
- if (inferredDefault !== "undefined") {
14577
- lines.push(` const ${propName} = ${PROPS_PARAM}.${propName} ?? ${inferredDefault}`);
14578
- } else {
14579
- lines.push(` const ${propName} = ${PROPS_PARAM}.${propName}`);
14580
- }
14581
15007
  } else {
14582
15008
  lines.push(` const ${propName} = ${PROPS_PARAM}.${propName}`);
14583
15009
  }
@@ -14625,9 +15051,63 @@ import { BF_SCOPE as BF_SCOPE2, BF_HOST, BF_AT } from "@barefootjs/shared";
14625
15051
  function loopKeyFn(loop) {
14626
15052
  if (loop.key === null)
14627
15053
  return "null";
14628
- const params = loop.kind === "nested" ? loop.param : `${loop.param}${loop.index ? `, ${loop.index}` : ""}`;
15054
+ const params = `${loop.param}${loop.index ? `, ${loop.index}` : ""}`;
14629
15055
  return `(${params}) => String(${loop.key})`;
14630
15056
  }
15057
+ function nestedLoopReferencesIndex(inner, comps, events) {
15058
+ const index = inner.index;
15059
+ if (!index)
15060
+ return false;
15061
+ const exprRefs = (text, precomputed) => {
15062
+ if (precomputed)
15063
+ return precomputed.has(index);
15064
+ if (!text)
15065
+ return false;
15066
+ return extractFreeIdentifiersFromText(text).has(index);
15067
+ };
15068
+ if (exprRefs(inner.key))
15069
+ return true;
15070
+ for (const t of inner.bindings.reactiveTexts) {
15071
+ if (exprRefs(t.expression, t.freeIdentifiers))
15072
+ return true;
15073
+ }
15074
+ for (const a of inner.bindings.reactiveAttrs) {
15075
+ if (exprRefs(a.expression, a.freeIdentifiers))
15076
+ return true;
15077
+ }
15078
+ if (inner.mapPreamble && extractFreeIdentifiersFromStatementText(inner.mapPreamble).has(index))
15079
+ return true;
15080
+ if (inner.template && extractFreeIdentifiersFromTemplateText(inner.template).has(index))
15081
+ return true;
15082
+ for (const ev of events) {
15083
+ if (exprRefs(ev.handler))
15084
+ return true;
15085
+ }
15086
+ for (const r of inner.bindings.refs) {
15087
+ if (exprRefs(r.callback))
15088
+ return true;
15089
+ }
15090
+ for (const c of comps) {
15091
+ for (const p of c.props) {
15092
+ if (exprRefs(attrValueToString(p.value)))
15093
+ return true;
15094
+ }
15095
+ if (c.children?.length) {
15096
+ const childrenExpr = irChildrenToJsExpr(c.children);
15097
+ if (childrenExpr && exprRefs(childrenExpr))
15098
+ return true;
15099
+ }
15100
+ }
15101
+ return false;
15102
+ }
15103
+ function nestedLoopIndexAlias(inner, syntheticIndexVar, paramHead, comps, events) {
15104
+ const index = inner.index;
15105
+ if (!index || index === paramHead)
15106
+ return null;
15107
+ if (!nestedLoopReferencesIndex(inner, comps, events))
15108
+ return null;
15109
+ return `const ${index} = ${syntheticIndexVar}`;
15110
+ }
14631
15111
  function buildChildRefBindings(refs, loopParam, loopParamBindings) {
14632
15112
  if (refs.length === 0)
14633
15113
  return [];
@@ -14808,6 +15288,7 @@ function buildOuterNestedPlan(elem, comp) {
14808
15288
  }
14809
15289
  function buildInnerLoopNestedPlan(elem, innerLoop, innerComps) {
14810
15290
  const outerIndexParam = elem.index || "__idx";
15291
+ const innerIndexParam = innerLoop.index || "__innerIdx";
14811
15292
  const comps = innerComps.map((comp) => ({
14812
15293
  componentName: comp.name,
14813
15294
  selector: buildCompSelector(comp),
@@ -14824,7 +15305,8 @@ function buildInnerLoopNestedPlan(elem, innerLoop, innerComps) {
14824
15305
  innerContainerSlotId: innerLoop.containerSlotId ?? null,
14825
15306
  innerArrayExpr: innerLoop.array,
14826
15307
  innerParam: innerLoop.param,
14827
- innerOffsetExpr: buildLoopChildIndexExpr("__innerIdx", innerLoop.offset),
15308
+ innerIndexParam,
15309
+ innerOffsetExpr: buildLoopChildIndexExpr(innerIndexParam, innerLoop.offset),
14828
15310
  innerPreludeStatements: innerLoop.mapPreamble ? [innerLoop.mapPreamble] : [],
14829
15311
  depth: innerLoop.depth,
14830
15312
  comps
@@ -14841,9 +15323,11 @@ function buildComponentRootedInnerLoopPlan(elem, innerLoop, innerComps) {
14841
15323
  containerVar: `_${varSlotId(elem.slotId)}`,
14842
15324
  outerArrayExpr: elem.array,
14843
15325
  outerParam: elem.param,
15326
+ outerIndexParam: elem.index,
14844
15327
  outerPreludeStatements: elem.mapPreamble ? [elem.mapPreamble] : [],
14845
15328
  innerArrayExpr: innerLoop.array,
14846
15329
  innerParam: innerLoop.param,
15330
+ innerIndexParam: innerLoop.index,
14847
15331
  innerPreludeStatements: innerLoop.mapPreamble ? [innerLoop.mapPreamble] : [],
14848
15332
  depth: innerLoop.depth,
14849
15333
  comps
@@ -14936,6 +15420,7 @@ function emitInnerLoopNested(lines, plan) {
14936
15420
  innerContainerSlotId,
14937
15421
  innerArrayExpr,
14938
15422
  innerParam,
15423
+ innerIndexParam,
14939
15424
  innerOffsetExpr,
14940
15425
  innerPreludeStatements,
14941
15426
  depth,
@@ -14954,7 +15439,7 @@ function emitInnerLoopNested(lines, plan) {
14954
15439
  } else {
14955
15440
  lines.push(` const __ic = __outerEl`);
14956
15441
  }
14957
- lines.push(` ${innerArrayExpr}.forEach((${innerParam}, __innerIdx) => {`);
15442
+ lines.push(` ${innerArrayExpr}.forEach((${innerParam}, ${innerIndexParam}) => {`);
14958
15443
  lines.push(` const __innerEl = __ic.children[${innerOffsetExpr}]`);
14959
15444
  lines.push(` if (!__innerEl) return`);
14960
15445
  for (const stmt of innerPreludeStatements) {
@@ -14975,9 +15460,11 @@ function emitComponentRootedInnerLoop(lines, plan) {
14975
15460
  containerVar,
14976
15461
  outerArrayExpr,
14977
15462
  outerParam,
15463
+ outerIndexParam,
14978
15464
  outerPreludeStatements,
14979
15465
  innerArrayExpr,
14980
15466
  innerParam,
15467
+ innerIndexParam,
14981
15468
  innerPreludeStatements,
14982
15469
  depth,
14983
15470
  comps
@@ -14991,11 +15478,11 @@ function emitComponentRootedInnerLoop(lines, plan) {
14991
15478
  lines.push(` const ${scopesVar(i)} = qsaChildScopes(${containerVar}, ${comp.selector})`);
14992
15479
  lines.push(` let ${cursorVar(i)} = 0`);
14993
15480
  });
14994
- lines.push(` ${outerArrayExpr}.forEach((${outerParam}) => {`);
15481
+ lines.push(` ${outerArrayExpr}.forEach((${outerParam}${outerIndexParam ? `, ${outerIndexParam}` : ""}) => {`);
14995
15482
  for (const stmt of outerPreludeStatements) {
14996
15483
  lines.push(` ${stmt}`);
14997
15484
  }
14998
- lines.push(` ${innerArrayExpr}.forEach((${innerParam}) => {`);
15485
+ lines.push(` ${innerArrayExpr}.forEach((${innerParam}${innerIndexParam ? `, ${innerIndexParam}` : ""}) => {`);
14999
15486
  for (const stmt of innerPreludeStatements) {
15000
15487
  lines.push(` ${stmt}`);
15001
15488
  }
@@ -15147,6 +15634,7 @@ function buildBranchInnerLoopsPlan(args) {
15147
15634
  ...ev,
15148
15635
  handler: wrapInner(ev.handler)
15149
15636
  }));
15637
+ const indexAlias = nestedLoopIndexAlias(inner, `__bidxbr_${i}`, paramHead, inner.childComponents ?? [], inner.bindings.events);
15150
15638
  const reactiveTexts = inner.bindings.reactiveTexts.map((text) => ({
15151
15639
  slotId: text.slotId,
15152
15640
  wrappedExpression: wrapBoth(text.expression),
@@ -15161,6 +15649,7 @@ function buildBranchInnerLoopsPlan(args) {
15161
15649
  keyFn: loopKeyFn(inner),
15162
15650
  paramHead,
15163
15651
  paramUnwrap,
15652
+ indexAlias,
15164
15653
  wrappedTemplate: inner.template,
15165
15654
  wrappedKey,
15166
15655
  keyDepth: 1,
@@ -15402,7 +15891,7 @@ function buildInnerLoopsPlan(args) {
15402
15891
  const containerExpr = inner.containerSlotId ? `qsa(${parentElVar}, '[bf="${inner.containerSlotId}"]')` : parentElVar;
15403
15892
  const refsParent = !!outerLoopParam && (inner.arrayFreeIdentifiers?.has(outerLoopParam) ?? false);
15404
15893
  const useReactive = refsParent && !!inner.template;
15405
- const emit = useReactive ? buildReactiveEmit(inner, level, wrapOuter) : buildStaticEmit(inner, level);
15894
+ const emit = useReactive ? buildReactiveEmit(inner, level, wrapOuter, uidSuffix) : buildStaticEmit(inner, level, uidSuffix);
15406
15895
  const arrayExpr = useReactive ? wrapOuter(inner.array) : inner.array;
15407
15896
  const childLevelsPlan = childLevels.length > 0 ? buildInnerLoopsPlan({
15408
15897
  levels: childLevels,
@@ -15428,7 +15917,7 @@ function buildInnerLoopsPlan(args) {
15428
15917
  }
15429
15918
  return plan;
15430
15919
  }
15431
- function buildReactiveEmit(inner, level, wrapOuter) {
15920
+ function buildReactiveEmit(inner, level, wrapOuter, uidSuffix) {
15432
15921
  const wrapInner = (expr) => wrapLoopParamAsAccessor(expr, inner.param, inner.paramBindings);
15433
15922
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(inner.param, inner.paramBindings);
15434
15923
  const wrappedKey = inner.key ? wrapLoopParamAsAccessor(inner.key, inner.param, inner.paramBindings) : null;
@@ -15475,6 +15964,9 @@ function buildReactiveEmit(inner, level, wrapOuter) {
15475
15964
  };
15476
15965
  });
15477
15966
  const preludeStatements = [];
15967
+ const indexAlias = nestedLoopIndexAlias(inner, `__innerIdx${uidSuffix}`, paramHead, level.comps, level.events);
15968
+ if (indexAlias)
15969
+ preludeStatements.push(indexAlias);
15478
15970
  if (paramUnwrap)
15479
15971
  preludeStatements.push(paramUnwrap);
15480
15972
  if (inner.mapPreamble)
@@ -15495,8 +15987,13 @@ function buildReactiveEmit(inner, level, wrapOuter) {
15495
15987
  childRefs
15496
15988
  };
15497
15989
  }
15498
- function buildStaticEmit(inner, level) {
15499
- const preludeStatements = inner.mapPreamble ? [inner.mapPreamble] : [];
15990
+ function buildStaticEmit(inner, level, uidSuffix) {
15991
+ const preludeStatements = [];
15992
+ const indexAlias = nestedLoopIndexAlias(inner, `__innerIdx${uidSuffix}`, inner.param, level.comps, level.events);
15993
+ if (indexAlias)
15994
+ preludeStatements.push(indexAlias);
15995
+ if (inner.mapPreamble)
15996
+ preludeStatements.push(inner.mapPreamble);
15500
15997
  return {
15501
15998
  mode: "static",
15502
15999
  rawKey: inner.key ?? null,
@@ -15999,151 +16496,6 @@ function emitReactiveChildProps(lines, ctx) {
15999
16496
  }
16000
16497
  }
16001
16498
 
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
16499
  // src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts
16148
16500
  function stringifyBranchEventBindings(lines, plan, indent) {
16149
16501
  for (const slot of plan) {
@@ -16165,6 +16517,9 @@ function stringifyBranchInnerLoops(lines, plan, indent, pc) {
16165
16517
  const uid = inner.uidSuffix;
16166
16518
  lines.push(`${indent}{ const __bic${uid} = ${inner.containerExpr}`);
16167
16519
  lines.push(`${indent}if (__bic${uid}) mapArray(() => ${inner.arrayExpr} || [], __bic${uid}, ${inner.keyFn}, (${inner.paramHead}, __bidx${uid}, __existing) => {`);
16520
+ if (inner.indexAlias) {
16521
+ lines.push(`${indent} ${inner.indexAlias}`);
16522
+ }
16168
16523
  if (inner.paramUnwrap) {
16169
16524
  lines.push(`${indent} ${inner.paramUnwrap}`);
16170
16525
  }
@@ -16229,13 +16584,15 @@ function stringifyLoopChildArm(lines, arm, armIndent, pc) {
16229
16584
 
16230
16585
  // src/ir-to-client-js/control-flow/stringify/reactive-effects.ts
16231
16586
  function stringifyReactiveEffects(lines, plan, opts) {
16232
- const { indent, elVar, bodyIsMultiRoot } = opts;
16587
+ const { indent, elVar, bodyIsMultiRoot, elementIndexBySlot, textIndexBySlot } = opts;
16233
16588
  const lookup = bodyIsMultiRoot ? "qsaItem" : "qsa";
16234
16589
  const pc = plan.profileComponentName;
16235
16590
  const bindingBfId = (slotId) => profileBindingId(pc, slotId);
16236
16591
  for (const slot of plan.attrSlots) {
16237
16592
  const varName = `__ra_${varSlotId(slot.slotId)}`;
16238
- lines.push(`${indent}{ const ${varName} = ${lookup}(${elVar}, '[bf="${slot.slotId}"]')`);
16593
+ const pIdx = elementIndexBySlot?.get(slot.slotId);
16594
+ const lookupExpr = pIdx !== undefined ? `__p ? __p[${pIdx}] : ${lookup}(${elVar}, '[bf="${slot.slotId}"]')` : `${lookup}(${elVar}, '[bf="${slot.slotId}"]')`;
16595
+ lines.push(`${indent}{ const ${varName} = ${lookupExpr}`);
16239
16596
  lines.push(`${indent}if (${varName}) {`);
16240
16597
  for (const attr of slot.attrs) {
16241
16598
  lines.push(`${indent} createEffect(() => {`);
@@ -16247,15 +16604,19 @@ function stringifyReactiveEffects(lines, plan, opts) {
16247
16604
  lines.push(`${indent}} }`);
16248
16605
  }
16249
16606
  for (const text of plan.outerTexts) {
16250
- emitOuterText(lines, indent, elVar, text, bindingBfId(text.slotId));
16607
+ emitOuterText(lines, indent, elVar, text, bindingBfId(text.slotId), textIndexBySlot?.get(text.slotId));
16251
16608
  }
16252
16609
  for (const cond of plan.conditionals) {
16253
16610
  emitOuterConditional(lines, indent, elVar, cond, pc);
16254
16611
  }
16255
16612
  }
16256
- function emitOuterText(lines, indent, elVar, text, bfId = "") {
16613
+ function emitOuterText(lines, indent, elVar, text, bfId = "", pIdx) {
16257
16614
  const varName = `__rt_${varSlotId(text.slotId)}`;
16258
- lines.push(`${indent}{ const [${varName}] = $t(${elVar}, '${text.slotId}')`);
16615
+ if (pIdx !== undefined) {
16616
+ lines.push(`${indent}{ const ${varName} = __p ? tAfter(__p[${pIdx}]) : $t(${elVar}, '${text.slotId}')[0]`);
16617
+ } else {
16618
+ lines.push(`${indent}{ const [${varName}] = $t(${elVar}, '${text.slotId}')`);
16619
+ }
16259
16620
  lines.push(`${indent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }${bfId}) }`);
16260
16621
  }
16261
16622
  function emitOuterConditional(lines, indent, elVar, cond, pc) {
@@ -16287,6 +16648,41 @@ function emitArmText(lines, indent, text, pc) {
16287
16648
  lines.push(`${indent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }${profileBindingId(pc, text.slotId)}) }`);
16288
16649
  }
16289
16650
 
16651
+ // src/ir-to-client-js/control-flow/stringify/skeleton-paths.ts
16652
+ function pathExpr(base, path) {
16653
+ let expr = base;
16654
+ for (const idx of path) {
16655
+ expr += ".firstChild";
16656
+ if (idx > 0)
16657
+ expr += ".nextSibling".repeat(idx);
16658
+ }
16659
+ return expr;
16660
+ }
16661
+ function buildSkeletonPathPlan(skeletonPaths, elVar, opts) {
16662
+ const arrayElems = [];
16663
+ const elementIndexBySlot = new Map;
16664
+ const textIndexBySlot = new Map;
16665
+ for (const slotId of opts.elementSlotIds) {
16666
+ if (elementIndexBySlot.has(slotId))
16667
+ continue;
16668
+ const path = skeletonPaths.elementPaths.get(slotId);
16669
+ if (!path)
16670
+ continue;
16671
+ elementIndexBySlot.set(slotId, arrayElems.length);
16672
+ arrayElems.push(pathExpr(elVar, path));
16673
+ }
16674
+ for (const slotId of opts.textSlotIds) {
16675
+ if (textIndexBySlot.has(slotId))
16676
+ continue;
16677
+ const path = skeletonPaths.textMarkerPaths.get(slotId);
16678
+ if (!path)
16679
+ continue;
16680
+ textIndexBySlot.set(slotId, arrayElems.length);
16681
+ arrayElems.push(pathExpr(elVar, path));
16682
+ }
16683
+ return { arrayElems, elementIndexBySlot, textIndexBySlot };
16684
+ }
16685
+
16290
16686
  // src/ir-to-client-js/control-flow/stringify/component-loop.ts
16291
16687
  function stringifyComponentLoop(lines, plan) {
16292
16688
  const {
@@ -16346,11 +16742,13 @@ function emitNestedInit(lines, indent, parentVar, nc) {
16346
16742
  function emitLoopChildRefs(lines, refs, opts) {
16347
16743
  if (refs.length === 0)
16348
16744
  return;
16349
- const { indent, elVar, bodyIsMultiRoot } = opts;
16745
+ const { indent, elVar, bodyIsMultiRoot, elementIndexBySlot } = opts;
16350
16746
  const lookup = bodyIsMultiRoot ? "qsaItem" : "qsa";
16351
16747
  for (const ref of refs) {
16352
16748
  const varName = `__rf_${varSlotId(ref.childSlotId)}`;
16353
- lines.push(`${indent}{ const ${varName} = ${lookup}(${elVar}, '[bf="${ref.childSlotId}"]')`);
16749
+ const pIdx = elementIndexBySlot?.get(ref.childSlotId);
16750
+ const lookupExpr = pIdx !== undefined ? `__p ? __p[${pIdx}] : ${lookup}(${elVar}, '[bf="${ref.childSlotId}"]')` : `${lookup}(${elVar}, '[bf="${ref.childSlotId}"]')`;
16751
+ lines.push(`${indent}{ const ${varName} = ${lookupExpr}`);
16354
16752
  lines.push(`${indent}if (${varName}) ${emitRefCall(ref.callback, varName)} }`);
16355
16753
  }
16356
16754
  }
@@ -16430,10 +16828,31 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
16430
16828
  singleRootLayout: "inline"
16431
16829
  });
16432
16830
  }
16831
+ let pathPlan = null;
16832
+ if (hoistedTpl && plan.skeletonPaths) {
16833
+ const elementSlotIds = [
16834
+ ...reactiveEffects?.attrSlots.map((s) => s.slotId) ?? [],
16835
+ ...childRefs.map((r) => r.childSlotId)
16836
+ ];
16837
+ const textSlotIds = reactiveEffects?.outerTexts.map((t) => t.slotId) ?? [];
16838
+ if (elementSlotIds.length > 0 || textSlotIds.length > 0) {
16839
+ const built = buildSkeletonPathPlan(plan.skeletonPaths, "__el", { elementSlotIds, textSlotIds });
16840
+ if (built.arrayElems.length > 0) {
16841
+ pathPlan = built;
16842
+ lines.push(`${bodyIndent}const __p = __existing ? null : [${built.arrayElems.join(", ")}]`);
16843
+ }
16844
+ }
16845
+ }
16433
16846
  if (reactiveEffects !== null) {
16434
- stringifyReactiveEffects(lines, reactiveEffects, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot });
16847
+ stringifyReactiveEffects(lines, reactiveEffects, {
16848
+ indent: bodyIndent,
16849
+ elVar: "__el",
16850
+ bodyIsMultiRoot,
16851
+ elementIndexBySlot: pathPlan?.elementIndexBySlot,
16852
+ textIndexBySlot: pathPlan?.textIndexBySlot
16853
+ });
16435
16854
  }
16436
- emitLoopChildRefs(lines, childRefs, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot });
16855
+ emitLoopChildRefs(lines, childRefs, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot, elementIndexBySlot: pathPlan?.elementIndexBySlot });
16437
16856
  lines.push(`${bodyIndent}return __el`);
16438
16857
  lines.push(`${topIndent}}, '${markerId}'${loopBfId})`);
16439
16858
  }
@@ -16500,12 +16919,14 @@ function stringifyStaticLoop(lines, plan) {
16500
16919
  lines.push(` let __iterEl = ${containerVar}.children[${childIndexExpr}]`);
16501
16920
  if (csrMaterialize) {
16502
16921
  lines.push(` if (!__iterEl) {`);
16922
+ const isSvg = csrMaterialize.bodyIsMultiRoot ? multiRootTemplateNeedsSvgWrap(csrMaterialize.itemTemplate) : templateRootIsSvg(csrMaterialize.itemTemplate);
16923
+ const itemHtml = isSvg ? `<svg>${csrMaterialize.itemTemplate}</svg>` : csrMaterialize.itemTemplate;
16503
16924
  if (csrMaterialize.bodyIsMultiRoot) {
16504
16925
  lines.push(` const __mtpl = document.createElement('template')`);
16505
- lines.push(` __mtpl.innerHTML = \`${csrMaterialize.itemTemplate}\``);
16926
+ lines.push(` __mtpl.innerHTML = \`${itemHtml}\``);
16506
16927
  lines.push(` const __anchor = ${containerVar}.children[${childIndexExpr}] ?? null`);
16507
16928
  lines.push(` let __first = null`);
16508
- lines.push(` let __sib = __mtpl.content.firstElementChild`);
16929
+ lines.push(` let __sib = __mtpl.content${isSvg ? ".firstElementChild" : ""}.firstElementChild`);
16509
16930
  lines.push(` while (__sib) {`);
16510
16931
  lines.push(` const __next = __sib.nextElementSibling`);
16511
16932
  lines.push(` const __cloned = __sib.cloneNode(true)`);
@@ -16516,8 +16937,8 @@ function stringifyStaticLoop(lines, plan) {
16516
16937
  lines.push(` __iterEl = __first`);
16517
16938
  } else {
16518
16939
  lines.push(` const __tpl = document.createElement('template')`);
16519
- lines.push(` __tpl.innerHTML = \`${csrMaterialize.itemTemplate}\``);
16520
- lines.push(` const __cloned = __tpl.content.firstElementChild`);
16940
+ lines.push(` __tpl.innerHTML = \`${itemHtml}\``);
16941
+ lines.push(` const __cloned = __tpl.content${isSvg ? ".firstElementChild" : ""}.firstElementChild`);
16521
16942
  lines.push(` if (__cloned) {`);
16522
16943
  lines.push(` const __anchor = ${containerVar}.children[${childIndexExpr}] ?? null`);
16523
16944
  lines.push(` ${containerVar}.insertBefore(__cloned, __anchor)`);
@@ -16584,7 +17005,10 @@ function emitReactive(lines, inner, indent, pc) {
16584
17005
  lines.push(`${innerIndent} __innerEl${uid}.__bfExtras = __innerExtras${uid}`);
16585
17006
  lines.push(`${indent} }`);
16586
17007
  } else {
16587
- lines.push(`${indent} let __innerEl${uid} = __existing ?? (() => { const __t = document.createElement('template'); __t.innerHTML = \`${emit.wrappedTemplate}\`; return __t.content.firstElementChild.cloneNode(true) })()`);
17008
+ const isSvg = templateRootIsSvg(emit.wrappedTemplate);
17009
+ const innerHtml = isSvg ? `<svg>${emit.wrappedTemplate}</svg>` : emit.wrappedTemplate;
17010
+ const childPath = isSvg ? ".firstElementChild.firstElementChild" : ".firstElementChild";
17011
+ lines.push(`${indent} let __innerEl${uid} = __existing ?? (() => { const __t = document.createElement('template'); __t.innerHTML = \`${innerHtml}\`; return __t.content${childPath}.cloneNode(true) })()`);
16588
17012
  }
16589
17013
  if (emit.wrappedKey) {
16590
17014
  lines.push(`${indent} __innerEl${uid}.setAttribute('${keyAttrName(inner.keyDepth)}', String(${emit.wrappedKey}))`);
@@ -17143,6 +17567,7 @@ function buildPlainLoopPlan(elem, profileComponentName) {
17143
17567
  mapPreambleWrapped: elem.mapPreamble ? wrap(elem.mapPreamble) : "",
17144
17568
  template: elem.template,
17145
17569
  skeletonTemplate: elem.skeletonTemplate,
17570
+ skeletonPaths: elem.skeletonPaths,
17146
17571
  reactiveEffects: hasReactive2 ? buildLoopReactiveEffectsPlan(elem, profileComponentName) : null,
17147
17572
  childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings),
17148
17573
  bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false,
@@ -20116,6 +20541,8 @@ function isStringTypedOperand(expr, isStringName) {
20116
20541
  if (expr.kind === "member" && expr.object.kind === "identifier" && expr.object.name === "props") {
20117
20542
  return isStringName(expr.property);
20118
20543
  }
20544
+ if (expr.kind === "identifier")
20545
+ return isStringName(expr.name);
20119
20546
  if (expr.kind === "binary" && expr.op === "+") {
20120
20547
  return isStringTypedOperand(expr.left, isStringName) || isStringTypedOperand(expr.right, isStringName);
20121
20548
  }
@@ -20175,6 +20602,221 @@ function emitParsedExpr(expr, emitter) {
20175
20602
  }
20176
20603
  }
20177
20604
  }
20605
+ // src/adapters/loop-bound-names.ts
20606
+ function collectLoopBoundNames(ir) {
20607
+ const names = new Set;
20608
+ const visit3 = (node) => {
20609
+ if (!node)
20610
+ return;
20611
+ switch (node.type) {
20612
+ case "element":
20613
+ case "component":
20614
+ case "fragment":
20615
+ case "provider":
20616
+ for (const child of node.children)
20617
+ visit3(child);
20618
+ break;
20619
+ case "async":
20620
+ visit3(node.fallback);
20621
+ for (const child of node.children)
20622
+ visit3(child);
20623
+ break;
20624
+ case "loop":
20625
+ names.add(node.param);
20626
+ if (node.index)
20627
+ names.add(node.index);
20628
+ for (const binding of node.paramBindings ?? [])
20629
+ names.add(binding.name);
20630
+ if (node.filterPredicate)
20631
+ names.add(node.filterPredicate.param);
20632
+ for (const child of node.children)
20633
+ visit3(child);
20634
+ if (node.childComponent) {
20635
+ for (const child of node.childComponent.children)
20636
+ visit3(child);
20637
+ }
20638
+ for (const nested of node.nestedComponents ?? []) {
20639
+ for (const child of nested.children)
20640
+ visit3(child);
20641
+ }
20642
+ for (const frag of node.flatMapCallback?.fragments ?? []) {
20643
+ visit3(frag.ir);
20644
+ }
20645
+ break;
20646
+ case "conditional":
20647
+ visit3(node.whenTrue);
20648
+ visit3(node.whenFalse);
20649
+ break;
20650
+ case "if-statement":
20651
+ visit3(node.consequent);
20652
+ if (node.alternate)
20653
+ visit3(node.alternate);
20654
+ break;
20655
+ case "text":
20656
+ case "expression":
20657
+ case "slot":
20658
+ break;
20659
+ }
20660
+ };
20661
+ visit3(ir.root);
20662
+ return names;
20663
+ }
20664
+ // src/signal-init-eval.ts
20665
+ var BLOCKED_GLOBALS = [
20666
+ "globalThis",
20667
+ "window",
20668
+ "document",
20669
+ "Date",
20670
+ "Math",
20671
+ "crypto",
20672
+ "performance",
20673
+ "fetch",
20674
+ "setTimeout",
20675
+ "setInterval",
20676
+ "require",
20677
+ "process",
20678
+ "Function"
20679
+ ];
20680
+ function isTransportable(value, ancestors = new Set) {
20681
+ if (value === null)
20682
+ return true;
20683
+ const t = typeof value;
20684
+ if (t === "boolean" || t === "number" || t === "string")
20685
+ return true;
20686
+ if (t !== "object")
20687
+ return false;
20688
+ if (ancestors.has(value))
20689
+ return false;
20690
+ ancestors.add(value);
20691
+ try {
20692
+ if (Array.isArray(value)) {
20693
+ if (Object.keys(value).length !== value.length)
20694
+ return false;
20695
+ return value.every((el) => el !== undefined && isTransportable(el, ancestors));
20696
+ }
20697
+ const proto = Object.getPrototypeOf(value);
20698
+ if (proto !== Object.prototype && proto !== null)
20699
+ return false;
20700
+ return Object.values(value).every((v) => isTransportable(v, ancestors));
20701
+ } finally {
20702
+ ancestors.delete(value);
20703
+ }
20704
+ }
20705
+ function tryEvaluateSignalInit(expr, props) {
20706
+ const src = expr.trim();
20707
+ if (src === "")
20708
+ return { ok: false };
20709
+ let fn;
20710
+ try {
20711
+ fn = new Function("props", ...BLOCKED_GLOBALS, `'use strict'; return (
20712
+ ${src}
20713
+ );`);
20714
+ } catch {
20715
+ return { ok: false };
20716
+ }
20717
+ try {
20718
+ const value = fn(props ?? {});
20719
+ if (value === undefined)
20720
+ return { ok: true, value: undefined };
20721
+ return isTransportable(value) ? { ok: true, value } : { ok: false };
20722
+ } catch {
20723
+ return { ok: false };
20724
+ }
20725
+ }
20726
+ function evaluateSignalInit(expr, props) {
20727
+ const result = tryEvaluateSignalInit(expr, props);
20728
+ return result.ok && result.value !== undefined ? result.value : null;
20729
+ }
20730
+ // src/static-literal.ts
20731
+ function evaluateStaticLiteral(expr, bindings) {
20732
+ switch (expr.kind) {
20733
+ case "literal":
20734
+ return { value: expr.value };
20735
+ case "template-literal": {
20736
+ let out = "";
20737
+ for (const part of expr.parts) {
20738
+ if (part.type === "string") {
20739
+ out += part.value;
20740
+ continue;
20741
+ }
20742
+ const resolved = evaluateStaticLiteral(part.expr, bindings);
20743
+ if (!resolved)
20744
+ return null;
20745
+ out += String(resolved.value);
20746
+ }
20747
+ return { value: out };
20748
+ }
20749
+ case "array-literal": {
20750
+ const values = [];
20751
+ for (const element of expr.elements) {
20752
+ const resolved = evaluateStaticLiteral(element, bindings);
20753
+ if (!resolved)
20754
+ return null;
20755
+ values.push(resolved.value);
20756
+ }
20757
+ return { value: values };
20758
+ }
20759
+ case "object-literal": {
20760
+ const out = {};
20761
+ for (const prop of expr.properties) {
20762
+ const resolved = evaluateStaticLiteral(prop.value, bindings);
20763
+ if (!resolved)
20764
+ return null;
20765
+ out[prop.key] = resolved.value;
20766
+ }
20767
+ return { value: out };
20768
+ }
20769
+ case "unary": {
20770
+ const resolved = evaluateStaticLiteral(expr.argument, bindings);
20771
+ if (!resolved)
20772
+ return null;
20773
+ if (expr.op === "-")
20774
+ return typeof resolved.value === "number" ? { value: -resolved.value } : null;
20775
+ if (expr.op === "+")
20776
+ return typeof resolved.value === "number" ? { value: +resolved.value } : null;
20777
+ if (expr.op === "!")
20778
+ return { value: !resolved.value };
20779
+ return null;
20780
+ }
20781
+ case "identifier":
20782
+ return bindings?.has(expr.name) ? { value: bindings.get(expr.name) } : null;
20783
+ case "member": {
20784
+ const base = evaluateStaticLiteral(expr.object, bindings);
20785
+ if (!base || base.value === null || typeof base.value !== "object")
20786
+ return null;
20787
+ return { value: base.value[expr.property] };
20788
+ }
20789
+ case "index-access": {
20790
+ const base = evaluateStaticLiteral(expr.object, bindings);
20791
+ const index = evaluateStaticLiteral(expr.index, bindings);
20792
+ if (!base || !index || !Array.isArray(base.value) || typeof index.value !== "number")
20793
+ return null;
20794
+ return { value: base.value[index.value] };
20795
+ }
20796
+ default:
20797
+ return null;
20798
+ }
20799
+ }
20800
+ function isFullyStaticLiteral(expr) {
20801
+ return evaluateStaticLiteral(expr) !== null;
20802
+ }
20803
+ function resolveStaticLoopSource(arrayParsed, localConstants, opts) {
20804
+ if (!arrayParsed)
20805
+ return null;
20806
+ let target = arrayParsed;
20807
+ if (arrayParsed.kind === "identifier") {
20808
+ if (opts?.isNameShadowed?.(arrayParsed.name))
20809
+ return null;
20810
+ const local = localConstants?.find((c) => c.name === arrayParsed.name);
20811
+ if (!local || local.isModule || !local.parsed)
20812
+ return null;
20813
+ target = local.parsed;
20814
+ }
20815
+ const resolved = evaluateStaticLiteral(target);
20816
+ if (!resolved || !Array.isArray(resolved.value))
20817
+ return null;
20818
+ return resolved.value;
20819
+ }
20178
20820
  // src/query-href-lowering.ts
20179
20821
  function matchQueryHrefCall(callee, args, localNames) {
20180
20822
  if (callee.kind !== "identifier" || !localNames.has(callee.name))
@@ -20310,6 +20952,71 @@ function emitAttrValue(value, emitter, name) {
20310
20952
  }
20311
20953
  }
20312
20954
  }
20955
+ // src/adapters/dangerous-inner-html.ts
20956
+ var DANGEROUS_INNER_HTML_ATTR = "dangerouslySetInnerHTML";
20957
+ function isDangerousInnerHtmlAttr(attr) {
20958
+ return attr.name === DANGEROUS_INNER_HTML_ATTR;
20959
+ }
20960
+ function resolveDangerousInnerHtml(element) {
20961
+ const attr = element.attrs.find(isDangerousInnerHtmlAttr);
20962
+ if (!attr)
20963
+ return null;
20964
+ if (attr.clientOnly)
20965
+ return null;
20966
+ if (attr.value.kind !== "expression") {
20967
+ return { kind: "dynamic", expr: "", loc: attr.loc };
20968
+ }
20969
+ const parsed = attr.value.parsed ?? parseExpression(attr.value.expr.trim());
20970
+ const html = staticHtmlLiteral(parsed);
20971
+ if (html !== null)
20972
+ return { kind: "static", html };
20973
+ return { kind: "dynamic", expr: attr.value.expr, loc: attr.loc };
20974
+ }
20975
+ function staticHtmlLiteral(parsed) {
20976
+ if (parsed.kind !== "object-literal")
20977
+ return null;
20978
+ if (parsed.properties.length !== 1)
20979
+ return null;
20980
+ const [prop] = parsed.properties;
20981
+ if (prop.shorthand)
20982
+ return null;
20983
+ if (prop.key !== "__html")
20984
+ return null;
20985
+ if (prop.value.kind !== "literal" || prop.value.literalType !== "string")
20986
+ return null;
20987
+ return prop.value.value;
20988
+ }
20989
+ var TEMPLATE_METACHAR_PATTERNS = {
20990
+ blade: /\{\{|\{!!|<\?|@\w|<\/?\s*x[-:]/,
20991
+ erb: /<%/,
20992
+ "go-template": /\{\{/,
20993
+ jinja: /\{\{|\{%|\{#/,
20994
+ minijinja: /\{\{|\{%|\{#/,
20995
+ mojolicious: /<%|^\s*%/m,
20996
+ twig: /\{\{|\{%|\{#/,
20997
+ xslate: /<:|^\s*:/m
20998
+ };
20999
+ function dangerousInnerHtmlMetacharViolation(html, adapterId) {
21000
+ const pattern = TEMPLATE_METACHAR_PATTERNS[adapterId];
21001
+ if (!pattern) {
21002
+ return `no template-metacharacter guard is defined for adapter '${adapterId}' — refusing rather than splicing unguarded`;
21003
+ }
21004
+ if (!pattern.test(html))
21005
+ return null;
21006
+ return `the literal HTML contains a sequence ${adapterId}'s own template compiler would interpret (not inert text once spliced into the template)`;
21007
+ }
21008
+ function dangerousInnerHtmlDiagnostic(expr, loc, reason) {
21009
+ const detail = reason ? ` — ${reason}.` : "";
21010
+ return {
21011
+ code: "BF101",
21012
+ severity: "error",
21013
+ message: `dangerouslySetInnerHTML requires a compile-time string literal __html value on template adapters (e.g. { __html: '...' })${expr ? `: ${expr.trim()}` : ""}${detail}`,
21014
+ loc,
21015
+ suggestion: {
21016
+ 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."
21017
+ }
21018
+ };
21019
+ }
20313
21020
  // src/combine-client-js.ts
20314
21021
  import ts19 from "typescript";
20315
21022
  var CHILD_PLACEHOLDER_RE = /import '\/\* @bf-child:(\w+) \*\/'/g;
@@ -23151,6 +23858,7 @@ function profileToJSON(profile) {
23151
23858
  registerBuiltinLoweringPlugins();
23152
23859
  export {
23153
23860
  tsNodeToParsedExpr,
23861
+ tryEvaluateSignalInit,
23154
23862
  traceUpdatePath,
23155
23863
  testAdapter,
23156
23864
  stringifyParsedExpr,
@@ -23158,7 +23866,9 @@ export {
23158
23866
  serializeParsedExpr,
23159
23867
  searchParamsLocalNames,
23160
23868
  rewriteImportsForTemplate,
23869
+ resolveStaticLoopSource,
23161
23870
  resolveSetters,
23871
+ resolveDangerousInnerHtml,
23162
23872
  resetCompilerCounters,
23163
23873
  renderImportMapHtml,
23164
23874
  registerLoweringPlugin,
@@ -23193,6 +23903,8 @@ export {
23193
23903
  isStringConcatBinary,
23194
23904
  isLowerableObjectRestDestructure,
23195
23905
  isLowerableLoopDestructure,
23906
+ isFullyStaticLiteral,
23907
+ isDangerousInnerHtmlAttr,
23196
23908
  isBooleanAttr,
23197
23909
  importsSearchParams,
23198
23910
  identifierPath,
@@ -23233,6 +23945,8 @@ export {
23233
23945
  extractFunctionParams,
23234
23946
  extractArrowBodyExpression,
23235
23947
  exprToString,
23948
+ evaluateStaticLiteral,
23949
+ evaluateSignalInit,
23236
23950
  evaluateProfileGates,
23237
23951
  evalStringArrayJoin,
23238
23952
  envSignalReaderFor,
@@ -23245,6 +23959,8 @@ export {
23245
23959
  diffStaticBudget,
23246
23960
  diffProfiles,
23247
23961
  describeFallback,
23962
+ dangerousInnerHtmlMetacharViolation,
23963
+ dangerousInnerHtmlDiagnostic,
23248
23964
  createProgramForFile,
23249
23965
  createProgramForCorpus,
23250
23966
  createError,
@@ -23253,6 +23969,7 @@ export {
23253
23969
  compileJSX,
23254
23970
  combineParentChildClientJs,
23255
23971
  collectModuleStringConsts,
23972
+ collectLoopBoundNames,
23256
23973
  collectContextConsumers,
23257
23974
  buildWhyUpdate,
23258
23975
  buildStaticBudget,
@@ -23283,6 +24000,7 @@ export {
23283
24000
  SourceMapGenerator,
23284
24001
  REACTIVE_PRIMITIVES,
23285
24002
  PROFILE_SCHEMA_VERSION,
24003
+ PARSED_EXPR_KINDS,
23286
24004
  JsxAdapter,
23287
24005
  ErrorCodes,
23288
24006
  ENV_SIGNAL_READERS,