@barefootjs/jsx 0.33.2 → 0.33.3

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 (47) hide show
  1. package/dist/analyzer.d.ts +17 -0
  2. package/dist/analyzer.d.ts.map +1 -1
  3. package/dist/compiler.d.ts +21 -5
  4. package/dist/compiler.d.ts.map +1 -1
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +707 -431
  8. package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
  9. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts.map +1 -1
  10. package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
  11. package/dist/ir-to-client-js/imports.d.ts +60 -2
  12. package/dist/ir-to-client-js/imports.d.ts.map +1 -1
  13. package/dist/ir-to-client-js/prop-handling.d.ts +4 -7
  14. package/dist/ir-to-client-js/prop-handling.d.ts.map +1 -1
  15. package/dist/ir-to-client-js/utils.d.ts +26 -2
  16. package/dist/ir-to-client-js/utils.d.ts.map +1 -1
  17. package/dist/jsx-to-ir.d.ts.map +1 -1
  18. package/dist/props-binding.d.ts +35 -0
  19. package/dist/props-binding.d.ts.map +1 -1
  20. package/dist/types.d.ts +50 -13
  21. package/dist/types.d.ts.map +1 -1
  22. package/package.json +2 -2
  23. package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +145 -97
  24. package/src/__tests__/child-component-ref-not-mirrored.test.ts +90 -0
  25. package/src/__tests__/ir-to-client-js/imports.test.ts +107 -0
  26. package/src/__tests__/ir-to-client-js/merge-compiled-client-js-imports.test.ts +138 -0
  27. package/src/__tests__/issue-2754-rest-spread-needs-slot.test.ts +85 -0
  28. package/src/__tests__/issue-2756-loop-row-honors-client-only.test.ts +173 -0
  29. package/src/__tests__/merge-template-imports.test.ts +41 -1
  30. package/src/__tests__/multi-component-shared-default-import.test.ts +55 -0
  31. package/src/__tests__/root-key-relay.test.ts +170 -0
  32. package/src/__tests__/signal-getter-not-called.test.ts +149 -0
  33. package/src/__tests__/state-only-file-default-import.test.ts +47 -0
  34. package/src/analyzer.ts +36 -0
  35. package/src/compiler.ts +94 -104
  36. package/src/index.ts +1 -1
  37. package/src/ir-to-client-js/collect-elements.ts +27 -5
  38. package/src/ir-to-client-js/control-flow/stringify/inner-loop.ts +6 -2
  39. package/src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts +5 -2
  40. package/src/ir-to-client-js/html-template.ts +122 -12
  41. package/src/ir-to-client-js/imports.ts +178 -5
  42. package/src/ir-to-client-js/index.ts +5 -0
  43. package/src/ir-to-client-js/prop-handling.ts +6 -17
  44. package/src/ir-to-client-js/utils.ts +30 -2
  45. package/src/jsx-to-ir.ts +480 -52
  46. package/src/props-binding.ts +51 -0
  47. package/src/types.ts +47 -13
@@ -68,3 +68,54 @@ export function buildPropAliasMap(params: readonly ParamInfo[]): Map<string, str
68
68
  }
69
69
  return map
70
70
  }
71
+
72
+ /**
73
+ * The two component bindings that forward the caller's leftover props:
74
+ * the destructured `...rest` binding and a whole undestructured `(props)`
75
+ * parameter. Both phases carry these names — the analyzer context in
76
+ * Phase 1, the client-JS context in Phase 2 — so the resolver below takes
77
+ * them as data rather than binding to either context type.
78
+ */
79
+ export interface RestSpreadBindings {
80
+ restPropsName: string | null
81
+ propsObjectName: string | null
82
+ }
83
+
84
+ /**
85
+ * Which of `bindings` the name `name` ultimately reaches, walking through
86
+ * any bare `const x__alias = <name>` hop recorded in `constantValues`
87
+ * (name → initializer text). `'rest'` for the destructured rest binding,
88
+ * `'props'` for the whole props object, `null` for anything else.
89
+ *
90
+ * The single definition of "this `{...spread}` forwards the caller's
91
+ * leftover props", shared by the two phases that must agree on it: Phase 1
92
+ * decides whether the host element gets a slot id (#2754 — without one the
93
+ * spread has no client-side patch point at all, so a pure CSR mount drops
94
+ * every caller-supplied attribute), and Phase 2 decides whether to route
95
+ * the spread to `applyRestAttrs` and filter it out of the template's
96
+ * `spreadAttrs({...})` merge. Two copies of the rule would let an element
97
+ * qualify for one and not the other, which is exactly the silent-drop
98
+ * shape #2754 reports.
99
+ *
100
+ * Walks hop by hop (not a precomputed set) so a multi-hop alias
101
+ * (`const p2 = props; const p3 = p2`) resolves through every link;
102
+ * `visited` guards a constant cycle (`const a = b; const b = a`). A
103
+ * constant whose value isn't a bare identifier on the chain (a real
104
+ * computed object) stops the walk, so a genuinely different spread
105
+ * expression is never mistaken for the rest object.
106
+ */
107
+ export function resolveRestSpreadOriginCore(
108
+ bindings: RestSpreadBindings,
109
+ constantValues: ReadonlyMap<string, string | undefined>,
110
+ name: string,
111
+ ): 'rest' | 'props' | null {
112
+ const visited = new Set<string>()
113
+ let current: string | undefined = name.trim()
114
+ while (current !== undefined && !visited.has(current)) {
115
+ if (bindings.restPropsName && current === bindings.restPropsName) return 'rest'
116
+ if (bindings.propsObjectName && current === bindings.propsObjectName) return 'props'
117
+ visited.add(current)
118
+ current = constantValues.get(current)?.trim()
119
+ }
120
+ return null
121
+ }
package/src/types.ts CHANGED
@@ -348,19 +348,43 @@ export interface IRElement {
348
348
  slotId: string | null
349
349
  needsScope: boolean
350
350
  /**
351
- * Set on the first ELEMENT among a `needsScopeComment` fragment root's own
352
- * top-level children (#2732) the fragment's five hydration markers
353
- * (`bf-s`/`bf-h`/`bf-m`/`bf-r`/`bf-p`) move to the wrapping
354
- * `<!--bf-scope:...-->` comment instead of an element attribute, but
355
- * `data-key` has to stay on an element because the client runtime's
356
- * `mapArray` adopt loop reads it as a DOM attribute
357
- * (`primaryEl.dataset.key`, map-array.ts). "First element, not first
358
- * node" mirrors the CSR runtime's own resolution of the identical
359
- * ambiguity (`component.ts`'s `roots.find(isElement)`, #2735) rather than
360
- * inventing a second answer. Always `undefined` when `needsScope` is
361
- * true the two are mutually exclusive ways of carrying the same key.
362
- */
363
- carriesDataKey?: boolean
351
+ * The row-key attribute this element carries, resolved ONCE here instead
352
+ * of independently by 16 emit sites (#2753 root-cause: the client
353
+ * runtime's `mapArray` stamping an index key onto unkeyed rows SSR never
354
+ * emits, and disagreeing with SSR's depth-suffixed name on nested rows —
355
+ * see that issue for the two measured shapes). Replaces the `carriesDataKey`
356
+ * boolean (#2732/#2744): a boolean was a degenerate encoding of the same
357
+ * decision "does this element get a key attribute, and what is it
358
+ * called" that still left the VALUE and the depth-derived NAME to be
359
+ * re-derived by every adapter (a mutable stack in Hono, a
360
+ * `currentLoopKeyDepth` field in every DSL adapter) and by the client
361
+ * runtime. Absent means "this element carries no key attribute" the
362
+ * correct, final answer for an unkeyed loop's row, not a hint adapters
363
+ * still have to gate further.
364
+ *
365
+ * Two disjoint sources, both resolved at IR-build time
366
+ * (`resolveKeyAttrs`/loop construction in `jsx-to-ir.ts`):
367
+ *
368
+ * - `value` present: this element is DIRECTLY a `.map()` row root
369
+ * compiled inline in this same component (`IRLoop.key`/`.depth`
370
+ * already resolved the expression and nesting depth) — adapters lower
371
+ * `value` exactly like any other attribute-value expression.
372
+ * - `value` absent: this element is one of the component's own possible
373
+ * render roots (a plain element/if-statement-branch root, or — when
374
+ * `needsScope` is false because hydration markers moved to a wrapping
375
+ * `<!--bf-scope:...-->` comment (#2732) — the first eligible element
376
+ * of a `needsScopeComment` fragment root) that RELAYS a key an
377
+ * external caller supplies at runtime, when THIS component is itself
378
+ * invoked as a caller's keyed loop row (the `data-key` prop / Rust
379
+ * `bf.data_key` field, or client `createComponent`'s `key` argument).
380
+ * There is no local expression to lower here, only a name.
381
+ *
382
+ * "First element, not first node" (for the fragment-root shape) mirrors
383
+ * the CSR runtime's own resolution of the identical ambiguity
384
+ * (`component.ts`'s `roots.find(isElement)`, #2735) rather than inventing
385
+ * a second answer.
386
+ */
387
+ keyAttr?: { name: string; value?: string }
364
388
  /**
365
389
  * Page-lifecycle boundary id for an element lowered from `<Region>`
366
390
  * (spec/router.md). Set only on region host elements; adapters emit it as
@@ -382,6 +406,16 @@ export interface IRExpression {
382
406
  expr: string
383
407
  /** Pre-transformed expr with destructured prop refs rewritten to _p.xxx (for client JS templates). */
384
408
  templateExpr?: string
409
+ /**
410
+ * Marks a VALUE moved into element content by a lowering (today only
411
+ * `lowerFormControlValueSsr`'s `<textarea>`, #2765) — client builders
412
+ * must escape it, unlike the normal pre-rendered-HTML `${...}` case.
413
+ *
414
+ * Not read via `templateExpr` instead: that field also rebinds to
415
+ * `_p.xxx`, which drops a destructured prop's `?? {}` default when the
416
+ * builder's output lives in init scope (`client-js-generation.test.ts`).
417
+ */
418
+ escapeInClientTemplate?: boolean
385
419
  /**
386
420
  * Structured parse of `expr` (`parseExpression(expr.trim())`), attached once
387
421
  * during IR construction so SSR adapters emit from the tree instead of each