@barefootjs/jsx 0.18.4 → 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 (109) 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 +2 -2
  6. package/dist/adapters/parsed-expr-emitter.d.ts.map +1 -1
  7. package/dist/analyzer.d.ts.map +1 -1
  8. package/dist/expression-parser.d.ts +2 -1
  9. package/dist/expression-parser.d.ts.map +1 -1
  10. package/dist/index.d.ts +14 -4
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1108 -307
  13. package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
  14. package/dist/ir-to-client-js/control-flow/plan/build-event-delegation.d.ts.map +1 -1
  15. package/dist/ir-to-client-js/control-flow/plan/build-inner-loop.d.ts.map +1 -1
  16. package/dist/ir-to-client-js/control-flow/plan/build-loop-child-arm.d.ts.map +1 -1
  17. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
  18. package/dist/ir-to-client-js/control-flow/plan/event-delegation.d.ts +10 -0
  19. package/dist/ir-to-client-js/control-flow/plan/event-delegation.d.ts.map +1 -1
  20. package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts +9 -0
  21. package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts.map +1 -1
  22. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +9 -0
  23. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
  24. package/dist/ir-to-client-js/control-flow/shared.d.ts +45 -3
  25. package/dist/ir-to-client-js/control-flow/shared.d.ts.map +1 -1
  26. package/dist/ir-to-client-js/control-flow/stringify/event-delegation.d.ts.map +1 -1
  27. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts.map +1 -1
  28. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts +1 -0
  29. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
  30. package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts +10 -0
  31. package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts.map +1 -1
  32. package/dist/ir-to-client-js/control-flow/stringify/skeleton-paths.d.ts +38 -0
  33. package/dist/ir-to-client-js/control-flow/stringify/skeleton-paths.d.ts.map +1 -0
  34. package/dist/ir-to-client-js/control-flow/stringify/template-parse.d.ts +19 -0
  35. package/dist/ir-to-client-js/control-flow/stringify/template-parse.d.ts.map +1 -1
  36. package/dist/ir-to-client-js/csr-substitute.d.ts +24 -0
  37. package/dist/ir-to-client-js/csr-substitute.d.ts.map +1 -1
  38. package/dist/ir-to-client-js/html-template.d.ts +42 -0
  39. package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
  40. package/dist/ir-to-client-js/imports.d.ts +2 -2
  41. package/dist/ir-to-client-js/imports.d.ts.map +1 -1
  42. package/dist/ir-to-client-js/plan/static-array-child-init.d.ts +20 -1
  43. package/dist/ir-to-client-js/plan/static-array-child-init.d.ts.map +1 -1
  44. package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
  45. package/dist/ir-to-client-js/stringify/static-array-child-init.d.ts +1 -1
  46. package/dist/ir-to-client-js/types.d.ts +30 -0
  47. package/dist/ir-to-client-js/types.d.ts.map +1 -1
  48. package/dist/ir-to-client-js/utils.d.ts +25 -0
  49. package/dist/ir-to-client-js/utils.d.ts.map +1 -1
  50. package/dist/jsx-to-ir.d.ts.map +1 -1
  51. package/dist/signal-init-eval.d.ts +82 -0
  52. package/dist/signal-init-eval.d.ts.map +1 -0
  53. package/dist/static-literal.d.ts +45 -0
  54. package/dist/static-literal.d.ts.map +1 -0
  55. package/dist/types.d.ts +66 -0
  56. package/dist/types.d.ts.map +1 -1
  57. package/package.json +2 -2
  58. package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +19 -13
  59. package/src/__tests__/create-selector.test.ts +110 -0
  60. package/src/__tests__/csr-template-loop-shadowing.test.ts +214 -0
  61. package/src/__tests__/dangerous-inner-html-resolver.test.ts +197 -0
  62. package/src/__tests__/event-delegation-index-param.test.ts +130 -0
  63. package/src/__tests__/expression-parser.test.ts +38 -0
  64. package/src/__tests__/inner-loop-svg-namespace.test.ts +245 -0
  65. package/src/__tests__/ir-walker.test.ts +1 -0
  66. package/src/__tests__/loop-bound-names.test.ts +102 -0
  67. package/src/__tests__/loop-plan-classification.test.ts +2 -0
  68. package/src/__tests__/map-function-reference.test.ts +303 -0
  69. package/src/__tests__/materialize-getter-calls.test.ts +1 -0
  70. package/src/__tests__/nested-loop-index-param.test.ts +285 -0
  71. package/src/__tests__/signal-init-eval.test.ts +138 -0
  72. package/src/__tests__/static-array-inner-loop-index-param.test.ts +180 -0
  73. package/src/__tests__/static-literal.test.ts +126 -0
  74. package/src/__tests__/string-concat-identifier.test.ts +55 -0
  75. package/src/adapters/dangerous-inner-html.ts +186 -0
  76. package/src/adapters/loop-bound-names.ts +80 -0
  77. package/src/adapters/parsed-expr-emitter.ts +19 -1
  78. package/src/analyzer.ts +6 -0
  79. package/src/expression-parser.ts +59 -25
  80. package/src/index.ts +19 -3
  81. package/src/ir-to-client-js/collect-elements.ts +18 -3
  82. package/src/ir-to-client-js/control-flow/plan/build-event-delegation.ts +6 -0
  83. package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +21 -4
  84. package/src/ir-to-client-js/control-flow/plan/build-loop-child-arm.ts +13 -1
  85. package/src/ir-to-client-js/control-flow/plan/build-loop.ts +1 -0
  86. package/src/ir-to-client-js/control-flow/plan/event-delegation.ts +10 -0
  87. package/src/ir-to-client-js/control-flow/plan/loop-child-arm.ts +9 -0
  88. package/src/ir-to-client-js/control-flow/plan/loop.ts +9 -0
  89. package/src/ir-to-client-js/control-flow/shared.ts +103 -6
  90. package/src/ir-to-client-js/control-flow/stringify/event-delegation.ts +31 -7
  91. package/src/ir-to-client-js/control-flow/stringify/inner-loop.ts +11 -2
  92. package/src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts +5 -0
  93. package/src/ir-to-client-js/control-flow/stringify/loop.ts +53 -10
  94. package/src/ir-to-client-js/control-flow/stringify/reactive-effects.ts +25 -4
  95. package/src/ir-to-client-js/control-flow/stringify/skeleton-paths.ts +70 -0
  96. package/src/ir-to-client-js/control-flow/stringify/template-parse.ts +25 -1
  97. package/src/ir-to-client-js/csr-substitute.ts +54 -1
  98. package/src/ir-to-client-js/html-template.ts +292 -13
  99. package/src/ir-to-client-js/imports.ts +1 -0
  100. package/src/ir-to-client-js/plan/build-static-array-child-init.ts +12 -1
  101. package/src/ir-to-client-js/plan/static-array-child-init.ts +20 -1
  102. package/src/ir-to-client-js/reactivity.ts +6 -0
  103. package/src/ir-to-client-js/stringify/static-array-child-init.ts +11 -6
  104. package/src/ir-to-client-js/types.ts +31 -0
  105. package/src/ir-to-client-js/utils.ts +34 -1
  106. package/src/jsx-to-ir.ts +322 -48
  107. package/src/signal-init-eval.ts +165 -0
  108. package/src/static-literal.ts +128 -0
  109. package/src/types.ts +68 -0
package/dist/index.js CHANGED
@@ -206,7 +206,6 @@ var UNSUPPORTED_METHODS = new Set([
206
206
  "some",
207
207
  "forEach",
208
208
  "flatMap",
209
- "replaceAll",
210
209
  "charAt",
211
210
  "charCodeAt",
212
211
  "codePointAt",
@@ -481,6 +480,9 @@ function convertNode(node, raw) {
481
480
  if (callee.property === "trim") {
482
481
  return { kind: "array-method", method: "trim", object: callee.object, args };
483
482
  }
483
+ if (callee.property === "trimStart" || callee.property === "trimEnd") {
484
+ return { kind: "array-method", method: callee.property, object: callee.object, args };
485
+ }
484
486
  if (callee.property === "toFixed") {
485
487
  return { kind: "array-method", method: "toFixed", object: callee.object, args };
486
488
  }
@@ -506,24 +508,25 @@ function convertNode(node, raw) {
506
508
  }
507
509
  return { kind: "array-method", method: callee.property, object: callee.object, args };
508
510
  }
509
- if (callee.property === "replace") {
511
+ if (callee.property === "replace" || callee.property === "replaceAll") {
512
+ const method = callee.property;
510
513
  if (args.length < 2) {
511
514
  return {
512
515
  kind: "unsupported",
513
516
  raw,
514
- reason: `\`.replace(${args.length === 0 ? "" : "pattern"})\` needs both a pattern and a replacement — JS coerces the missing argument to the string "undefined", a degenerate result. Pass both arguments, or pre-compute the value before the template.`
517
+ reason: `\`.${method}(${args.length === 0 ? "" : "pattern"})\` needs both a pattern and a replacement — JS coerces the missing argument to the string "undefined", a degenerate result. Pass both arguments, or pre-compute the value before the template.`
515
518
  };
516
519
  }
517
520
  const patternNode = node.arguments[0];
518
521
  if (patternNode && ts2.isRegularExpressionLiteral(patternNode)) {
519
- return { kind: "array-method", method: "replace", object: callee.object, args };
522
+ return { kind: "array-method", method, object: callee.object, args };
520
523
  }
521
524
  const badArg = args[0].kind === "unsupported" || args[0].kind === "object-literal" ? args[0] : args[1].kind === "unsupported" || args[1].kind === "object-literal" ? args[1] : undefined;
522
525
  if (badArg) {
523
526
  const reason = badArg.kind === "unsupported" ? badArg.reason : "Unsupported syntax: ObjectLiteralExpression";
524
527
  return { kind: "unsupported", raw, reason };
525
528
  }
526
- return { kind: "array-method", method: "replace", object: callee.object, args };
529
+ return { kind: "array-method", method, object: callee.object, args };
527
530
  }
528
531
  if (callee.property === "repeat") {
529
532
  return { kind: "array-method", method: "repeat", object: callee.object, args };
@@ -558,7 +561,7 @@ function convertNode(node, raw) {
558
561
  if (ts2.isPropertyAccessExpression(node)) {
559
562
  const object = convertNode(node.expression, raw);
560
563
  const property = node.name.text;
561
- return { kind: "member", object, property, computed: false };
564
+ return { kind: "member", object, property, computed: false, optional: !!node.questionDotToken };
562
565
  }
563
566
  if (ts2.isElementAccessExpression(node)) {
564
567
  const object = convertNode(node.expression, raw);
@@ -567,10 +570,10 @@ function convertNode(node, raw) {
567
570
  return { kind: "unsupported", raw, reason: "Element access with no index expression" };
568
571
  }
569
572
  if (ts2.isNumericLiteral(argNode)) {
570
- return { kind: "member", object, property: argNode.text, computed: true };
573
+ return { kind: "member", object, property: argNode.text, computed: true, optional: !!node.questionDotToken };
571
574
  }
572
575
  if (ts2.isStringLiteral(argNode)) {
573
- return { kind: "member", object, property: argNode.text, computed: true };
576
+ return { kind: "member", object, property: argNode.text, computed: true, optional: !!node.questionDotToken };
574
577
  }
575
578
  const index = convertNode(argNode, raw);
576
579
  if (index.kind === "unsupported" || index.kind === "object-literal")
@@ -1121,7 +1124,7 @@ function substituteDestructuredFields(expr, fieldMap, syntheticParam, restName)
1121
1124
  return e;
1122
1125
  let node = { kind: "identifier", name: syntheticParam };
1123
1126
  for (const segment of entry.path) {
1124
- node = { kind: "member", object: node, property: segment, computed: false };
1127
+ node = { kind: "member", object: node, property: segment, computed: false, optional: false };
1125
1128
  }
1126
1129
  if (entry.defaultExpr) {
1127
1130
  return { kind: "logical", op: "??", left: node, right: entry.defaultExpr };
@@ -1136,10 +1139,11 @@ function substituteDestructuredFields(expr, fieldMap, syntheticParam, restName)
1136
1139
  kind: "member",
1137
1140
  object: { kind: "identifier", name: syntheticParam },
1138
1141
  property: e.property,
1139
- computed: false
1142
+ computed: false,
1143
+ optional: e.optional
1140
1144
  };
1141
1145
  }
1142
- return { kind: "member", object: walk(e.object), property: e.property, computed: e.computed };
1146
+ return { kind: "member", object: walk(e.object), property: e.property, computed: e.computed, optional: e.optional };
1143
1147
  case "index-access":
1144
1148
  return { kind: "index-access", object: walk(e.object), index: walk(e.index) };
1145
1149
  case "binary":
@@ -1251,10 +1255,10 @@ function checkSupport(expr) {
1251
1255
  return { supported: true, level: "L2" };
1252
1256
  }
1253
1257
  case "array-method": {
1254
- if (expr.method === "replace" && expr.args[0]?.kind === "regex") {
1258
+ if ((expr.method === "replace" || expr.method === "replaceAll") && expr.args[0]?.kind === "regex") {
1255
1259
  return {
1256
1260
  supported: false,
1257
- reason: "String.prototype.replace supports only a string pattern + string replacement (the regex form is deferred); use a string pattern or wrap the expression in /* @client */"
1261
+ reason: `String.prototype.${expr.method} supports only a string pattern + string replacement (the regex form is deferred); use a string pattern or wrap the expression in /* @client */`
1258
1262
  };
1259
1263
  }
1260
1264
  const objSupport = checkSupport(expr.object);
@@ -1628,7 +1632,7 @@ function inlineBinding(expr, name, value) {
1628
1632
  case "call":
1629
1633
  return { kind: "call", callee: walk(e.callee, enclosing), args: e.args.map((a) => walk(a, enclosing)) };
1630
1634
  case "member":
1631
- return { kind: "member", object: walk(e.object, enclosing), property: e.property, computed: e.computed };
1635
+ return { kind: "member", object: walk(e.object, enclosing), property: e.property, computed: e.computed, optional: e.optional };
1632
1636
  case "index-access":
1633
1637
  return { kind: "index-access", object: walk(e.object, enclosing), index: walk(e.index, enclosing) };
1634
1638
  case "binary":
@@ -1879,7 +1883,7 @@ function materializeGetterCalls(expr, names) {
1879
1883
  alternate: rw(expr.alternate)
1880
1884
  };
1881
1885
  case "member":
1882
- return { kind: "member", object: rw(expr.object), property: expr.property, computed: expr.computed };
1886
+ return { kind: "member", object: rw(expr.object), property: expr.property, computed: expr.computed, optional: expr.optional };
1883
1887
  case "index-access":
1884
1888
  return { kind: "index-access", object: rw(expr.object), index: rw(expr.index) };
1885
1889
  case "template-literal":
@@ -2293,13 +2297,23 @@ function attrValueToString(value, opts) {
2293
2297
  return null;
2294
2298
  }
2295
2299
  }
2300
+ function applyObjectIterationWrap(node, arrayExpr) {
2301
+ if (node.objectIteration === "entries")
2302
+ return `Object.entries(${arrayExpr})`;
2303
+ if (node.objectIteration === "keys")
2304
+ return `Object.keys(${arrayExpr})`;
2305
+ if (node.objectIteration === "values")
2306
+ return `Object.values(${arrayExpr})`;
2307
+ return arrayExpr;
2308
+ }
2296
2309
  function buildChainedArrayExpr(elem) {
2297
- return buildLoopChainExpr({
2310
+ const chained = buildLoopChainExpr({
2298
2311
  base: elem.array,
2299
2312
  sortComparator: elem.sortComparator,
2300
2313
  filterPredicate: elem.filterPredicate,
2301
2314
  chainOrder: elem.chainOrder
2302
2315
  });
2316
+ return applyObjectIterationWrap(elem, chained);
2303
2317
  }
2304
2318
  function loopOffsetTerms(offset) {
2305
2319
  if (!offset)
@@ -2928,6 +2942,29 @@ function extractFreeIdentifiersFromText(text) {
2928
2942
  const expr = ts4.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
2929
2943
  return extractFreeIdentifiersFromNode(expr);
2930
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
+ }
2931
2968
  function extractMemoBodyExpr(computation) {
2932
2969
  const arrowMatch = computation.match(/^\(\)\s*=>\s*(.+)$/s);
2933
2970
  if (!arrowMatch)
@@ -2971,7 +3008,7 @@ function normalizeSignalInitial(signal, propsObjectName) {
2971
3008
  }
2972
3009
 
2973
3010
  // src/ir-to-client-js/html-template.ts
2974
- import { BF_PARENT_SCOPE_PLACEHOLDER, BF_SCOPE } from "@barefootjs/shared";
3011
+ import { BF_PARENT_SCOPE_PLACEHOLDER, BF_SCOPE, escapeHtml } from "@barefootjs/shared";
2975
3012
  function createStringProtector() {
2976
3013
  const strings = [];
2977
3014
  const protect = (s) => {
@@ -3073,6 +3110,24 @@ function applyIterationShape(node, arrayExpr, indexParam) {
3073
3110
  callbackParam: `(${node.param})`
3074
3111
  };
3075
3112
  }
3113
+ if (node.objectIteration === "entries") {
3114
+ return {
3115
+ array: `Object.entries(${arrayExpr})`,
3116
+ callbackParam: node.index ? `([${node.index}, ${node.param}])` : `(${node.param}${indexParam})`
3117
+ };
3118
+ }
3119
+ if (node.objectIteration === "keys") {
3120
+ return {
3121
+ array: `Object.keys(${arrayExpr})`,
3122
+ callbackParam: `(${node.param})`
3123
+ };
3124
+ }
3125
+ if (node.objectIteration === "values") {
3126
+ return {
3127
+ array: `Object.values(${arrayExpr})`,
3128
+ callbackParam: `(${node.param})`
3129
+ };
3130
+ }
3076
3131
  return { array: arrayExpr, callbackParam: `(${node.param}${indexParam})` };
3077
3132
  }
3078
3133
  function childrenPropEntry(children, recurse) {
@@ -3132,7 +3187,7 @@ function renderTemplateAttrPart(attr, attrName, wrap, restSpreadNames) {
3132
3187
  case "boolean-attr":
3133
3188
  return attrName;
3134
3189
  case "literal":
3135
- return `${attrName}="${v.value}"`;
3190
+ return `${attrName}="${escapeHtml(v.value)}"`;
3136
3191
  case "expression": {
3137
3192
  const valExpr = wrap(v.expr);
3138
3193
  return templateAttrExpr(attrName, valExpr, v.presenceOrUndefined);
@@ -3263,7 +3318,7 @@ function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, bran
3263
3318
  return `<${node.tag}${attrs ? " " + attrs : ""} />`;
3264
3319
  }
3265
3320
  case "text":
3266
- return node.value;
3321
+ return escapeHtml(node.value);
3267
3322
  case "expression":
3268
3323
  if (node.expr === "null" || node.expr === "undefined")
3269
3324
  return "";
@@ -3371,7 +3426,7 @@ function buildLoopSkeletonTemplate(node, safe) {
3371
3426
  const v = a.value;
3372
3427
  switch (v.kind) {
3373
3428
  case "literal":
3374
- attrParts.push(`${toHtmlAttrName(a.name)}="${v.value}"`);
3429
+ attrParts.push(`${toHtmlAttrName(a.name)}="${escapeHtml(v.value)}"`);
3375
3430
  break;
3376
3431
  case "boolean-attr":
3377
3432
  attrParts.push(toHtmlAttrName(a.name));
@@ -3406,7 +3461,7 @@ function buildLoopSkeletonTemplate(node, safe) {
3406
3461
  return `<${node.tag}${attrs ? " " + attrs : ""} />`;
3407
3462
  }
3408
3463
  case "text":
3409
- return node.value;
3464
+ return escapeHtml(node.value);
3410
3465
  case "expression":
3411
3466
  if (node.expr === "null" || node.expr === "undefined")
3412
3467
  return "";
@@ -3438,6 +3493,133 @@ function buildLoopSkeletonTemplate(node, safe) {
3438
3493
  return assertNever(node);
3439
3494
  }
3440
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
+ }
3441
3623
  function irToPlaceholderTemplate(node, restSpreadNames, loopDepth = 0, loopParams) {
3442
3624
  const recurse = (n) => irToPlaceholderTemplate(n, restSpreadNames, loopDepth, loopParams);
3443
3625
  const wrapExpr = (expr) => wrapExprWithLoopParams(expr, loopParams);
@@ -3458,7 +3640,7 @@ function irToPlaceholderTemplate(node, restSpreadNames, loopDepth = 0, loopParam
3458
3640
  return `<${node.tag}${attrs ? " " + attrs : ""} />`;
3459
3641
  }
3460
3642
  case "text":
3461
- return node.value;
3643
+ return escapeHtml(node.value);
3462
3644
  case "expression":
3463
3645
  if (node.expr === "null" || node.expr === "undefined")
3464
3646
  return "";
@@ -3650,7 +3832,7 @@ function irToComponentTemplateWithOpts(node, opts) {
3650
3832
  return templateAttrExpr(keyName, transformExpr(tmplStr));
3651
3833
  }
3652
3834
  case "literal":
3653
- return `${keyName}="${v.value}"`;
3835
+ return `${keyName}="${escapeHtml(v.value)}"`;
3654
3836
  default:
3655
3837
  return "";
3656
3838
  }
@@ -3660,7 +3842,7 @@ function irToComponentTemplateWithOpts(node, opts) {
3660
3842
  case "boolean-attr":
3661
3843
  return attrName;
3662
3844
  case "literal":
3663
- return `${attrName}="${v.value}"`;
3845
+ return `${attrName}="${escapeHtml(v.value)}"`;
3664
3846
  case "expression":
3665
3847
  return templateAttrExpr(attrName, transformExpr(v.expr, v.templateExpr), v.presenceOrUndefined);
3666
3848
  case "template": {
@@ -3686,7 +3868,7 @@ function irToComponentTemplateWithOpts(node, opts) {
3686
3868
  return `<${node.tag}${attrs ? " " + attrs : ""} />`;
3687
3869
  }
3688
3870
  case "text":
3689
- return node.value;
3871
+ return escapeHtml(node.value);
3690
3872
  case "expression":
3691
3873
  if (node.expr === "null" || node.expr === "undefined")
3692
3874
  return "";
@@ -3978,7 +4160,7 @@ function generateCsrTemplateWithOpts(node, opts) {
3978
4160
  case "boolean-attr":
3979
4161
  return attrName;
3980
4162
  case "literal":
3981
- return `${attrName}="${v.value}"`;
4163
+ return `${attrName}="${escapeHtml(v.value)}"`;
3982
4164
  case "expression":
3983
4165
  return templateAttrExpr(attrName, transformExpr(v.expr, v.templateExpr), v.presenceOrUndefined);
3984
4166
  case "template": {
@@ -4004,7 +4186,7 @@ function generateCsrTemplateWithOpts(node, opts) {
4004
4186
  return `<${node.tag}${attrs ? " " + attrs : ""} />`;
4005
4187
  }
4006
4188
  case "text":
4007
- return node.value;
4189
+ return escapeHtml(node.value);
4008
4190
  case "expression":
4009
4191
  if (node.expr === "null" || node.expr === "undefined")
4010
4192
  return "";
@@ -4080,7 +4262,28 @@ function generateCsrTemplateWithOpts(node, opts) {
4080
4262
  return `\${renderChild('${nameForRegistryRef(node.name)}', ${propsExpr}${keyArg || (slotArg ? ", undefined" : "")}${slotArg})}`;
4081
4263
  }
4082
4264
  case "loop": {
4083
- 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("");
4084
4287
  if (node.bodyIsItemConditional && node.key) {
4085
4288
  childTemplate = `${itemAnchorTemplate(node.key)}${childTemplate}`;
4086
4289
  }
@@ -4094,7 +4297,7 @@ function generateCsrTemplateWithOpts(node, opts) {
4094
4297
  if (node.flatMapCallback) {
4095
4298
  let body = node.flatMapCallback.templateBody ?? node.flatMapCallback.body;
4096
4299
  for (const frag of node.flatMapCallback.fragments) {
4097
- const renderedIr = recurseInLoop(frag.ir);
4300
+ const renderedIr = recurseInLoopBody(frag.ir);
4098
4301
  body = body.replace(frag.placeholder, `\`${renderedIr}\``);
4099
4302
  }
4100
4303
  body = applyPropsRewrite(body, propsObjectName ?? null);
@@ -5054,6 +5257,8 @@ function needsTypeBasedDetection(source) {
5054
5257
  return true;
5055
5258
  if (/\.map\s*\(/.test(source))
5056
5259
  return true;
5260
+ if (source.includes("createSelector"))
5261
+ return true;
5057
5262
  return false;
5058
5263
  }
5059
5264
  function findBrandPackageImportLoc(sourceFile, filePath) {
@@ -6030,6 +6235,7 @@ var CLIENT_EXPORTS = new Set([
6030
6235
  "createEffect",
6031
6236
  "createDisposableEffect",
6032
6237
  "createMemo",
6238
+ "createSelector",
6033
6239
  "createRoot",
6034
6240
  "onCleanup",
6035
6241
  "onMount",
@@ -8034,7 +8240,7 @@ function resolveFreeRefs(node, env) {
8034
8240
  }
8035
8241
 
8036
8242
  // src/jsx-to-ir.ts
8037
- import { toHTMLAttrName } from "@barefootjs/shared";
8243
+ import { toHTMLAttrName, decodeEntities } from "@barefootjs/shared";
8038
8244
  var CLIENT_DIRECTIVE_INTERIOR_RE2 = /^\s*@client\s*$/;
8039
8245
  var BLOCK_COMMENT_RE2 = /\/\*([\s\S]*?)\*\//g;
8040
8246
  function hasLeadingClientDirective(expr, sourceFile) {
@@ -8116,9 +8322,15 @@ function exprHasFunctionCalls(expr) {
8116
8322
  return found;
8117
8323
  }
8118
8324
  function rewriteBarePropRefs2(text, expr, ctx) {
8119
- const propNames = getDestructuredPropNames(ctx);
8325
+ let propNames = getDestructuredPropNames(ctx);
8120
8326
  if (!propNames)
8121
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
+ }
8122
8334
  const extraPropRefs = collectBranchLocalPropRefsViaSubstitution(expr, ctx);
8123
8335
  return rewriteBarePropRefs(text, expr, propNames, extraPropRefs);
8124
8336
  }
@@ -8183,6 +8395,7 @@ function createTransformContext(analyzer) {
8183
8395
  isRoot: true,
8184
8396
  insideComponentChildren: false,
8185
8397
  loopParams: new Set,
8398
+ loopDepth: 0,
8186
8399
  patterns: {
8187
8400
  signals: analyzer.signals.map((s) => ({
8188
8401
  getter: s.getter,
@@ -8283,26 +8496,29 @@ function makeBindingEnv(ctx) {
8283
8496
  function parseValueExpr(trimmed) {
8284
8497
  return parseExpression(trimmed.startsWith("{") ? `(${trimmed})` : trimmed);
8285
8498
  }
8286
- 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);
8287
8503
  if (node.type === "expression") {
8288
8504
  const trimmed = node.expr.trim();
8289
8505
  if (trimmed)
8290
- node.parsed = parseExpression(trimmed);
8506
+ node.parsed = parse2(trimmed);
8291
8507
  } else if (node.type === "conditional" || node.type === "if-statement") {
8292
8508
  const trimmed = node.condition.trim();
8293
8509
  if (trimmed)
8294
- node.parsedCondition = parseExpression(trimmed);
8510
+ node.parsedCondition = parse2(trimmed);
8295
8511
  }
8296
8512
  if (node.type === "element") {
8297
8513
  for (const attr of node.attrs) {
8298
8514
  if (attr.value.kind === "expression") {
8299
8515
  const trimmed = attr.value.expr.trim();
8300
8516
  if (trimmed)
8301
- attr.value.parsed = parseValueExpr(trimmed);
8517
+ attr.value.parsed = parseValue(trimmed);
8302
8518
  } else if (attr.value.kind === "spread") {
8303
8519
  const trimmed = attr.value.expr.trim();
8304
8520
  if (trimmed)
8305
- attr.value.parsed = parseExpression(trimmed);
8521
+ attr.value.parsed = parse2(trimmed);
8306
8522
  }
8307
8523
  }
8308
8524
  } else if (node.type === "component") {
@@ -8310,14 +8526,14 @@ function attachParsedExpressions(node) {
8310
8526
  if (prop.value.kind === "expression") {
8311
8527
  const trimmed = prop.value.expr.trim();
8312
8528
  if (trimmed)
8313
- prop.value.parsed = parseValueExpr(trimmed);
8529
+ prop.value.parsed = parseValue(trimmed);
8314
8530
  }
8315
8531
  }
8316
8532
  } else if (node.type === "provider") {
8317
8533
  if (node.valueProp.value.kind === "expression") {
8318
8534
  const trimmed = node.valueProp.value.expr.trim();
8319
8535
  if (trimmed)
8320
- node.valueProp.value.parsed = parseValueExpr(trimmed);
8536
+ node.valueProp.value.parsed = parseValue(trimmed);
8321
8537
  }
8322
8538
  }
8323
8539
  switch (node.type) {
@@ -8326,47 +8542,51 @@ function attachParsedExpressions(node) {
8326
8542
  case "fragment":
8327
8543
  case "provider":
8328
8544
  for (const child of node.children)
8329
- attachParsedExpressions(child);
8545
+ attachParsedExpressions(child, analyzer, bound);
8330
8546
  break;
8331
8547
  case "async":
8332
- attachParsedExpressions(node.fallback);
8548
+ attachParsedExpressions(node.fallback, analyzer, bound);
8333
8549
  for (const child of node.children)
8334
- attachParsedExpressions(child);
8550
+ attachParsedExpressions(child, analyzer, bound);
8335
8551
  break;
8336
8552
  case "loop": {
8337
8553
  const trimmedArray = node.array.trim();
8338
8554
  if (trimmedArray)
8339
- 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);
8340
8560
  for (const child of node.children)
8341
- attachParsedExpressions(child);
8561
+ attachParsedExpressions(child, analyzer, loopBound);
8342
8562
  if (node.childComponent) {
8343
8563
  for (const child of node.childComponent.children)
8344
- attachParsedExpressions(child);
8564
+ attachParsedExpressions(child, analyzer, loopBound);
8345
8565
  }
8346
8566
  for (const nested of node.nestedComponents ?? []) {
8347
8567
  for (const child of nested.children)
8348
- attachParsedExpressions(child);
8568
+ attachParsedExpressions(child, analyzer, loopBound);
8349
8569
  }
8350
8570
  for (const frag of node.flatMapCallback?.fragments ?? []) {
8351
- attachParsedExpressions(frag.ir);
8571
+ attachParsedExpressions(frag.ir, analyzer, loopBound);
8352
8572
  }
8353
8573
  break;
8354
8574
  }
8355
8575
  case "conditional":
8356
- attachParsedExpressions(node.whenTrue);
8357
- attachParsedExpressions(node.whenFalse);
8576
+ attachParsedExpressions(node.whenTrue, analyzer, bound);
8577
+ attachParsedExpressions(node.whenFalse, analyzer, bound);
8358
8578
  break;
8359
8579
  case "if-statement":
8360
- attachParsedExpressions(node.consequent);
8580
+ attachParsedExpressions(node.consequent, analyzer, bound);
8361
8581
  if (node.alternate)
8362
- attachParsedExpressions(node.alternate);
8582
+ attachParsedExpressions(node.alternate, analyzer, bound);
8363
8583
  break;
8364
8584
  }
8365
8585
  }
8366
8586
  function jsxToIR(analyzer) {
8367
8587
  const root = buildIRRoot(analyzer);
8368
8588
  if (root)
8369
- attachParsedExpressions(root);
8589
+ attachParsedExpressions(root, analyzer);
8370
8590
  return root;
8371
8591
  }
8372
8592
  function buildIRRoot(analyzer) {
@@ -8836,7 +9056,7 @@ function transformText(node, ctx) {
8836
9056
  }
8837
9057
  return {
8838
9058
  type: "text",
8839
- value: text,
9059
+ value: decodeEntities(text),
8840
9060
  loc: getSourceLocation(node, ctx.sourceFile, ctx.filePath)
8841
9061
  };
8842
9062
  }
@@ -9392,6 +9612,22 @@ function isIteratorShapeCall(node) {
9392
9612
  return null;
9393
9613
  return { array: node.expression.expression, shape: name };
9394
9614
  }
9615
+ function isObjectIteratorCall(node) {
9616
+ if (!ts11.isCallExpression(node))
9617
+ return null;
9618
+ if (!ts11.isPropertyAccessExpression(node.expression))
9619
+ return null;
9620
+ if (!ts11.isIdentifier(node.expression.expression))
9621
+ return null;
9622
+ if (node.expression.expression.text !== "Object")
9623
+ return null;
9624
+ if (node.arguments.length !== 1)
9625
+ return null;
9626
+ const name = node.expression.name.text;
9627
+ if (name !== "entries" && name !== "keys" && name !== "values")
9628
+ return null;
9629
+ return { object: node.arguments[0], shape: name };
9630
+ }
9395
9631
  function extractSortComparator(callback, _method, ctx) {
9396
9632
  const outerRaw = ctx.getJS(callback);
9397
9633
  const unsupported = () => ({
@@ -9437,8 +9673,23 @@ function extractSortComparator(callback, _method, ctx) {
9437
9673
  };
9438
9674
  }
9439
9675
  function resolveSortComparatorIdentifier(name, ctx) {
9440
- const constInfo = findLocalConst(name, ctx);
9441
- 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);
9442
9693
  if (constInfo && fnInfo)
9443
9694
  return null;
9444
9695
  if (constInfo) {
@@ -9451,6 +9702,57 @@ function resolveSortComparatorIdentifier(name, ctx) {
9451
9702
  }
9452
9703
  return null;
9453
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
+ }
9454
9756
  function extractFilterPredicate(callback, ctx) {
9455
9757
  if (!ts11.isArrowFunction(callback))
9456
9758
  return { result: null };
@@ -9810,6 +10112,7 @@ function extractItemConditionalKey(cond) {
9810
10112
  }
9811
10113
  function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
9812
10114
  const isNested = ctx.loopParams.size > 0;
10115
+ const depth = ctx.loopDepth;
9813
10116
  const propAccess = node.expression;
9814
10117
  const mapSource = propAccess.expression;
9815
10118
  let array = "";
@@ -9822,6 +10125,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
9822
10125
  let templateMapPreamble;
9823
10126
  let typedMapPreamble;
9824
10127
  let iterationShape;
10128
+ let objectIteration;
9825
10129
  const setArray = (node2) => {
9826
10130
  array = ctx.getJS(node2);
9827
10131
  templateArray = rewriteBarePropRefs2(array, node2, ctx);
@@ -9836,6 +10140,12 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
9836
10140
  } else if (iteratorInfo.shape === "keys") {
9837
10141
  iterationShape = "keys";
9838
10142
  }
10143
+ } else {
10144
+ const objectIteratorInfo = isObjectIteratorCall(mapSource);
10145
+ if (objectIteratorInfo) {
10146
+ chainSource = objectIteratorInfo.object;
10147
+ objectIteration = objectIteratorInfo.shape;
10148
+ }
9839
10149
  }
9840
10150
  const filterInfo = isFilterCall(chainSource);
9841
10151
  const sortInfo = isSortCall(chainSource);
@@ -9910,13 +10220,11 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
9910
10220
  setArray(innerSort.array);
9911
10221
  }
9912
10222
  } else {
9913
- array = ctx.getJS(filterInfo.array);
9914
- arrayExpr = filterInfo.array;
10223
+ setArray(filterInfo.array);
9915
10224
  }
9916
10225
  }
9917
10226
  } else {
9918
- array = ctx.getJS(chainSource);
9919
- arrayExpr = chainSource;
10227
+ setArray(chainSource);
9920
10228
  }
9921
10229
  const callback = node.arguments[0];
9922
10230
  let param = "item";
@@ -9933,7 +10241,8 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
9933
10241
  if (firstParam.type) {
9934
10242
  paramType = firstParam.type.getText(ctx.sourceFile);
9935
10243
  }
9936
- if (iterationShape === "entries" && ts11.isArrayBindingPattern(firstParam.name)) {
10244
+ const isEntriesShape = iterationShape === "entries" || objectIteration === "entries";
10245
+ if (isEntriesShape && ts11.isArrayBindingPattern(firstParam.name)) {
9937
10246
  const elements = firstParam.name.elements.filter((el) => !ts11.isOmittedExpression(el));
9938
10247
  if (elements.length === 2 && ts11.isBindingElement(elements[0]) && ts11.isIdentifier(elements[0].name) && ts11.isBindingElement(elements[1]) && ts11.isIdentifier(elements[1].name)) {
9939
10248
  index = elements[0].name.text;
@@ -9955,7 +10264,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
9955
10264
  }
9956
10265
  }
9957
10266
  }
9958
- if (callback.parameters.length > 1 && iterationShape !== "entries") {
10267
+ if (callback.parameters.length > 1 && iterationShape !== "entries" && objectIteration !== "entries") {
9959
10268
  const secondParam = callback.parameters[1];
9960
10269
  index = secondParam.name.getText(ctx.sourceFile);
9961
10270
  if (secondParam.type) {
@@ -9970,6 +10279,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
9970
10279
  }
9971
10280
  if (index)
9972
10281
  ctx.loopParams.add(index);
10282
+ ctx.loopDepth++;
9973
10283
  const tryTransformRenderableBody = (expr) => {
9974
10284
  if (!ts11.isBinaryExpression(expr))
9975
10285
  return;
@@ -10066,6 +10376,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
10066
10376
  }
10067
10377
  if (index)
10068
10378
  ctx.loopParams.delete(index);
10379
+ ctx.loopDepth--;
10069
10380
  }
10070
10381
  if (children.length === 0 && !flatMapCallback) {
10071
10382
  return null;
@@ -10094,7 +10405,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
10094
10405
  const callsReactive = exprCallsReactiveGetters(arrayExpr, ctx);
10095
10406
  const hasCalls = exprHasFunctionCalls(arrayExpr);
10096
10407
  const isDirectPropArray = method !== "flatMap" && isArrayExprDirectPropRef(arrayExpr, ctx);
10097
- const isStaticArray = !isSignalOrMemoArray(array, ctx) && !isDirectPropArray && !hasCalls;
10408
+ const isStaticArray = !isSignalOrMemoArray(array, ctx) && !isDirectPropArray && !hasCalls && !objectIteration;
10098
10409
  const nestedComponents = collectNestedComponents(children).filter((c) => c.name !== childComponent?.name);
10099
10410
  return {
10100
10411
  type: "loop",
@@ -10121,6 +10432,8 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
10121
10432
  sortComparator,
10122
10433
  chainOrder,
10123
10434
  iterationShape,
10435
+ objectIteration,
10436
+ depth,
10124
10437
  clientOnly: isClientOnly || undefined,
10125
10438
  mapPreamble,
10126
10439
  templateMapPreamble,
@@ -10363,7 +10676,7 @@ function getAttributeValue(attr, ctx) {
10363
10676
  return AttrValueOf.booleanAttr();
10364
10677
  }
10365
10678
  if (ts11.isStringLiteral(attr.initializer)) {
10366
- return AttrValueOf.literal(attr.initializer.text);
10679
+ return AttrValueOf.literal(decodeEntities(attr.initializer.text));
10367
10680
  }
10368
10681
  if (ts11.isJsxExpression(attr.initializer) && attr.initializer.expression) {
10369
10682
  let expr = attr.initializer.expression;
@@ -10461,7 +10774,7 @@ function parseTemplateLiteral(expr, ctx) {
10461
10774
  }
10462
10775
  function tryResolveTemplateSpanFromConst(expr, ctx) {
10463
10776
  if (ts11.isIdentifier(expr)) {
10464
- const constInfo = findLocalConst(expr.text, ctx);
10777
+ const constInfo = findLocalConst(expr.text, ctx.analyzer);
10465
10778
  if (!constInfo)
10466
10779
  return null;
10467
10780
  const ast = parseConstInitializer(constInfo);
@@ -10475,7 +10788,7 @@ function tryResolveTemplateSpanFromConst(expr, ctx) {
10475
10788
  if (ts11.isElementAccessExpression(expr)) {
10476
10789
  if (!ts11.isIdentifier(expr.expression))
10477
10790
  return null;
10478
- const constInfo = findLocalConst(expr.expression.text, ctx);
10791
+ const constInfo = findLocalConst(expr.expression.text, ctx.analyzer);
10479
10792
  if (!constInfo)
10480
10793
  return null;
10481
10794
  const ast = parseConstInitializer(constInfo);
@@ -10501,16 +10814,16 @@ function tryResolveTemplateSpanFromConst(expr, ctx) {
10501
10814
  }
10502
10815
  return null;
10503
10816
  }
10504
- function findLocalConst(name, ctx) {
10505
- 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);
10506
10819
  if (matches.length === 0)
10507
10820
  return;
10508
10821
  const fnScoped = matches.filter((c) => !c.isModule);
10509
10822
  const pool = fnScoped.length > 0 ? fnScoped : matches;
10510
10823
  return pool[pool.length - 1];
10511
10824
  }
10512
- function findLocalFunction(name, ctx) {
10513
- 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);
10514
10827
  if (matches.length === 0)
10515
10828
  return;
10516
10829
  const fnScoped = matches.filter((f) => !f.isModule);
@@ -10552,7 +10865,9 @@ function hasDynamicTagBinding(name, sourceFile) {
10552
10865
  return found;
10553
10866
  }
10554
10867
  function tryResolveIdentifierAsTemplateLiteral(ident, ctx) {
10555
- const constInfo = findLocalConst(ident.text, ctx);
10868
+ if (ctx.loopParams.has(ident.text))
10869
+ return null;
10870
+ const constInfo = findLocalConst(ident.text, ctx.analyzer);
10556
10871
  if (!constInfo)
10557
10872
  return null;
10558
10873
  const ast = parseConstInitializer(constInfo);
@@ -10649,8 +10964,8 @@ function tryDesugarInterleaveTaggedTemplate(expr, ctx) {
10649
10964
  return rewritten ?? expr;
10650
10965
  }
10651
10966
  function resolveInterleaveTagIdentifier(name, ctx) {
10652
- const constInfo = findLocalConst(name, ctx);
10653
- const fnInfo = findLocalFunction(name, ctx);
10967
+ const constInfo = findLocalConst(name, ctx.analyzer);
10968
+ const fnInfo = findLocalFunction(name, ctx.analyzer);
10654
10969
  if (constInfo && fnInfo)
10655
10970
  return null;
10656
10971
  if (constInfo) {
@@ -11404,6 +11719,7 @@ function collectLoopChildEventsWithNesting(node, initialNestingStack = []) {
11404
11719
  depth: scope.nestingStack.length + 1,
11405
11720
  array: l.array,
11406
11721
  param: l.param,
11722
+ index: l.index,
11407
11723
  key: l.key,
11408
11724
  markerId: l.markerId,
11409
11725
  containerSlotId: scope.lastElementSlotId,
@@ -11494,64 +11810,215 @@ function collectLoopChildReactiveAttrs(node, ctx, loopParam, loopParamBindings)
11494
11810
  return attrs;
11495
11811
  }
11496
11812
 
11497
- // src/ir-to-client-js/collect-elements.ts
11498
- var EMPTY_RENDER_EXPRS = new Set(["null", "undefined", "false", "''", '""', "``"]);
11499
- function domElementCount(node) {
11500
- switch (node.type) {
11501
- case "element":
11502
- case "component":
11503
- case "provider":
11504
- case "async":
11505
- return 1;
11506
- case "text":
11507
- return 0;
11508
- case "expression":
11509
- return EMPTY_RENDER_EXPRS.has(node.expr.trim()) ? 0 : null;
11510
- case "loop":
11511
- if (node.bodyIsItemConditional || node.method === "flatMap")
11512
- return null;
11513
- return `(${buildLoopChainExpr({
11514
- base: node.array,
11515
- sortComparator: node.sortComparator,
11516
- filterPredicate: node.filterPredicate,
11517
- chainOrder: node.chainOrder
11518
- })}).length`;
11519
- case "conditional": {
11520
- const t = domElementCount(node.whenTrue);
11521
- const f = domElementCount(node.whenFalse);
11522
- if (t === null || f === null)
11523
- return null;
11524
- if (typeof t === "number" && typeof f === "number" && t === f)
11525
- return t;
11526
- return `(${node.condition} ? ${t} : ${f})`;
11527
- }
11528
- case "fragment":
11529
- return sumElementCounts(node.children);
11530
- default:
11531
- return null;
11532
- }
11533
- }
11534
- function sumElementCounts(nodes) {
11535
- let staticCount = 0;
11536
- const dynamic = [];
11537
- for (const n of nodes) {
11538
- const c = domElementCount(n);
11539
- if (c === null)
11540
- return null;
11541
- if (typeof c === "number")
11542
- staticCount += c;
11543
- else
11544
- dynamic.push(c);
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());
11545
11863
  }
11546
- if (dynamic.length === 0)
11547
- return staticCount;
11548
- const parts = staticCount > 0 ? [String(staticCount), ...dynamic] : dynamic;
11549
- return parts.length === 1 ? parts[0] : `(${parts.join(" + ")})`;
11864
+ const branches = extractConditionalBranchTemplates(stripped);
11865
+ if (branches === null || branches.length === 0)
11866
+ return false;
11867
+ return branches.every(templateRootIsSvg);
11550
11868
  }
11551
- function legacyElementCount(node) {
11552
- 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;
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);
11553
11874
  }
11554
- function computeLoopSiblingOffsets(root) {
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();
11882
+ }
11883
+ return s;
11884
+ }
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);
11896
+ }
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) {
11555
12022
  const offsets = new Map;
11556
12023
  const recordRun = (children, preceding) => {
11557
12024
  for (const child of children) {
@@ -11672,11 +12139,13 @@ function collectInnerLoops(nodes, siblingOffsets, outerLoopParam, ctx, options)
11672
12139
  arrayFreeIdentifiers: n.arrayFreeIdentifiers,
11673
12140
  param: n.param,
11674
12141
  paramBindings: n.paramBindings,
12142
+ index: n.index,
11675
12143
  key: n.key,
11676
12144
  markerId: n.markerId,
11677
12145
  bodyIsMultiRoot: n.bodyIsMultiRoot,
11678
12146
  bodyIsItemConditional: n.bodyIsItemConditional,
11679
12147
  iterationShape: n.iterationShape,
12148
+ objectIteration: n.objectIteration,
11680
12149
  containerSlotId: scope.parentSlotId,
11681
12150
  template,
11682
12151
  mapPreamble: n.mapPreamble,
@@ -11868,6 +12337,7 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
11868
12337
  let template = "";
11869
12338
  let staticItemTemplate;
11870
12339
  let skeletonTemplate;
12340
+ let skeletonPaths;
11871
12341
  if (l.childComponent) {
11872
12342
  template = "";
11873
12343
  if (l.isStaticArray && l.children[0]) {
@@ -11879,10 +12349,14 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
11879
12349
  if (l.isStaticArray) {
11880
12350
  staticItemTemplate = useElementReconciliation ? irToPlaceholderTemplate(l.children[0], buildRestSpreadNames(ctx), 0) : irToHtmlTemplate(l.children[0], buildRestSpreadNames(ctx), 0);
11881
12351
  } else if (!useElementReconciliation && !l.bodyIsMultiRoot && !l.bodyIsItemConditional) {
11882
- skeletonTemplate = buildLoopSkeletonTemplate(l.children[0], {
12352
+ const skeletonSafeSlots = {
11883
12353
  reactiveAttrKeys: new Set(bindings.reactiveAttrs.map((a) => `${a.childSlotId}::${a.attrName}`)),
11884
12354
  reactiveTextSlotIds: new Set(bindings.reactiveTexts.map((t) => t.slotId))
11885
- }) ?? undefined;
12355
+ };
12356
+ skeletonTemplate = buildLoopSkeletonTemplate(l.children[0], skeletonSafeSlots) ?? undefined;
12357
+ if (skeletonTemplate && !templateRootIsSvg(skeletonTemplate)) {
12358
+ skeletonPaths = computeSkeletonSlotPaths(l.children[0], skeletonSafeSlots) ?? undefined;
12359
+ }
11886
12360
  }
11887
12361
  }
11888
12362
  ctx.loopElements.push({
@@ -11898,9 +12372,11 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
11898
12372
  bodyIsMultiRoot: l.bodyIsMultiRoot,
11899
12373
  bodyIsItemConditional: l.bodyIsItemConditional,
11900
12374
  iterationShape: l.iterationShape,
12375
+ objectIteration: l.objectIteration,
11901
12376
  template,
11902
12377
  staticItemTemplate,
11903
12378
  skeletonTemplate,
12379
+ skeletonPaths,
11904
12380
  childEventHandlers: childHandlers,
11905
12381
  bindings,
11906
12382
  childComponent: l.childComponent,
@@ -12097,6 +12573,7 @@ function collectBranchLoops(node, ctx, siblingOffsets) {
12097
12573
  bodyIsMultiRoot: n.bodyIsMultiRoot,
12098
12574
  bodyIsItemConditional: n.bodyIsItemConditional,
12099
12575
  iterationShape: n.iterationShape,
12576
+ objectIteration: n.objectIteration,
12100
12577
  template: childTemplate,
12101
12578
  containerSlotId: containerSlot,
12102
12579
  mapPreamble: n.mapPreamble ?? null,
@@ -12690,6 +13167,7 @@ var RUNTIME_IMPORT_CANDIDATES = [
12690
13167
  "__slot",
12691
13168
  "__bfSlot",
12692
13169
  "__bfText",
13170
+ "tAfter",
12693
13171
  "beginTurn",
12694
13172
  "endTurn"
12695
13173
  ];
@@ -14560,9 +15038,63 @@ import { BF_SCOPE as BF_SCOPE2, BF_HOST, BF_AT } from "@barefootjs/shared";
14560
15038
  function loopKeyFn(loop) {
14561
15039
  if (loop.key === null)
14562
15040
  return "null";
14563
- const params = loop.kind === "nested" ? loop.param : `${loop.param}${loop.index ? `, ${loop.index}` : ""}`;
15041
+ const params = `${loop.param}${loop.index ? `, ${loop.index}` : ""}`;
14564
15042
  return `(${params}) => String(${loop.key})`;
14565
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
+ }
14566
15098
  function buildChildRefBindings(refs, loopParam, loopParamBindings) {
14567
15099
  if (refs.length === 0)
14568
15100
  return [];
@@ -14743,6 +15275,7 @@ function buildOuterNestedPlan(elem, comp) {
14743
15275
  }
14744
15276
  function buildInnerLoopNestedPlan(elem, innerLoop, innerComps) {
14745
15277
  const outerIndexParam = elem.index || "__idx";
15278
+ const innerIndexParam = innerLoop.index || "__innerIdx";
14746
15279
  const comps = innerComps.map((comp) => ({
14747
15280
  componentName: comp.name,
14748
15281
  selector: buildCompSelector(comp),
@@ -14759,7 +15292,8 @@ function buildInnerLoopNestedPlan(elem, innerLoop, innerComps) {
14759
15292
  innerContainerSlotId: innerLoop.containerSlotId ?? null,
14760
15293
  innerArrayExpr: innerLoop.array,
14761
15294
  innerParam: innerLoop.param,
14762
- innerOffsetExpr: buildLoopChildIndexExpr("__innerIdx", innerLoop.offset),
15295
+ innerIndexParam,
15296
+ innerOffsetExpr: buildLoopChildIndexExpr(innerIndexParam, innerLoop.offset),
14763
15297
  innerPreludeStatements: innerLoop.mapPreamble ? [innerLoop.mapPreamble] : [],
14764
15298
  depth: innerLoop.depth,
14765
15299
  comps
@@ -14776,9 +15310,11 @@ function buildComponentRootedInnerLoopPlan(elem, innerLoop, innerComps) {
14776
15310
  containerVar: `_${varSlotId(elem.slotId)}`,
14777
15311
  outerArrayExpr: elem.array,
14778
15312
  outerParam: elem.param,
15313
+ outerIndexParam: elem.index,
14779
15314
  outerPreludeStatements: elem.mapPreamble ? [elem.mapPreamble] : [],
14780
15315
  innerArrayExpr: innerLoop.array,
14781
15316
  innerParam: innerLoop.param,
15317
+ innerIndexParam: innerLoop.index,
14782
15318
  innerPreludeStatements: innerLoop.mapPreamble ? [innerLoop.mapPreamble] : [],
14783
15319
  depth: innerLoop.depth,
14784
15320
  comps
@@ -14871,6 +15407,7 @@ function emitInnerLoopNested(lines, plan) {
14871
15407
  innerContainerSlotId,
14872
15408
  innerArrayExpr,
14873
15409
  innerParam,
15410
+ innerIndexParam,
14874
15411
  innerOffsetExpr,
14875
15412
  innerPreludeStatements,
14876
15413
  depth,
@@ -14889,7 +15426,7 @@ function emitInnerLoopNested(lines, plan) {
14889
15426
  } else {
14890
15427
  lines.push(` const __ic = __outerEl`);
14891
15428
  }
14892
- lines.push(` ${innerArrayExpr}.forEach((${innerParam}, __innerIdx) => {`);
15429
+ lines.push(` ${innerArrayExpr}.forEach((${innerParam}, ${innerIndexParam}) => {`);
14893
15430
  lines.push(` const __innerEl = __ic.children[${innerOffsetExpr}]`);
14894
15431
  lines.push(` if (!__innerEl) return`);
14895
15432
  for (const stmt of innerPreludeStatements) {
@@ -14910,9 +15447,11 @@ function emitComponentRootedInnerLoop(lines, plan) {
14910
15447
  containerVar,
14911
15448
  outerArrayExpr,
14912
15449
  outerParam,
15450
+ outerIndexParam,
14913
15451
  outerPreludeStatements,
14914
15452
  innerArrayExpr,
14915
15453
  innerParam,
15454
+ innerIndexParam,
14916
15455
  innerPreludeStatements,
14917
15456
  depth,
14918
15457
  comps
@@ -14926,11 +15465,11 @@ function emitComponentRootedInnerLoop(lines, plan) {
14926
15465
  lines.push(` const ${scopesVar(i)} = qsaChildScopes(${containerVar}, ${comp.selector})`);
14927
15466
  lines.push(` let ${cursorVar(i)} = 0`);
14928
15467
  });
14929
- lines.push(` ${outerArrayExpr}.forEach((${outerParam}) => {`);
15468
+ lines.push(` ${outerArrayExpr}.forEach((${outerParam}${outerIndexParam ? `, ${outerIndexParam}` : ""}) => {`);
14930
15469
  for (const stmt of outerPreludeStatements) {
14931
15470
  lines.push(` ${stmt}`);
14932
15471
  }
14933
- lines.push(` ${innerArrayExpr}.forEach((${innerParam}) => {`);
15472
+ lines.push(` ${innerArrayExpr}.forEach((${innerParam}${innerIndexParam ? `, ${innerIndexParam}` : ""}) => {`);
14934
15473
  for (const stmt of innerPreludeStatements) {
14935
15474
  lines.push(` ${stmt}`);
14936
15475
  }
@@ -15082,6 +15621,7 @@ function buildBranchInnerLoopsPlan(args) {
15082
15621
  ...ev,
15083
15622
  handler: wrapInner(ev.handler)
15084
15623
  }));
15624
+ const indexAlias = nestedLoopIndexAlias(inner, `__bidxbr_${i}`, paramHead, inner.childComponents ?? [], inner.bindings.events);
15085
15625
  const reactiveTexts = inner.bindings.reactiveTexts.map((text) => ({
15086
15626
  slotId: text.slotId,
15087
15627
  wrappedExpression: wrapBoth(text.expression),
@@ -15096,6 +15636,7 @@ function buildBranchInnerLoopsPlan(args) {
15096
15636
  keyFn: loopKeyFn(inner),
15097
15637
  paramHead,
15098
15638
  paramUnwrap,
15639
+ indexAlias,
15099
15640
  wrappedTemplate: inner.template,
15100
15641
  wrappedKey,
15101
15642
  keyDepth: 1,
@@ -15337,7 +15878,7 @@ function buildInnerLoopsPlan(args) {
15337
15878
  const containerExpr = inner.containerSlotId ? `qsa(${parentElVar}, '[bf="${inner.containerSlotId}"]')` : parentElVar;
15338
15879
  const refsParent = !!outerLoopParam && (inner.arrayFreeIdentifiers?.has(outerLoopParam) ?? false);
15339
15880
  const useReactive = refsParent && !!inner.template;
15340
- const emit = useReactive ? buildReactiveEmit(inner, level, wrapOuter) : buildStaticEmit(inner, level);
15881
+ const emit = useReactive ? buildReactiveEmit(inner, level, wrapOuter, uidSuffix) : buildStaticEmit(inner, level, uidSuffix);
15341
15882
  const arrayExpr = useReactive ? wrapOuter(inner.array) : inner.array;
15342
15883
  const childLevelsPlan = childLevels.length > 0 ? buildInnerLoopsPlan({
15343
15884
  levels: childLevels,
@@ -15363,7 +15904,7 @@ function buildInnerLoopsPlan(args) {
15363
15904
  }
15364
15905
  return plan;
15365
15906
  }
15366
- function buildReactiveEmit(inner, level, wrapOuter) {
15907
+ function buildReactiveEmit(inner, level, wrapOuter, uidSuffix) {
15367
15908
  const wrapInner = (expr) => wrapLoopParamAsAccessor(expr, inner.param, inner.paramBindings);
15368
15909
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(inner.param, inner.paramBindings);
15369
15910
  const wrappedKey = inner.key ? wrapLoopParamAsAccessor(inner.key, inner.param, inner.paramBindings) : null;
@@ -15410,6 +15951,9 @@ function buildReactiveEmit(inner, level, wrapOuter) {
15410
15951
  };
15411
15952
  });
15412
15953
  const preludeStatements = [];
15954
+ const indexAlias = nestedLoopIndexAlias(inner, `__innerIdx${uidSuffix}`, paramHead, level.comps, level.events);
15955
+ if (indexAlias)
15956
+ preludeStatements.push(indexAlias);
15413
15957
  if (paramUnwrap)
15414
15958
  preludeStatements.push(paramUnwrap);
15415
15959
  if (inner.mapPreamble)
@@ -15430,8 +15974,13 @@ function buildReactiveEmit(inner, level, wrapOuter) {
15430
15974
  childRefs
15431
15975
  };
15432
15976
  }
15433
- function buildStaticEmit(inner, level) {
15434
- 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);
15435
15984
  return {
15436
15985
  mode: "static",
15437
15986
  rawKey: inner.key ?? null,
@@ -15566,6 +16115,7 @@ function buildDynamicLoopDelegationPlan(elem, profileComponentName) {
15566
16115
  param: elem.param,
15567
16116
  paramBindings: elem.paramBindings,
15568
16117
  key: elem.key,
16118
+ index: elem.index,
15569
16119
  mapPreamble: elem.mapPreamble ?? null
15570
16120
  })
15571
16121
  };
@@ -15581,6 +16131,7 @@ function buildBranchLoopDelegationPlan(loop, cv, profileComponentName) {
15581
16131
  param: loop.param,
15582
16132
  paramBindings: loop.paramBindings,
15583
16133
  key: loop.key,
16134
+ index: loop.index,
15584
16135
  mapPreamble: loop.mapPreamble ?? null
15585
16136
  })
15586
16137
  };
@@ -15596,7 +16147,8 @@ function buildStaticArrayDelegationPlan(elem, profileComponentName) {
15596
16147
  arrayExpr: buildChainedArrayExpr(elem),
15597
16148
  param: elem.param,
15598
16149
  mapPreamble: elem.mapPreamble ?? null,
15599
- offset: elem.offset ?? null
16150
+ offset: elem.offset ?? null,
16151
+ indexParam: elem.index ?? null
15600
16152
  }
15601
16153
  };
15602
16154
  }
@@ -15611,7 +16163,8 @@ function buildKeyedOrIndexLookup(args) {
15611
16163
  paramBindings: args.paramBindings,
15612
16164
  keyWithItem,
15613
16165
  mapPreamble: args.mapPreamble,
15614
- hasBindings
16166
+ hasBindings,
16167
+ indexParam: args.index
15615
16168
  };
15616
16169
  }
15617
16170
  return {
@@ -15619,7 +16172,8 @@ function buildKeyedOrIndexLookup(args) {
15619
16172
  arrayExpr: args.array,
15620
16173
  param: args.param,
15621
16174
  mapPreamble: args.mapPreamble,
15622
- hasBindings
16175
+ hasBindings,
16176
+ indexParam: args.index
15623
16177
  };
15624
16178
  }
15625
16179
 
@@ -15929,151 +16483,6 @@ function emitReactiveChildProps(lines, ctx) {
15929
16483
  }
15930
16484
  }
15931
16485
 
15932
- // src/ir-to-client-js/control-flow/stringify/template-parse.ts
15933
- var SVG_ROOT_TAGS = new Set([
15934
- "svg",
15935
- "path",
15936
- "circle",
15937
- "rect",
15938
- "line",
15939
- "polyline",
15940
- "polygon",
15941
- "ellipse",
15942
- "text",
15943
- "tspan",
15944
- "textPath",
15945
- "g",
15946
- "defs",
15947
- "use",
15948
- "symbol",
15949
- "switch",
15950
- "clipPath",
15951
- "mask",
15952
- "marker",
15953
- "pattern",
15954
- "linearGradient",
15955
- "radialGradient",
15956
- "stop",
15957
- "image",
15958
- "foreignObject",
15959
- "filter",
15960
- "feBlend",
15961
- "feColorMatrix",
15962
- "feComposite",
15963
- "feFlood",
15964
- "feGaussianBlur",
15965
- "feMerge",
15966
- "feMergeNode",
15967
- "feMorphology",
15968
- "feOffset",
15969
- "feTurbulence",
15970
- "animate",
15971
- "animateTransform",
15972
- "animateMotion"
15973
- ]);
15974
- function templateRootIsSvg(template) {
15975
- const stripped = stripLeadingNonContent(template);
15976
- const m = stripped.match(/^<\s*([A-Za-z][A-Za-z0-9-]*)/);
15977
- if (m) {
15978
- const tag = m[1];
15979
- if (SVG_ROOT_TAGS.has(tag))
15980
- return true;
15981
- return SVG_ROOT_TAGS.has(tag.toLowerCase());
15982
- }
15983
- const branches = extractConditionalBranchTemplates(stripped);
15984
- if (branches === null || branches.length === 0)
15985
- return false;
15986
- return branches.every(templateRootIsSvg);
15987
- }
15988
- function stripLeadingNonContent(template) {
15989
- let s = template.trimStart();
15990
- while (s.startsWith("<!--")) {
15991
- const end = s.indexOf("-->");
15992
- if (end < 0)
15993
- return s;
15994
- s = s.slice(end + 3).trimStart();
15995
- }
15996
- return s;
15997
- }
15998
- function extractConditionalBranchTemplates(template) {
15999
- if (!template.startsWith("${"))
16000
- return null;
16001
- const exprEnd = findInterpolationEnd(template, 2);
16002
- if (exprEnd < 0)
16003
- return null;
16004
- const trailing = stripLeadingNonContent(template.slice(exprEnd + 1));
16005
- if (trailing.length > 0)
16006
- return null;
16007
- const expr = template.slice(2, exprEnd);
16008
- return findTopLevelTemplateLiterals(expr);
16009
- }
16010
- function emitTemplateCloneInline(template) {
16011
- if (templateRootIsSvg(template)) {
16012
- return `const __tpl = document.createElement('template'); __tpl.innerHTML = \`<svg>${template}</svg>\`; return __tpl.content.firstElementChild.firstElementChild.cloneNode(true)`;
16013
- }
16014
- return `const __tpl = document.createElement('template'); __tpl.innerHTML = \`${template}\`; return __tpl.content.firstElementChild.cloneNode(true)`;
16015
- }
16016
- function emitHoistedTemplateDecl(lines, indent, tplVar, skeletonTemplate) {
16017
- const isSvg = templateRootIsSvg(skeletonTemplate);
16018
- const html = isSvg ? `<svg>${skeletonTemplate}</svg>` : skeletonTemplate;
16019
- lines.push(`${indent}const ${tplVar} = document.createElement('template')`);
16020
- lines.push(`${indent}${tplVar}.innerHTML = \`${html}\``);
16021
- }
16022
- function hoistedCloneExpr(tplVar, skeletonTemplate) {
16023
- return templateRootIsSvg(skeletonTemplate) ? `${tplVar}.content.firstElementChild.firstElementChild.cloneNode(true)` : `${tplVar}.content.firstElementChild.cloneNode(true)`;
16024
- }
16025
- function emitTemplateCloneLines(template, indent) {
16026
- if (templateRootIsSvg(template)) {
16027
- return [
16028
- `${indent}const __tpl = document.createElement('template')`,
16029
- `${indent}__tpl.innerHTML = \`<svg>${template}</svg>\``,
16030
- `${indent}return __tpl.content.firstElementChild.firstElementChild.cloneNode(true)`
16031
- ];
16032
- }
16033
- return [
16034
- `${indent}const __tpl = document.createElement('template')`,
16035
- `${indent}__tpl.innerHTML = \`${template}\``,
16036
- `${indent}return __tpl.content.firstElementChild.cloneNode(true)`
16037
- ];
16038
- }
16039
- function emitLoopItemElementSetup(lines, opts) {
16040
- const { template, bodyIsMultiRoot, indent, singleRootLayout } = opts;
16041
- const innerIndent = indent + " ";
16042
- if (bodyIsMultiRoot) {
16043
- lines.push(`${indent}let __el, __extras`);
16044
- lines.push(`${indent}if (__existing) {`);
16045
- lines.push(`${innerIndent}__el = __existing`);
16046
- lines.push(`${indent}} else {`);
16047
- for (const ln of emitMultiRootTemplateCloneLines(template, innerIndent, "__el", "__extras")) {
16048
- lines.push(ln);
16049
- }
16050
- lines.push(`${innerIndent}__el.__bfExtras = __extras`);
16051
- lines.push(`${indent}}`);
16052
- return;
16053
- }
16054
- if (singleRootLayout === "inline") {
16055
- const cloneExpr = emitTemplateCloneInline(template);
16056
- lines.push(`${indent}const __el = __existing ?? (() => { ${cloneExpr} })()`);
16057
- return;
16058
- }
16059
- lines.push(`${indent}const __el = __existing ?? (() => {`);
16060
- for (const ln of emitTemplateCloneLines(template, innerIndent))
16061
- lines.push(ln);
16062
- lines.push(`${indent}})()`);
16063
- }
16064
- function emitMultiRootTemplateCloneLines(template, indent, varEl, varExtras) {
16065
- const isSvg = templateRootIsSvg(template);
16066
- const innerHtmlExpr = isSvg ? `\`<svg>${template}</svg>\`` : `\`${template}\``;
16067
- const parentExpr = isSvg ? `__tpl.content.firstElementChild` : `__tpl.content`;
16068
- return [
16069
- `${indent}const __tpl = document.createElement('template')`,
16070
- `${indent}__tpl.innerHTML = ${innerHtmlExpr}`,
16071
- `${indent}${varEl} = ${parentExpr}.firstElementChild.cloneNode(true)`,
16072
- `${indent}${varExtras} = []`,
16073
- `${indent}{ let __sib = ${parentExpr}.firstElementChild.nextElementSibling; while (__sib) { ${varExtras}.push(__sib.cloneNode(true)); __sib = __sib.nextElementSibling } }`
16074
- ];
16075
- }
16076
-
16077
16486
  // src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts
16078
16487
  function stringifyBranchEventBindings(lines, plan, indent) {
16079
16488
  for (const slot of plan) {
@@ -16095,6 +16504,9 @@ function stringifyBranchInnerLoops(lines, plan, indent, pc) {
16095
16504
  const uid = inner.uidSuffix;
16096
16505
  lines.push(`${indent}{ const __bic${uid} = ${inner.containerExpr}`);
16097
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
+ }
16098
16510
  if (inner.paramUnwrap) {
16099
16511
  lines.push(`${indent} ${inner.paramUnwrap}`);
16100
16512
  }
@@ -16159,13 +16571,15 @@ function stringifyLoopChildArm(lines, arm, armIndent, pc) {
16159
16571
 
16160
16572
  // src/ir-to-client-js/control-flow/stringify/reactive-effects.ts
16161
16573
  function stringifyReactiveEffects(lines, plan, opts) {
16162
- const { indent, elVar, bodyIsMultiRoot } = opts;
16574
+ const { indent, elVar, bodyIsMultiRoot, elementIndexBySlot, textIndexBySlot } = opts;
16163
16575
  const lookup = bodyIsMultiRoot ? "qsaItem" : "qsa";
16164
16576
  const pc = plan.profileComponentName;
16165
16577
  const bindingBfId = (slotId) => profileBindingId(pc, slotId);
16166
16578
  for (const slot of plan.attrSlots) {
16167
16579
  const varName = `__ra_${varSlotId(slot.slotId)}`;
16168
- 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}`);
16169
16583
  lines.push(`${indent}if (${varName}) {`);
16170
16584
  for (const attr of slot.attrs) {
16171
16585
  lines.push(`${indent} createEffect(() => {`);
@@ -16177,15 +16591,19 @@ function stringifyReactiveEffects(lines, plan, opts) {
16177
16591
  lines.push(`${indent}} }`);
16178
16592
  }
16179
16593
  for (const text of plan.outerTexts) {
16180
- emitOuterText(lines, indent, elVar, text, bindingBfId(text.slotId));
16594
+ emitOuterText(lines, indent, elVar, text, bindingBfId(text.slotId), textIndexBySlot?.get(text.slotId));
16181
16595
  }
16182
16596
  for (const cond of plan.conditionals) {
16183
16597
  emitOuterConditional(lines, indent, elVar, cond, pc);
16184
16598
  }
16185
16599
  }
16186
- function emitOuterText(lines, indent, elVar, text, bfId = "") {
16600
+ function emitOuterText(lines, indent, elVar, text, bfId = "", pIdx) {
16187
16601
  const varName = `__rt_${varSlotId(text.slotId)}`;
16188
- 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
+ }
16189
16607
  lines.push(`${indent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }${bfId}) }`);
16190
16608
  }
16191
16609
  function emitOuterConditional(lines, indent, elVar, cond, pc) {
@@ -16217,6 +16635,41 @@ function emitArmText(lines, indent, text, pc) {
16217
16635
  lines.push(`${indent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }${profileBindingId(pc, text.slotId)}) }`);
16218
16636
  }
16219
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
+
16220
16673
  // src/ir-to-client-js/control-flow/stringify/component-loop.ts
16221
16674
  function stringifyComponentLoop(lines, plan) {
16222
16675
  const {
@@ -16276,11 +16729,13 @@ function emitNestedInit(lines, indent, parentVar, nc) {
16276
16729
  function emitLoopChildRefs(lines, refs, opts) {
16277
16730
  if (refs.length === 0)
16278
16731
  return;
16279
- const { indent, elVar, bodyIsMultiRoot } = opts;
16732
+ const { indent, elVar, bodyIsMultiRoot, elementIndexBySlot } = opts;
16280
16733
  const lookup = bodyIsMultiRoot ? "qsaItem" : "qsa";
16281
16734
  for (const ref of refs) {
16282
16735
  const varName = `__rf_${varSlotId(ref.childSlotId)}`;
16283
- 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}`);
16284
16739
  lines.push(`${indent}if (${varName}) ${emitRefCall(ref.callback, varName)} }`);
16285
16740
  }
16286
16741
  }
@@ -16360,10 +16815,31 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
16360
16815
  singleRootLayout: "inline"
16361
16816
  });
16362
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
+ }
16363
16833
  if (reactiveEffects !== null) {
16364
- 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
+ });
16365
16841
  }
16366
- emitLoopChildRefs(lines, childRefs, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot });
16842
+ emitLoopChildRefs(lines, childRefs, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot, elementIndexBySlot: pathPlan?.elementIndexBySlot });
16367
16843
  lines.push(`${bodyIndent}return __el`);
16368
16844
  lines.push(`${topIndent}}, '${markerId}'${loopBfId})`);
16369
16845
  }
@@ -16430,12 +16906,14 @@ function stringifyStaticLoop(lines, plan) {
16430
16906
  lines.push(` let __iterEl = ${containerVar}.children[${childIndexExpr}]`);
16431
16907
  if (csrMaterialize) {
16432
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;
16433
16911
  if (csrMaterialize.bodyIsMultiRoot) {
16434
16912
  lines.push(` const __mtpl = document.createElement('template')`);
16435
- lines.push(` __mtpl.innerHTML = \`${csrMaterialize.itemTemplate}\``);
16913
+ lines.push(` __mtpl.innerHTML = \`${itemHtml}\``);
16436
16914
  lines.push(` const __anchor = ${containerVar}.children[${childIndexExpr}] ?? null`);
16437
16915
  lines.push(` let __first = null`);
16438
- lines.push(` let __sib = __mtpl.content.firstElementChild`);
16916
+ lines.push(` let __sib = __mtpl.content${isSvg ? ".firstElementChild" : ""}.firstElementChild`);
16439
16917
  lines.push(` while (__sib) {`);
16440
16918
  lines.push(` const __next = __sib.nextElementSibling`);
16441
16919
  lines.push(` const __cloned = __sib.cloneNode(true)`);
@@ -16446,8 +16924,8 @@ function stringifyStaticLoop(lines, plan) {
16446
16924
  lines.push(` __iterEl = __first`);
16447
16925
  } else {
16448
16926
  lines.push(` const __tpl = document.createElement('template')`);
16449
- lines.push(` __tpl.innerHTML = \`${csrMaterialize.itemTemplate}\``);
16450
- lines.push(` const __cloned = __tpl.content.firstElementChild`);
16927
+ lines.push(` __tpl.innerHTML = \`${itemHtml}\``);
16928
+ lines.push(` const __cloned = __tpl.content${isSvg ? ".firstElementChild" : ""}.firstElementChild`);
16451
16929
  lines.push(` if (__cloned) {`);
16452
16930
  lines.push(` const __anchor = ${containerVar}.children[${childIndexExpr}] ?? null`);
16453
16931
  lines.push(` ${containerVar}.insertBefore(__cloned, __anchor)`);
@@ -16514,7 +16992,10 @@ function emitReactive(lines, inner, indent, pc) {
16514
16992
  lines.push(`${innerIndent} __innerEl${uid}.__bfExtras = __innerExtras${uid}`);
16515
16993
  lines.push(`${indent} }`);
16516
16994
  } else {
16517
- 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) })()`);
16518
16999
  }
16519
17000
  if (emit.wrappedKey) {
16520
17001
  lines.push(`${indent} __innerEl${uid}.setAttribute('${keyAttrName(inner.keyDepth)}', String(${emit.wrappedKey}))`);
@@ -16662,6 +17143,13 @@ function withTurn(call, componentName, childSlotId, eventName) {
16662
17143
  const id = JSON.stringify(`${componentName}#handler:${childSlotId}:${eventName}`);
16663
17144
  return `beginTurn(${id}); try { ${call} } finally { endTurn() }`;
16664
17145
  }
17146
+ function indexBindingLine(handler, indexParam, indexExpr) {
17147
+ if (!indexParam || indexParam === indexExpr)
17148
+ return null;
17149
+ if (!extractFreeIdentifiersFromText(handler).has(indexParam))
17150
+ return null;
17151
+ return `const ${indexParam} = ${indexExpr}`;
17152
+ }
16665
17153
  function stringifyEventDelegation(lines, plan) {
16666
17154
  const { containerVar, events, itemLookup, profileComponentName } = plan;
16667
17155
  const eventsByName = new Map;
@@ -16707,8 +17195,9 @@ function stringifyEventDelegation(lines, plan) {
16707
17195
  }
16708
17196
  }
16709
17197
  function emitKeyedLookup(ls, ev, handlerCall, lookup) {
16710
- const { arrayExpr, param, keyWithItem, mapPreamble, hasBindings } = lookup;
17198
+ const { arrayExpr, param, keyWithItem, mapPreamble, hasBindings, indexParam } = lookup;
16711
17199
  if (ev.nestedLoops.length === 0) {
17200
+ const idxLine2 = indexBindingLine(ev.handler, indexParam, `${arrayExpr}.findIndex(item => String(${keyWithItem}) === key)`);
16712
17201
  ls.push(` const li = ${varSlotId(ev.childSlotId)}El.closest('[${DATA_KEY}]')`);
16713
17202
  ls.push(` if (li) {`);
16714
17203
  ls.push(` const key = li.getAttribute('${DATA_KEY}')`);
@@ -16718,13 +17207,18 @@ function emitKeyedLookup(ls, ev, handlerCall, lookup) {
16718
17207
  ls.push(` const ${param} = __bfLoopItem`);
16719
17208
  if (mapPreamble)
16720
17209
  ls.push(` ${mapPreamble}`);
17210
+ if (idxLine2)
17211
+ ls.push(` ${idxLine2}`);
16721
17212
  ls.push(` ${handlerCall}`);
16722
17213
  ls.push(` }`);
16723
17214
  } else {
16724
17215
  ls.push(` const ${param} = ${arrayExpr}.find(item => String(${keyWithItem}) === key)`);
16725
17216
  if (mapPreamble)
16726
17217
  ls.push(` ${mapPreamble}`);
16727
- ls.push(` if (${param}) ${handlerCall}`);
17218
+ if (idxLine2)
17219
+ ls.push(` if (${param}) { ${idxLine2}; ${handlerCall} }`);
17220
+ else
17221
+ ls.push(` if (${param}) ${handlerCall}`);
16728
17222
  }
16729
17223
  ls.push(` }`);
16730
17224
  return;
@@ -16753,10 +17247,15 @@ function emitKeyedLookup(ls, ev, handlerCall, lookup) {
16753
17247
  const allParams = [outerGuard, ...ev.nestedLoops.map((n) => n.param)];
16754
17248
  if (mapPreamble)
16755
17249
  ls.push(` ${mapPreamble}`);
16756
- ls.push(` if (${allParams.join(" && ")}) ${handlerCall}`);
17250
+ const idxLine = indexBindingLine(ev.handler, indexParam, `${arrayExpr}.findIndex(item => String(${keyWithItem}) === outerKey)`);
17251
+ if (idxLine)
17252
+ ls.push(` if (${allParams.join(" && ")}) { ${idxLine}; ${handlerCall} }`);
17253
+ else
17254
+ ls.push(` if (${allParams.join(" && ")}) ${handlerCall}`);
16757
17255
  }
16758
17256
  function emitDynamicIndexLookup(ls, ev, handlerCall, lookup) {
16759
- const { arrayExpr, param, mapPreamble, hasBindings } = lookup;
17257
+ const { arrayExpr, param, mapPreamble, hasBindings, indexParam } = lookup;
17258
+ const idxLine = indexBindingLine(ev.handler, indexParam, "idx");
16760
17259
  ls.push(` const li = ${varSlotId(ev.childSlotId)}El.closest('li, [bf-i]')`);
16761
17260
  ls.push(` if (li && li.parentElement) {`);
16762
17261
  ls.push(` const idx = Array.from(li.parentElement.children).indexOf(li)`);
@@ -16766,18 +17265,24 @@ function emitDynamicIndexLookup(ls, ev, handlerCall, lookup) {
16766
17265
  ls.push(` const ${param} = __bfLoopItem`);
16767
17266
  if (mapPreamble)
16768
17267
  ls.push(` ${mapPreamble}`);
17268
+ if (idxLine)
17269
+ ls.push(` ${idxLine}`);
16769
17270
  ls.push(` ${handlerCall}`);
16770
17271
  ls.push(` }`);
16771
17272
  } else {
16772
17273
  ls.push(` const ${param} = ${arrayExpr}[idx]`);
16773
17274
  if (mapPreamble)
16774
17275
  ls.push(` ${mapPreamble}`);
16775
- ls.push(` if (${param}) ${handlerCall}`);
17276
+ if (idxLine)
17277
+ ls.push(` if (${param}) { ${idxLine}; ${handlerCall} }`);
17278
+ else
17279
+ ls.push(` if (${param}) ${handlerCall}`);
16776
17280
  }
16777
17281
  ls.push(` }`);
16778
17282
  }
16779
17283
  function emitStaticIndexLookup(ls, ev, handlerCall, lookup, containerVar) {
16780
- const { arrayExpr, param, mapPreamble, offset } = lookup;
17284
+ const { arrayExpr, param, mapPreamble, offset, indexParam } = lookup;
17285
+ const idxLine = indexBindingLine(ev.handler, indexParam, "__idx");
16781
17286
  ls.push(` let __el = ${varSlotId(ev.childSlotId)}El`);
16782
17287
  ls.push(` while (__el.parentElement && __el.parentElement !== ${containerVar}) __el = __el.parentElement`);
16783
17288
  ls.push(` if (__el.parentElement === ${containerVar}) {`);
@@ -16786,7 +17291,10 @@ function emitStaticIndexLookup(ls, ev, handlerCall, lookup, containerVar) {
16786
17291
  ls.push(` const ${param} = ${arrayExpr}[__idx]`);
16787
17292
  if (mapPreamble)
16788
17293
  ls.push(` ${mapPreamble}`);
16789
- ls.push(` if (${param}) ${handlerCall}`);
17294
+ if (idxLine)
17295
+ ls.push(` if (${param}) { ${idxLine}; ${handlerCall} }`);
17296
+ else
17297
+ ls.push(` if (${param}) ${handlerCall}`);
16790
17298
  ls.push(` }`);
16791
17299
  }
16792
17300
 
@@ -17046,6 +17554,7 @@ function buildPlainLoopPlan(elem, profileComponentName) {
17046
17554
  mapPreambleWrapped: elem.mapPreamble ? wrap(elem.mapPreamble) : "",
17047
17555
  template: elem.template,
17048
17556
  skeletonTemplate: elem.skeletonTemplate,
17557
+ skeletonPaths: elem.skeletonPaths,
17049
17558
  reactiveEffects: hasReactive2 ? buildLoopReactiveEffectsPlan(elem, profileComponentName) : null,
17050
17559
  childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings),
17051
17560
  bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false,
@@ -20019,6 +20528,8 @@ function isStringTypedOperand(expr, isStringName) {
20019
20528
  if (expr.kind === "member" && expr.object.kind === "identifier" && expr.object.name === "props") {
20020
20529
  return isStringName(expr.property);
20021
20530
  }
20531
+ if (expr.kind === "identifier")
20532
+ return isStringName(expr.name);
20022
20533
  if (expr.kind === "binary" && expr.op === "+") {
20023
20534
  return isStringTypedOperand(expr.left, isStringName) || isStringTypedOperand(expr.right, isStringName);
20024
20535
  }
@@ -20044,7 +20555,7 @@ function emitParsedExpr(expr, emitter) {
20044
20555
  return emitter.call(expr.callee, expr.args, emit);
20045
20556
  }
20046
20557
  case "member":
20047
- return emitter.member(expr.object, expr.property, expr.computed, emit);
20558
+ return emitter.member(expr.object, expr.property, expr.computed, expr.optional, emit);
20048
20559
  case "index-access":
20049
20560
  return emitter.indexAccess(expr.object, expr.index, emit);
20050
20561
  case "binary":
@@ -20078,6 +20589,221 @@ function emitParsedExpr(expr, emitter) {
20078
20589
  }
20079
20590
  }
20080
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
+ }
20081
20807
  // src/query-href-lowering.ts
20082
20808
  function matchQueryHrefCall(callee, args, localNames) {
20083
20809
  if (callee.kind !== "identifier" || !localNames.has(callee.name))
@@ -20213,6 +20939,71 @@ function emitAttrValue(value, emitter, name) {
20213
20939
  }
20214
20940
  }
20215
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
+ }
20216
21007
  // src/combine-client-js.ts
20217
21008
  import ts19 from "typescript";
20218
21009
  var CHILD_PLACEHOLDER_RE = /import '\/\* @bf-child:(\w+) \*\/'/g;
@@ -23054,6 +23845,7 @@ function profileToJSON(profile) {
23054
23845
  registerBuiltinLoweringPlugins();
23055
23846
  export {
23056
23847
  tsNodeToParsedExpr,
23848
+ tryEvaluateSignalInit,
23057
23849
  traceUpdatePath,
23058
23850
  testAdapter,
23059
23851
  stringifyParsedExpr,
@@ -23061,7 +23853,9 @@ export {
23061
23853
  serializeParsedExpr,
23062
23854
  searchParamsLocalNames,
23063
23855
  rewriteImportsForTemplate,
23856
+ resolveStaticLoopSource,
23064
23857
  resolveSetters,
23858
+ resolveDangerousInnerHtml,
23065
23859
  resetCompilerCounters,
23066
23860
  renderImportMapHtml,
23067
23861
  registerLoweringPlugin,
@@ -23096,6 +23890,8 @@ export {
23096
23890
  isStringConcatBinary,
23097
23891
  isLowerableObjectRestDestructure,
23098
23892
  isLowerableLoopDestructure,
23893
+ isFullyStaticLiteral,
23894
+ isDangerousInnerHtmlAttr,
23099
23895
  isBooleanAttr,
23100
23896
  importsSearchParams,
23101
23897
  identifierPath,
@@ -23136,6 +23932,8 @@ export {
23136
23932
  extractFunctionParams,
23137
23933
  extractArrowBodyExpression,
23138
23934
  exprToString,
23935
+ evaluateStaticLiteral,
23936
+ evaluateSignalInit,
23139
23937
  evaluateProfileGates,
23140
23938
  evalStringArrayJoin,
23141
23939
  envSignalReaderFor,
@@ -23148,6 +23946,8 @@ export {
23148
23946
  diffStaticBudget,
23149
23947
  diffProfiles,
23150
23948
  describeFallback,
23949
+ dangerousInnerHtmlMetacharViolation,
23950
+ dangerousInnerHtmlDiagnostic,
23151
23951
  createProgramForFile,
23152
23952
  createProgramForCorpus,
23153
23953
  createError,
@@ -23156,6 +23956,7 @@ export {
23156
23956
  compileJSX,
23157
23957
  combineParentChildClientJs,
23158
23958
  collectModuleStringConsts,
23959
+ collectLoopBoundNames,
23159
23960
  collectContextConsumers,
23160
23961
  buildWhyUpdate,
23161
23962
  buildStaticBudget,