@barefootjs/jsx 0.29.0 → 0.30.2

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 (90) hide show
  1. package/dist/adapters/child-scope.d.ts +17 -0
  2. package/dist/adapters/child-scope.d.ts.map +1 -0
  3. package/dist/adapters/loop-bound-names.d.ts.map +1 -1
  4. package/dist/analyzer-context.d.ts.map +1 -1
  5. package/dist/analyzer.d.ts.map +1 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +990 -367
  9. package/dist/ir-to-client-js/collect-elements.d.ts +5 -25
  10. package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
  11. package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts +66 -2
  12. package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts.map +1 -1
  13. package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts.map +1 -1
  14. package/dist/ir-to-client-js/control-flow/plan/lazy-conditional.d.ts +92 -0
  15. package/dist/ir-to-client-js/control-flow/plan/lazy-conditional.d.ts.map +1 -0
  16. package/dist/ir-to-client-js/control-flow/plan/lazy-preamble.d.ts +134 -0
  17. package/dist/ir-to-client-js/control-flow/plan/lazy-preamble.d.ts.map +1 -0
  18. package/dist/ir-to-client-js/control-flow/plan/lazy-row-eligibility.d.ts +46 -11
  19. package/dist/ir-to-client-js/control-flow/plan/lazy-row-eligibility.d.ts.map +1 -1
  20. package/dist/ir-to-client-js/control-flow/plan/reactive-effects.d.ts +7 -0
  21. package/dist/ir-to-client-js/control-flow/plan/reactive-effects.d.ts.map +1 -1
  22. package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts.map +1 -1
  23. package/dist/ir-to-client-js/csr-substitute.d.ts.map +1 -1
  24. package/dist/ir-to-client-js/html-template.d.ts +2 -3
  25. package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
  26. package/dist/ir-to-client-js/plan/declaration-emit.d.ts +6 -0
  27. package/dist/ir-to-client-js/plan/declaration-emit.d.ts.map +1 -1
  28. package/dist/ir-to-client-js/reactivity.d.ts +1 -10
  29. package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
  30. package/dist/ir-to-client-js/types.d.ts +12 -0
  31. package/dist/ir-to-client-js/types.d.ts.map +1 -1
  32. package/dist/jsx-to-ir.d.ts.map +1 -1
  33. package/dist/prop-rewrite.d.ts +24 -7
  34. package/dist/prop-rewrite.d.ts.map +1 -1
  35. package/dist/rich-type-evidence.d.ts.map +1 -1
  36. package/dist/ssr-defaults.d.ts.map +1 -1
  37. package/dist/to-locale-date-lowering.d.ts.map +1 -1
  38. package/dist/types.d.ts +78 -0
  39. package/dist/types.d.ts.map +1 -1
  40. package/package.json +2 -2
  41. package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +6 -24
  42. package/src/__tests__/branch-loop-plain.test.ts +28 -45
  43. package/src/__tests__/csr-substitution-safety-divergence.test.ts +9 -3
  44. package/src/__tests__/csr-template-scope-soundness.test.ts +167 -0
  45. package/src/__tests__/early-return-branch-switch.test.ts +96 -0
  46. package/src/__tests__/form-control-value-ssr.test.ts +102 -0
  47. package/src/__tests__/lazy-conditional.test.ts +190 -0
  48. package/src/__tests__/lazy-preamble.test.ts +297 -0
  49. package/src/__tests__/lazy-row-eligibility.test.ts +97 -28
  50. package/src/__tests__/loop-item-root-scope.test.ts +213 -0
  51. package/src/__tests__/map-multi-return-body.test.ts +30 -3
  52. package/src/__tests__/nested-loop-conditional.test.ts +27 -6
  53. package/src/__tests__/preamble-attr-reactivity.test.ts +217 -0
  54. package/src/__tests__/preamble-declarations.test.ts +207 -0
  55. package/src/__tests__/ssr-defaults.test.ts +44 -0
  56. package/src/adapters/child-scope.ts +22 -0
  57. package/src/adapters/loop-bound-names.ts +6 -0
  58. package/src/analyzer-context.ts +43 -0
  59. package/src/analyzer.ts +33 -3
  60. package/src/index.ts +1 -0
  61. package/src/ir-to-client-js/collect-elements.ts +51 -10
  62. package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +1 -1
  63. package/src/ir-to-client-js/control-flow/plan/build-composite-loop.ts +2 -2
  64. package/src/ir-to-client-js/control-flow/plan/build-event-delegation.ts +3 -3
  65. package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +2 -2
  66. package/src/ir-to-client-js/control-flow/plan/build-lazy-row.ts +147 -5
  67. package/src/ir-to-client-js/control-flow/plan/build-loop.ts +2 -2
  68. package/src/ir-to-client-js/control-flow/plan/build-reactive-effects.ts +1 -0
  69. package/src/ir-to-client-js/control-flow/plan/lazy-conditional.ts +151 -0
  70. package/src/ir-to-client-js/control-flow/plan/lazy-preamble.ts +287 -0
  71. package/src/ir-to-client-js/control-flow/plan/lazy-row-eligibility.ts +85 -22
  72. package/src/ir-to-client-js/control-flow/plan/reactive-effects.ts +7 -0
  73. package/src/ir-to-client-js/control-flow/stringify/lazy-row.ts +133 -35
  74. package/src/ir-to-client-js/control-flow/stringify/reactive-effects.ts +24 -4
  75. package/src/ir-to-client-js/csr-substitute.ts +6 -1
  76. package/src/ir-to-client-js/emit-registration.ts +1 -1
  77. package/src/ir-to-client-js/html-template.ts +51 -40
  78. package/src/ir-to-client-js/index.ts +1 -1
  79. package/src/ir-to-client-js/plan/build-declaration-emit.ts +2 -0
  80. package/src/ir-to-client-js/plan/build-static-array-child-init.ts +1 -1
  81. package/src/ir-to-client-js/plan/declaration-emit.ts +6 -0
  82. package/src/ir-to-client-js/reactivity.ts +25 -0
  83. package/src/ir-to-client-js/stringify/declaration-emit.ts +9 -4
  84. package/src/ir-to-client-js/types.ts +12 -0
  85. package/src/jsx-to-ir.ts +409 -24
  86. package/src/prop-rewrite.ts +149 -19
  87. package/src/rich-type-evidence.ts +6 -7
  88. package/src/ssr-defaults.ts +7 -2
  89. package/src/to-locale-date-lowering.ts +8 -2
  90. package/src/types.ts +80 -0
@@ -30,7 +30,7 @@ import { irChildrenToJsExpr, renderPreamble, irToHtmlTemplate } from '../html-te
30
30
  function staticPreludeStatements(preamble: MapCallbackPreamble | undefined): string[] {
31
31
  return preamble
32
32
  ? [renderPreamble(preamble, {
33
- renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, undefined, undefined, true),
33
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, undefined, undefined),
34
34
  })]
35
35
  : []
36
36
  }
@@ -27,6 +27,12 @@ export interface SignalEmitPlan {
27
27
  getter: string
28
28
  /** Setter identifier — `null` for read-only signals. */
29
29
  setter: string | null
30
+ /**
31
+ * Source used the getter-elided form (`const [, setX] = ...`). `getter`
32
+ * holds the synthesized internal name (see `SignalInfo.getterElided`);
33
+ * stringify reproduces the source's hole instead of emitting it.
34
+ */
35
+ getterElided?: boolean
30
36
  /**
31
37
  * Fully-resolved initial value expression. Prop rewrites
32
38
  * (`<propsName>.X` → `_p.X`) and `?? <default>` insertions for controlled
@@ -16,6 +16,7 @@ import type {
16
16
  } from './types.ts'
17
17
  import { attrValueToString, freeIdsFromRefs, tokenContainsIdent } from './utils.ts'
18
18
  import { expandConstantForReactivity } from './prop-handling.ts'
19
+ import { extractFreeIdentifiersFromText } from './csr-substitute.ts'
19
20
  import { walkIR, stopAt } from './walker.ts'
20
21
 
21
22
  /**
@@ -621,12 +622,20 @@ export function collectLoopChildReactiveTexts(
621
622
  * same semantics. Pass true only when the caller also separately collects
622
623
  * nested reactive conditionals and gives each its own `insert()`.
623
624
  */
625
+ /** Does any name in `names` appear in `set`? Iterates rather than
626
+ * spreading — this runs per attribute (Copilot review). */
627
+ function anyNameIn(names: Iterable<string>, set: ReadonlySet<string>): boolean {
628
+ for (const n of names) if (set.has(n)) return true
629
+ return false
630
+ }
631
+
624
632
  export function collectLoopChildReactiveAttrs(
625
633
  node: IRNode,
626
634
  ctx: ClientJsContext,
627
635
  loopParam?: string,
628
636
  loopParamBindings?: readonly LoopParamBinding[],
629
637
  stopAtReactiveConditionals = false,
638
+ preambleNames?: ReadonlySet<string>,
630
639
  ): LoopChildReactiveAttr[] {
631
640
  const attrs: LoopChildReactiveAttr[] = []
632
641
  traverseElements(node, (el) => {
@@ -664,8 +673,23 @@ export function collectLoopChildReactiveAttrs(
664
673
  // / `hasFunctionCalls` fallback so the loop-child path matches the
665
674
  // top-level one — a harmless over-wrap at worst (an effect that
666
675
  // subscribes to nothing runs once).
676
+ // A `.map()` callback preamble local (#2447 follow-up). The
677
+ // classifier cannot see it: `const cls = row.done ? …` makes `cls`
678
+ // neither a signal read nor a loop-param read, so `class={cls}`
679
+ // scored 'none' and the value froze at its row-construction value —
680
+ // a same-key item update rewrote the sibling `{row.label}` text and
681
+ // left the class stale. Its reactivity is the PREAMBLE's: whatever
682
+ // the initializer read. Flag it and let the emitter re-run the
683
+ // preamble ahead of the write (`stringifyReactiveEffects`), which is
684
+ // the child-position `preambleRegions` treatment (#2389) applied to
685
+ // the attribute position.
686
+ const readsPreamble =
687
+ preambleNames !== undefined &&
688
+ preambleNames.size > 0 &&
689
+ anyNameIn(expanded.freeIds ?? extractFreeIdentifiersFromText(expanded.expr), preambleNames)
667
690
  const reactive =
668
691
  classifyReactivity(expanded.expr, ctx, loopParam, loopParamBindings, expanded.freeIds).kind !== 'none'
692
+ || readsPreamble
669
693
  || attr.callsReactiveGetters
670
694
  || attr.hasFunctionCalls
671
695
  if (!attr.clientOnly && !reactive) continue
@@ -675,6 +699,7 @@ export function collectLoopChildReactiveAttrs(
675
699
  expression: expanded.expr,
676
700
  ...pickAttrMetaFromIR(attr),
677
701
  ...(expanded.freeIds !== undefined && { freeIdentifiers: expanded.freeIds }),
702
+ ...(readsPreamble && { readsPreamble: true }),
678
703
  })
679
704
  }
680
705
  }
@@ -65,12 +65,17 @@ function bfIdArg(bfId: string | undefined): string {
65
65
  }
66
66
 
67
67
  function emitSignal(lines: string[], plan: SignalEmitPlan): void {
68
+ // Getter-elided source form (`const [, setX] = createSignal(...)`):
69
+ // reproduce the hole instead of introducing the synthesized internal
70
+ // getter name into output (#2468 sweep — dropping the declaration
71
+ // entirely left the setter as a guaranteed ReferenceError).
72
+ const getterSlot = plan.getterElided ? '' : plan.getter
68
73
  // Env signal (#2057): emit its own factory call verbatim (`createSearchParams()`)
69
74
  // — a stable request-scoped view, so no baked initial value, profile id,
70
75
  // controlled effect, or branch condition applies.
71
76
  if (plan.initializerOverride) {
72
77
  if (plan.setter) {
73
- lines.push(` const [${plan.getter}, ${plan.setter}] = ${plan.initializerOverride}`)
78
+ lines.push(` const [${getterSlot}, ${plan.setter}] = ${plan.initializerOverride}`)
74
79
  } else {
75
80
  lines.push(` const [${plan.getter}] = ${plan.initializerOverride}`)
76
81
  }
@@ -85,9 +90,9 @@ function emitSignal(lines: string[], plan: SignalEmitPlan): void {
85
90
  // runs when the branch is taken. Sibling branches with different
86
91
  // signals coexist because each gets its own `let` + guarded assign.
87
92
  if (plan.setter) {
88
- lines.push(` let ${plan.getter}, ${plan.setter}`)
93
+ lines.push(plan.getterElided ? ` let ${plan.setter}` : ` let ${plan.getter}, ${plan.setter}`)
89
94
  lines.push(` if (${plan.branchCondition}) {`)
90
- lines.push(` ;[${plan.getter}, ${plan.setter}] = createSignal(${plan.initialValueExpr}${id})`)
95
+ lines.push(` ;[${getterSlot}, ${plan.setter}] = createSignal(${plan.initialValueExpr}${id})`)
91
96
  lines.push(` }`)
92
97
  } else {
93
98
  lines.push(` let ${plan.getter}`)
@@ -101,7 +106,7 @@ function emitSignal(lines: string[], plan: SignalEmitPlan): void {
101
106
  return
102
107
  }
103
108
  if (plan.setter) {
104
- lines.push(` const [${plan.getter}, ${plan.setter}] = createSignal(${plan.initialValueExpr}${id})`)
109
+ lines.push(` const [${getterSlot}, ${plan.setter}] = createSignal(${plan.initialValueExpr}${id})`)
105
110
  } else {
106
111
  lines.push(` const [${plan.getter}] = createSignal(${plan.initialValueExpr}${id})`)
107
112
  }
@@ -494,6 +494,18 @@ export interface LoopChildReactiveAttr extends AttrMeta {
494
494
  childSlotId: string // bf slot ID of the element with reactive attr
495
495
  attrName: string // 'className', 'disabled', etc.
496
496
  expression: string // Expression that reads signals
497
+ /**
498
+ * The expression reads a `.map()` callback preamble local (#2447
499
+ * follow-up), so it is reactive by way of whatever the preamble read — the
500
+ * classifier cannot see through the declaration, and the local is not in
501
+ * scope in the update body until the preamble is re-run there.
502
+ *
503
+ * Two consequences, both handled by the emitter rather than here: the row
504
+ * effect runs `mapPreambleWrapped` BEFORE the attr writes, and the lazy row
505
+ * graph refuses the loop (`lazyRowEligibility`'s `readsPreamble` gate), so
506
+ * such a row keeps the eager per-row effect.
507
+ */
508
+ readsPreamble?: boolean
497
509
  }
498
510
 
499
511
  export interface LoopChildReactiveText {