@barefootjs/jsx 0.35.0 → 0.35.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.
- package/dist/analyzer-context.d.ts.map +1 -1
- package/dist/index.js +731 -691
- package/dist/ir-to-client-js/collect-elements.d.ts +22 -2
- package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-composite-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-inner-loop.d.ts +14 -2
- package/dist/ir-to-client-js/control-flow/plan/build-inner-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-loop-child-arm.d.ts +6 -1
- package/dist/ir-to-client-js/control-flow/plan/build-loop-child-arm.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-plain-row.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts +3 -2
- package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/inner-loop.d.ts +24 -44
- package/dist/ir-to-client-js/control-flow/plan/inner-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts +14 -0
- package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/inner-loop.d.ts +6 -14
- package/dist/ir-to-client-js/control-flow/stringify/inner-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts +1 -3
- package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts +19 -3
- package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts +5 -1
- package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts.map +1 -1
- package/dist/ir-to-client-js/emit-reactive.d.ts +54 -0
- package/dist/ir-to-client-js/emit-reactive.d.ts.map +1 -1
- package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
- package/dist/ir-to-client-js/reactivity.d.ts +62 -18
- package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
- package/dist/ir-to-client-js/types.d.ts +18 -2
- package/dist/ir-to-client-js/types.d.ts.map +1 -1
- package/dist/reactivity-checker.d.ts +18 -0
- package/dist/reactivity-checker.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +128 -10
- package/src/__tests__/binding-scope-ratchet.test.ts +26 -3
- package/src/__tests__/child-prop-fallback-wrap.test.ts +4 -2
- package/src/__tests__/compiler-stress-1244.test.ts +8 -8
- package/src/__tests__/issue-2868-cond-arm-tag-collision.test.ts +185 -0
- package/src/__tests__/issue-2869-attr-dedup-guard.test.ts +141 -0
- package/src/__tests__/loop-index-reactivity.test.ts +208 -0
- package/src/__tests__/map-body-brand-checker.test.ts +176 -0
- package/src/__tests__/nested-loop-index-param.test.ts +27 -5
- package/src/__tests__/nested-loop-non-outer-reactivity.test.ts +177 -0
- package/src/__tests__/nested-loop-plain.test.ts +39 -25
- package/src/__tests__/nested-loop-reactive-attrs.test.ts +12 -4
- package/src/__tests__/preamble-branch-arm-rerun.test.ts +135 -0
- package/src/__tests__/style-css-var-reactive.test.ts +4 -1
- package/src/analyzer-context.ts +12 -10
- package/src/ir-to-client-js/collect-elements.ts +110 -32
- package/src/ir-to-client-js/control-flow/plan/build-composite-loop.ts +2 -0
- package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +28 -51
- package/src/ir-to-client-js/control-flow/plan/build-lazy-row.ts +7 -2
- package/src/ir-to-client-js/control-flow/plan/build-loop-child-arm.ts +33 -6
- package/src/ir-to-client-js/control-flow/plan/build-plain-row.ts +7 -1
- package/src/ir-to-client-js/control-flow/plan/build-reactive-effects.ts +18 -6
- package/src/ir-to-client-js/control-flow/plan/inner-loop.ts +24 -45
- package/src/ir-to-client-js/control-flow/plan/loop-child-arm.ts +14 -0
- package/src/ir-to-client-js/control-flow/stringify/inner-loop.ts +20 -66
- package/src/ir-to-client-js/control-flow/stringify/insert.ts +4 -2
- package/src/ir-to-client-js/control-flow/stringify/lazy-row.ts +12 -15
- package/src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts +53 -11
- package/src/ir-to-client-js/control-flow/stringify/loop.ts +4 -2
- package/src/ir-to-client-js/control-flow/stringify/reactive-effects.ts +29 -18
- package/src/ir-to-client-js/emit-reactive.ts +88 -5
- package/src/ir-to-client-js/html-template.ts +9 -1
- package/src/ir-to-client-js/reactivity.ts +73 -27
- package/src/ir-to-client-js/types.ts +19 -3
- package/src/jsx-to-ir.ts +21 -4
- package/src/reactivity-checker.ts +60 -0
|
@@ -218,9 +218,14 @@ export function decideLazyRow(args: BuildLazyRowArgs): {
|
|
|
218
218
|
const condFacts: LazyConditionalFacts[] = []
|
|
219
219
|
let conditionalRefusal: string | null = null
|
|
220
220
|
for (const cond of rawConditionals) {
|
|
221
|
+
// whenTrueHtml/whenFalseHtml are already loop-param-wrapped at IR
|
|
222
|
+
// render time (`irToHtmlTemplate`'s `loopParams`, collect-elements.ts)
|
|
223
|
+
// — do NOT re-wrap the rendered HTML string here (#2868: a
|
|
224
|
+
// word-boundary regex over assembled markup can match a bare tag name
|
|
225
|
+
// colliding with the param/index identifier, e.g. `<i>` -> `<i()>`).
|
|
221
226
|
const verdict = analyzeLazyConditional(cond, {
|
|
222
|
-
whenTrueHtml: addCondAttrToTemplate(
|
|
223
|
-
whenFalseHtml: addCondAttrToTemplate(
|
|
227
|
+
whenTrueHtml: addCondAttrToTemplate(cond.whenTrueHtml, cond.slotId),
|
|
228
|
+
whenFalseHtml: addCondAttrToTemplate(cond.whenFalseHtml, cond.slotId),
|
|
224
229
|
})
|
|
225
230
|
if (!verdict.lazySafe) { conditionalRefusal = verdict.reason; break }
|
|
226
231
|
condFacts.push(verdict.facts)
|
|
@@ -202,6 +202,8 @@ export interface BuildBranchInnerLoopsArgs {
|
|
|
202
202
|
outerLoopParam: string
|
|
203
203
|
/** Outer loop param destructuring metadata. */
|
|
204
204
|
outerLoopParamBindings?: readonly LoopParamBinding[]
|
|
205
|
+
/** Outer loop's index param name, when present (#2861). */
|
|
206
|
+
outerLoopIndex?: string | null
|
|
205
207
|
/**
|
|
206
208
|
* Outer-wrap closure — defaults to wrapping with `outerLoopParam`. Overridden
|
|
207
209
|
* by `emitNestedLoopChildConditionals` recursion (which threads its own wrap).
|
|
@@ -225,6 +227,7 @@ export function buildBranchInnerLoopsPlan(
|
|
|
225
227
|
condSlotId,
|
|
226
228
|
outerLoopParam,
|
|
227
229
|
outerLoopParamBindings,
|
|
230
|
+
outerLoopIndex,
|
|
228
231
|
wrapOuter,
|
|
229
232
|
} = args
|
|
230
233
|
if (!innerLoops || innerLoops.length === 0) return []
|
|
@@ -232,7 +235,12 @@ export function buildBranchInnerLoopsPlan(
|
|
|
232
235
|
const plan: BranchInnerLoop[] = []
|
|
233
236
|
for (let i = 0; i < innerLoops.length; i++) {
|
|
234
237
|
const inner = innerLoops[i]
|
|
235
|
-
|
|
238
|
+
// #2865: an inner loop inside a conditional arm used to be dropped
|
|
239
|
+
// entirely (no `mapArray`, no hydration wiring at all) whenever its
|
|
240
|
+
// array didn't reference the outer loop's item — the same unsound
|
|
241
|
+
// gate `build-inner-loop.ts` used, just answered by skipping instead
|
|
242
|
+
// of falling back to a weaker static emission. Always build the plan.
|
|
243
|
+
if (!inner.template) continue
|
|
236
244
|
|
|
237
245
|
const wrapInner = (expr: string) => wrapLoopParamAsAccessor(expr, inner.param, inner.paramBindings, inner.index)
|
|
238
246
|
const wrapBoth = (expr: string) => wrapLoopParamAsAccessor(wrapOuter(expr), inner.param, inner.paramBindings, inner.index)
|
|
@@ -327,11 +335,13 @@ export function buildBranchInnerLoopsPlan(
|
|
|
327
335
|
wrap: wrapBoth,
|
|
328
336
|
loopParam: inner.param,
|
|
329
337
|
loopParamBindings: inner.paramBindings,
|
|
338
|
+
loopIndex: inner.index,
|
|
330
339
|
}),
|
|
331
340
|
innerLoopParam: inner.param,
|
|
332
341
|
innerLoopParamBindings: inner.paramBindings,
|
|
333
342
|
outerLoopParam,
|
|
334
343
|
outerLoopParamBindings,
|
|
344
|
+
outerLoopIndex,
|
|
335
345
|
})
|
|
336
346
|
}
|
|
337
347
|
return plan
|
|
@@ -352,6 +362,8 @@ export interface BuildLoopChildConditionalsArgs {
|
|
|
352
362
|
loopParam: string
|
|
353
363
|
/** Loop param destructuring metadata. */
|
|
354
364
|
loopParamBindings?: readonly LoopParamBinding[]
|
|
365
|
+
/** Loop's index param name, when it declares one (#2861) — same role as `loopParam` above. */
|
|
366
|
+
loopIndex?: string | null
|
|
355
367
|
}
|
|
356
368
|
|
|
357
369
|
/**
|
|
@@ -360,7 +372,8 @@ export interface BuildLoopChildConditionalsArgs {
|
|
|
360
372
|
* recursion. Each conditional pre-builds:
|
|
361
373
|
*
|
|
362
374
|
* - the wrapped condition expression
|
|
363
|
-
* - the
|
|
375
|
+
* - the addCondAttr'd whenTrue / whenFalse template HTML (already
|
|
376
|
+
* loop-param-wrapped at IR render time, #2868)
|
|
364
377
|
* - per-arm `LoopChildArmPlan`: events / child components / inner
|
|
365
378
|
* loops / nested conditionals (recursion)
|
|
366
379
|
*
|
|
@@ -370,7 +383,7 @@ export interface BuildLoopChildConditionalsArgs {
|
|
|
370
383
|
export function buildLoopChildConditionalsPlan(
|
|
371
384
|
args: BuildLoopChildConditionalsArgs,
|
|
372
385
|
): LoopChildConditionalPlan[] {
|
|
373
|
-
const { conditionals, scopeVar, wrap, loopParam, loopParamBindings } = args
|
|
386
|
+
const { conditionals, scopeVar, wrap, loopParam, loopParamBindings, loopIndex } = args
|
|
374
387
|
if (!conditionals || conditionals.length === 0) return []
|
|
375
388
|
|
|
376
389
|
const plans: LoopChildConditionalPlan[] = []
|
|
@@ -379,13 +392,20 @@ export function buildLoopChildConditionalsPlan(
|
|
|
379
392
|
slotId: cond.slotId,
|
|
380
393
|
scopeVar,
|
|
381
394
|
wrappedCondition: wrap(cond.condition),
|
|
382
|
-
|
|
383
|
-
|
|
395
|
+
// whenTrueHtml/whenFalseHtml are already loop-param-wrapped for the
|
|
396
|
+
// full ancestor chain at IR render time (`irToHtmlTemplate`'s
|
|
397
|
+
// `loopParams`, collect-elements.ts) — do NOT re-wrap the rendered
|
|
398
|
+
// HTML string here (#2868: a word-boundary regex over assembled
|
|
399
|
+
// markup can match a bare tag name colliding with the param/index
|
|
400
|
+
// identifier, e.g. `<i>` -> `<i()>`).
|
|
401
|
+
whenTrueTemplateHtml: addCondAttrToTemplate(cond.whenTrueHtml, cond.slotId),
|
|
402
|
+
whenFalseTemplateHtml: addCondAttrToTemplate(cond.whenFalseHtml, cond.slotId),
|
|
384
403
|
whenTrueArm: buildLoopChildArmPlan({
|
|
385
404
|
branch: cond.whenTrue,
|
|
386
405
|
wrap,
|
|
387
406
|
loopParam,
|
|
388
407
|
loopParamBindings,
|
|
408
|
+
loopIndex,
|
|
389
409
|
condId: cond.slotId,
|
|
390
410
|
}),
|
|
391
411
|
whenFalseArm: buildLoopChildArmPlan({
|
|
@@ -393,8 +413,10 @@ export function buildLoopChildConditionalsPlan(
|
|
|
393
413
|
wrap,
|
|
394
414
|
loopParam,
|
|
395
415
|
loopParamBindings,
|
|
416
|
+
loopIndex,
|
|
396
417
|
condId: cond.slotId,
|
|
397
418
|
}),
|
|
419
|
+
...(cond.readsPreamble && { readsPreamble: true }),
|
|
398
420
|
})
|
|
399
421
|
}
|
|
400
422
|
return plans
|
|
@@ -429,6 +451,7 @@ export function buildArmAttrsPlan(
|
|
|
429
451
|
attrName: attr.attrName,
|
|
430
452
|
wrappedExpression: wrap(attr.expression),
|
|
431
453
|
meta: pickAttrMeta(attr),
|
|
454
|
+
...(attr.readsPreamble && { readsPreamble: true }),
|
|
432
455
|
})),
|
|
433
456
|
})
|
|
434
457
|
}
|
|
@@ -455,12 +478,14 @@ interface BuildLoopChildArmArgs {
|
|
|
455
478
|
wrap: (expr: string) => string
|
|
456
479
|
loopParam: string
|
|
457
480
|
loopParamBindings?: readonly LoopParamBinding[]
|
|
481
|
+
/** Loop's index param name, when it declares one (#2861). */
|
|
482
|
+
loopIndex?: string | null
|
|
458
483
|
/** The enclosing conditional's own slot id — threaded to `buildBranchInnerLoopsPlan`'s `condSlotId` (#2705). */
|
|
459
484
|
condId: string
|
|
460
485
|
}
|
|
461
486
|
|
|
462
487
|
function buildLoopChildArmPlan(args: BuildLoopChildArmArgs): LoopChildArmPlan {
|
|
463
|
-
const { branch, wrap, loopParam, loopParamBindings, condId } = args
|
|
488
|
+
const { branch, wrap, loopParam, loopParamBindings, loopIndex, condId } = args
|
|
464
489
|
return {
|
|
465
490
|
events: buildBranchEventBindingsPlan({
|
|
466
491
|
events: branch.events,
|
|
@@ -476,6 +501,7 @@ function buildLoopChildArmPlan(args: BuildLoopChildArmArgs): LoopChildArmPlan {
|
|
|
476
501
|
condSlotId: condId,
|
|
477
502
|
outerLoopParam: loopParam,
|
|
478
503
|
outerLoopParamBindings: loopParamBindings,
|
|
504
|
+
outerLoopIndex: loopIndex,
|
|
479
505
|
wrapOuter: wrap,
|
|
480
506
|
}),
|
|
481
507
|
nestedConditionals: buildLoopChildConditionalsPlan({
|
|
@@ -484,6 +510,7 @@ function buildLoopChildArmPlan(args: BuildLoopChildArmArgs): LoopChildArmPlan {
|
|
|
484
510
|
wrap,
|
|
485
511
|
loopParam,
|
|
486
512
|
loopParamBindings,
|
|
513
|
+
loopIndex,
|
|
487
514
|
}),
|
|
488
515
|
attrs: buildArmAttrsPlan(branch.reactiveAttrs, wrap),
|
|
489
516
|
texts: buildArmTextsPlan(branch.reactiveTexts, wrap),
|
|
@@ -93,8 +93,14 @@ export function buildPlainRowCore(inputs: PlainRowInputs): PlainRowCore {
|
|
|
93
93
|
const mapPreambleWrappedFinal = !lazyRow && loop.index
|
|
94
94
|
? wrapIndexParamAsAccessor(mapPreambleWrapped, loop.index)
|
|
95
95
|
: mapPreambleWrapped
|
|
96
|
+
// `loop.templateIndexed` is a second structured render (index wrapped at
|
|
97
|
+
// IR time, `collect-elements.ts`) rather than a post-hoc regex pass over
|
|
98
|
+
// `loop.template` — a word-boundary regex over assembled HTML can match a
|
|
99
|
+
// bare tag name colliding with the index identifier (`<i>` -> `<i()>`,
|
|
100
|
+
// #2868). Falls back to `loop.template` for the rare shape that doesn't
|
|
101
|
+
// populate it (e.g. a flatMap projection loop).
|
|
96
102
|
const templateFinal = !lazyRow && loop.index
|
|
97
|
-
?
|
|
103
|
+
? (loop.templateIndexed ?? loop.template)
|
|
98
104
|
: loop.template
|
|
99
105
|
|
|
100
106
|
return {
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
* from `LoopChildBranchSummary.reactiveAttrs` / `.reactiveTexts` —
|
|
13
13
|
* collected per-branch, recursively, so a doubly-nested conditional's
|
|
14
14
|
* arm carries its own bindings instead of an outer scope reaching in.
|
|
15
|
-
* 4. Apply `addCondAttrToTemplate` to
|
|
16
|
-
*
|
|
15
|
+
* 4. Apply `addCondAttrToTemplate` to each branch's HTML (already
|
|
16
|
+
* loop-param-wrapped at IR render time, #2868) so the stringifier
|
|
17
|
+
* emits a ready-to-interpolate template literal.
|
|
17
18
|
* 5. Recurse into the per-arm sub-plans via `LoopChildArmPlan` —
|
|
18
19
|
* events, child component inits, inner loops, nested conditionals,
|
|
19
20
|
* attrs, texts. No legacy passthrough remains.
|
|
@@ -110,10 +111,17 @@ export function buildReactiveEffectsPlan(
|
|
|
110
111
|
conditionalPlans.push({
|
|
111
112
|
slotId: cond.slotId,
|
|
112
113
|
wrappedCondition: wrap(cond.condition),
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
// whenTrueHtml/whenFalseHtml are already loop-param-wrapped at IR
|
|
115
|
+
// render time (`irToHtmlTemplate`'s `loopParams`, collect-elements.ts)
|
|
116
|
+
// — do NOT re-wrap the rendered HTML string here. A word-boundary
|
|
117
|
+
// regex over already-assembled markup matches a bare tag name that
|
|
118
|
+
// collides with the param/index identifier (`<i>` -> `<i()>`) just
|
|
119
|
+
// as readily as a real reference, since `<`/`>` are non-word
|
|
120
|
+
// characters (#2868).
|
|
121
|
+
whenTrueTemplateHtml: addCondAttrToTemplate(cond.whenTrueHtml, cond.slotId),
|
|
122
|
+
whenFalseTemplateHtml: addCondAttrToTemplate(cond.whenFalseHtml, cond.slotId),
|
|
123
|
+
whenTrueArm: buildOuterArm(cond.whenTrue, wrap, loopParam, loopParamBindings, loopIndex, cond.slotId, profileComponentName),
|
|
124
|
+
whenFalseArm: buildOuterArm(cond.whenFalse, wrap, loopParam, loopParamBindings, loopIndex, cond.slotId, profileComponentName),
|
|
117
125
|
...(cond.readsPreamble && { readsPreamble: true }),
|
|
118
126
|
})
|
|
119
127
|
}
|
|
@@ -132,6 +140,8 @@ function buildOuterArm(
|
|
|
132
140
|
wrap: (expr: string) => string,
|
|
133
141
|
loopParam: string,
|
|
134
142
|
loopParamBindings: readonly LoopParamBinding[] | undefined,
|
|
143
|
+
/** This loop's index param name, when it declares one (#2861). */
|
|
144
|
+
loopIndex: string | null | undefined,
|
|
135
145
|
/** The conditional's own slot id — threaded to `buildBranchInnerLoopsPlan`'s `condSlotId` (#2705). */
|
|
136
146
|
condSlotId: string,
|
|
137
147
|
profileComponentName?: string,
|
|
@@ -152,6 +162,7 @@ function buildOuterArm(
|
|
|
152
162
|
condSlotId,
|
|
153
163
|
outerLoopParam: loopParam,
|
|
154
164
|
outerLoopParamBindings: loopParamBindings,
|
|
165
|
+
outerLoopIndex: loopIndex,
|
|
155
166
|
wrapOuter: wrap,
|
|
156
167
|
}),
|
|
157
168
|
nestedConditionals: buildLoopChildConditionalsPlan({
|
|
@@ -160,6 +171,7 @@ function buildOuterArm(
|
|
|
160
171
|
wrap,
|
|
161
172
|
loopParam,
|
|
162
173
|
loopParamBindings,
|
|
174
|
+
loopIndex,
|
|
163
175
|
}),
|
|
164
176
|
attrs: buildArmAttrsPlan(branch.reactiveAttrs, wrap),
|
|
165
177
|
texts: buildArmTextsPlan(branch.reactiveTexts, wrap),
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plan types for inner loops emitted inside a composite loop's renderItem
|
|
3
|
-
* body. Each `InnerLoopPlan` is one level (= one `mapArray`
|
|
4
|
-
*
|
|
5
|
-
* deeper nestings.
|
|
3
|
+
* body. Each `InnerLoopPlan` is one level (= one `mapArray`); `childLevels`
|
|
4
|
+
* recurses for deeper nestings.
|
|
6
5
|
*
|
|
7
6
|
* Replaces the legacy `emitInnerLoopSetup` + `DepthLevel` walk. The
|
|
8
|
-
* builder pre-resolves every per-level decision (
|
|
9
|
-
*
|
|
7
|
+
* builder pre-resolves every per-level decision (container query, wrapped
|
|
8
|
+
* array / template / key, narrowed-outer-param children) so the
|
|
10
9
|
* stringifier is a deterministic walk.
|
|
10
|
+
*
|
|
11
|
+
* Every level gets the reactive `mapArray` shape (#2865) — there used to
|
|
12
|
+
* be a second "static forEach, setup-only" shape for a nested loop whose
|
|
13
|
+
* array didn't reference the outer item, but that gate answered the wrong
|
|
14
|
+
* question (it never checked whether the array was reactive at all, only
|
|
15
|
+
* whether it mentioned the outer item's name) and silently froze any other
|
|
16
|
+
* reactivity in the row. See `build-inner-loop.ts`'s module docstring.
|
|
11
17
|
*/
|
|
12
18
|
|
|
13
19
|
import type { LoopChildEvent } from '../../types.ts'
|
|
@@ -64,20 +70,9 @@ export interface InnerLoopReactiveAttr {
|
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
/**
|
|
67
|
-
* One inner loop level inside a composite renderItem
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* - `reactive`: full `mapArray(...)` with renderItem (template clone,
|
|
72
|
-
* key attr, components/events with inner-wrapped props, reactive text
|
|
73
|
-
* effects, recursive childLevels).
|
|
74
|
-
* - `static`: `forEach(...)` for setup-only (children are rendered
|
|
75
|
-
* into the SSR HTML); components/events use the raw param, no
|
|
76
|
-
* wrap, no template clone.
|
|
77
|
-
*
|
|
78
|
-
* Choosing the mode requires checking whether `inner.array` references
|
|
79
|
-
* the outer loop param at *this* level (legacy O-8 fix). The builder
|
|
80
|
-
* does that check; the stringifier just dispatches on `mode`.
|
|
73
|
+
* One inner loop level inside a composite renderItem: a full `mapArray(...)`
|
|
74
|
+
* with renderItem (template clone, key attr, components/events with
|
|
75
|
+
* inner-wrapped props, reactive text/attr effects, recursive childLevels).
|
|
81
76
|
*/
|
|
82
77
|
export interface InnerLoopPlan {
|
|
83
78
|
/** Unique suffix used in `__ic` / `__innerEl` / `__innerIdx` var names. */
|
|
@@ -101,21 +96,25 @@ export interface InnerLoopPlan {
|
|
|
101
96
|
* `// Initialize <array> loop components and events` comments.
|
|
102
97
|
*/
|
|
103
98
|
arraySrc: string
|
|
104
|
-
/** Inner loop parameter identifier
|
|
99
|
+
/** Inner loop parameter identifier. */
|
|
105
100
|
param: string
|
|
106
101
|
/** Depth used by `keyAttrName(...)`. Same as the IR's `inner.depth`. */
|
|
107
102
|
keyDepth: number
|
|
108
|
-
/**
|
|
109
|
-
|
|
110
|
-
* narrowed and prevents accidental cross-talk.
|
|
111
|
-
*/
|
|
112
|
-
emit: InnerLoopReactiveEmit | InnerLoopStaticEmit
|
|
103
|
+
/** Emission data for this level's `mapArray` renderItem. */
|
|
104
|
+
emit: InnerLoopReactiveEmit
|
|
113
105
|
/** Recursive child levels — narrowed-outer-param at build time. */
|
|
114
106
|
childLevels: readonly InnerLoopPlan[]
|
|
115
107
|
/** Outer loop param threaded into emitComponentAndEventSetup. */
|
|
116
108
|
outerLoopParam: string | undefined
|
|
117
109
|
/** Outer loop param destructuring metadata. */
|
|
118
110
|
outerLoopParamBindings?: readonly LoopParamBinding[]
|
|
111
|
+
/**
|
|
112
|
+
* Outer loop's index param name, when present (#2861) — threaded into
|
|
113
|
+
* `emitComponentAndEventSetup` so a component prop / event handler in
|
|
114
|
+
* this row that closes over the OUTER loop's index reads it through the
|
|
115
|
+
* live accessor instead of a value frozen at row-creation time.
|
|
116
|
+
*/
|
|
117
|
+
outerLoopIndex?: string | null
|
|
119
118
|
}
|
|
120
119
|
|
|
121
120
|
export interface InnerLoopReactiveEmit {
|
|
@@ -164,24 +163,4 @@ export interface InnerLoopReactiveEmit {
|
|
|
164
163
|
childRefs: readonly LoopChildRefBinding[]
|
|
165
164
|
}
|
|
166
165
|
|
|
167
|
-
export interface InnerLoopStaticEmit {
|
|
168
|
-
mode: 'static'
|
|
169
|
-
/** Raw key expression (used as-is in setAttribute) — null when no key. */
|
|
170
|
-
rawKey: string | null
|
|
171
|
-
/**
|
|
172
|
-
* Body-entry statements (see `PreludeStatements`) emitted at the top of
|
|
173
|
-
* the static `forEach(...)` body, after the existence guard. Holds the
|
|
174
|
-
* inner `.map()` callback preamble locals (#1064). Emitted unwrapped
|
|
175
|
-
* because the forEach param is the literal item, not a signal accessor —
|
|
176
|
-
* no accessor rewrite is needed.
|
|
177
|
-
*/
|
|
178
|
-
preludeStatements: PreludeStatements
|
|
179
|
-
/** Raw components (no inner-wrap; the static body has no signal accessor). */
|
|
180
|
-
components: readonly IRLoopChildComponent[]
|
|
181
|
-
/** Raw events (no inner-wrap). */
|
|
182
|
-
events: readonly LoopChildEvent[]
|
|
183
|
-
/** Imperative ref callbacks (unwrapped — static body has no signal accessor). */
|
|
184
|
-
childRefs: readonly LoopChildRefBinding[]
|
|
185
|
-
}
|
|
186
|
-
|
|
187
166
|
export type InnerLoopsPlan = readonly InnerLoopPlan[]
|
|
@@ -146,6 +146,13 @@ export interface BranchInnerLoop {
|
|
|
146
146
|
outerLoopParam: string
|
|
147
147
|
/** Outer loop param destructuring metadata. */
|
|
148
148
|
outerLoopParamBindings?: readonly import('../../../types.ts').LoopParamBinding[]
|
|
149
|
+
/**
|
|
150
|
+
* Outer loop's index param name, when present (#2861) — threaded into
|
|
151
|
+
* `emitComponentAndEventSetup`'s own outer-wrap pass alongside
|
|
152
|
+
* `outerLoopParam`/`outerLoopParamBindings`, mirroring `InnerLoopPlan`'s
|
|
153
|
+
* field of the same name.
|
|
154
|
+
*/
|
|
155
|
+
outerLoopIndex?: string | null
|
|
149
156
|
}
|
|
150
157
|
|
|
151
158
|
export type BranchInnerLoopsPlan = readonly BranchInnerLoop[]
|
|
@@ -206,4 +213,11 @@ export interface LoopChildConditionalPlan {
|
|
|
206
213
|
whenFalseTemplateHtml: string
|
|
207
214
|
whenTrueArm: LoopChildArmPlan
|
|
208
215
|
whenFalseArm: LoopChildArmPlan
|
|
216
|
+
/**
|
|
217
|
+
* `wrappedCondition` reads a `.map()` callback preamble local (#2596,
|
|
218
|
+
* same field as `NestedConditionalPlan.readsPreamble` one level up) —
|
|
219
|
+
* the stringifier must run the row's preamble inside the getter passed
|
|
220
|
+
* to `insert()`; the local isn't otherwise in scope there.
|
|
221
|
+
*/
|
|
222
|
+
readsPreamble?: boolean
|
|
209
223
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Stringify `InnerLoopsPlan` into source lines.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* One emission shape (#2865 — the former second "static forEach,
|
|
5
|
+
* setup-only" shape for a nested loop whose array didn't reference the
|
|
6
|
+
* outer item was removed: it answered "does the array mention the outer
|
|
7
|
+
* item" instead of "is this reactive at all", so it silently froze any
|
|
8
|
+
* other reactivity — a signal, a memo, a prop, a preamble local — the
|
|
9
|
+
* inner array or its rows depended on):
|
|
6
10
|
*
|
|
7
11
|
* reactive — full mapArray:
|
|
8
12
|
* <indent>// Reactive inner loop: <arraySrc>
|
|
@@ -17,23 +21,11 @@
|
|
|
17
21
|
* <indent> per-item conditionals: insert() over __innerEl<uid> (#2706)
|
|
18
22
|
* <indent> return __innerEl<uid>
|
|
19
23
|
* <indent>}) }
|
|
20
|
-
*
|
|
21
|
-
* static — forEach setup-only:
|
|
22
|
-
* <indent>// Initialize <arraySrc> loop components and events
|
|
23
|
-
* <indent>{ const __ic<uid> = <containerExpr>
|
|
24
|
-
* <indent>if (__ic<uid> && <arrayExpr>) <arrayExpr>.forEach((<param>, __innerIdx<uid>) => {
|
|
25
|
-
* <indent> const __innerEl<uid> = __ic<uid>.children[__innerIdx<uid>]
|
|
26
|
-
* <indent> if (!__innerEl<uid>) return
|
|
27
|
-
* <indent> <preludeStatements*> // raw preamble (#1064)
|
|
28
|
-
* <indent> __innerEl<uid>.setAttribute('<keyAttr>', String(<rawKey>))?
|
|
29
|
-
* <indent> emitComponentAndEventSetup(...)
|
|
30
|
-
* <indent> recurse on childLevels
|
|
31
|
-
* <indent>}) }
|
|
32
24
|
*/
|
|
33
25
|
|
|
34
26
|
import { keyAttrName, mapArrayKeyArgs, profileBindingId, varSlotId } from '../../utils.ts'
|
|
35
27
|
import { emitComponentAndEventSetup } from '../shared.ts'
|
|
36
|
-
import {
|
|
28
|
+
import { emitDedupedAttrUpdate, DEDUP_STORE_DECL } from '../../emit-reactive.ts'
|
|
37
29
|
import { emitMultiRootTemplateCloneLines, namespaceWrapForTemplate } from './template-parse.ts'
|
|
38
30
|
import { emitLoopChildRefs } from './loop.ts'
|
|
39
31
|
import { claimPlanLiteral, claimWriterVarName, type ClaimSlotSpec } from './claim-plan.ts'
|
|
@@ -50,18 +42,13 @@ export function stringifyInnerLoops(
|
|
|
50
42
|
pc?: string,
|
|
51
43
|
): void {
|
|
52
44
|
for (const inner of plan) {
|
|
53
|
-
|
|
54
|
-
emitReactive(lines, inner, indent, pc)
|
|
55
|
-
} else {
|
|
56
|
-
emitStatic(lines, inner, indent, pc)
|
|
57
|
-
}
|
|
45
|
+
emitReactive(lines, inner, indent, pc)
|
|
58
46
|
}
|
|
59
47
|
}
|
|
60
48
|
|
|
61
49
|
function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string, pc: string | undefined): void {
|
|
62
50
|
const uid = inner.uidSuffix
|
|
63
51
|
const emit = inner.emit
|
|
64
|
-
if (emit.mode !== 'reactive') return // narrow
|
|
65
52
|
|
|
66
53
|
lines.push(`${indent}// Reactive inner loop: ${inner.arraySrc}`)
|
|
67
54
|
lines.push(`${indent}{ const __ic${uid} = ${inner.containerExpr}`)
|
|
@@ -108,6 +95,8 @@ function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string, pc:
|
|
|
108
95
|
[...emit.events],
|
|
109
96
|
inner.outerLoopParam,
|
|
110
97
|
inner.outerLoopParamBindings,
|
|
98
|
+
false,
|
|
99
|
+
inner.outerLoopIndex,
|
|
111
100
|
)
|
|
112
101
|
}
|
|
113
102
|
if (inner.childLevels.length > 0) {
|
|
@@ -132,11 +121,13 @@ function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string, pc:
|
|
|
132
121
|
lines.push(`${indent} createEffect(() => { ${writer}('${text.slotId}', String(${text.wrappedExpression})) }${profileBindingId(pc, text.slotId)})`)
|
|
133
122
|
}
|
|
134
123
|
}
|
|
124
|
+
if (emit.reactiveAttrs.length > 0) lines.push(`${indent} ${DEDUP_STORE_DECL}`)
|
|
125
|
+
let attrOrdinal = 0
|
|
135
126
|
for (const attr of emit.reactiveAttrs) {
|
|
136
127
|
const targetVar = `__ta_${attr.slotId.replace(/[^a-zA-Z0-9]/g, '_')}`
|
|
137
128
|
lines.push(`${indent} { const ${targetVar} = qsa(__innerEl${uid}, '[bf="${attr.slotId}"]')`)
|
|
138
129
|
lines.push(`${indent} if (${targetVar}) createEffect(() => {`)
|
|
139
|
-
for (const stmt of
|
|
130
|
+
for (const stmt of emitDedupedAttrUpdate(targetVar, attr.attrName, attr.wrappedExpression, attr.meta, attrOrdinal++)) {
|
|
140
131
|
lines.push(`${indent} ${stmt}`)
|
|
141
132
|
}
|
|
142
133
|
lines.push(`${indent} }${profileBindingId(pc, attr.slotId)}) }`)
|
|
@@ -146,7 +137,13 @@ function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string, pc:
|
|
|
146
137
|
// ternary. Mirrors `stringifyBranchInnerLoops`'s identical call for a
|
|
147
138
|
// branch-scoped inner loop's own conditionals.
|
|
148
139
|
if (emit.conditionals.length > 0) {
|
|
149
|
-
|
|
140
|
+
// This inner loop's own preamble (`preludeStatements` above) is not a
|
|
141
|
+
// single re-runnable string like the outer row's `mapPreambleWrapped`,
|
|
142
|
+
// so there is nothing correct to thread here — `readsPreamble &&
|
|
143
|
+
// mapPreambleWrapped`'s guard downstream makes this `undefined` a
|
|
144
|
+
// no-op, exactly as before this fix (inner-loop preambles are out of
|
|
145
|
+
// scope — see PR notes on #2596 follow-up).
|
|
146
|
+
stringifyLoopChildConditionals(lines, emit.conditionals, `${indent} `, pc, undefined)
|
|
150
147
|
}
|
|
151
148
|
// Imperative ref callbacks fire on every renderItem invocation, which
|
|
152
149
|
// means every mount: SSR hydration, initial CSR creation, and same-key
|
|
@@ -163,46 +160,3 @@ function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string, pc:
|
|
|
163
160
|
// instead of the default `data-key`.
|
|
164
161
|
lines.push(`${indent}}, '${inner.markerId}'${mapArrayKeyArgs(profileBindingId(pc, inner.slotId), !!emit.wrappedKey, inner.keyDepth)}) }`)
|
|
165
162
|
}
|
|
166
|
-
|
|
167
|
-
function emitStatic(lines: string[], inner: InnerLoopPlan, indent: string, pc: string | undefined): void {
|
|
168
|
-
const uid = inner.uidSuffix
|
|
169
|
-
const emit = inner.emit
|
|
170
|
-
if (emit.mode !== 'static') return
|
|
171
|
-
|
|
172
|
-
lines.push(`${indent}// Initialize ${inner.arraySrc} loop components and events`)
|
|
173
|
-
lines.push(`${indent}{ const __ic${uid} = ${inner.containerExpr}`)
|
|
174
|
-
// Guard: inner array may be undefined when inside a conditional branch.
|
|
175
|
-
lines.push(`${indent}if (__ic${uid} && ${inner.arrayExpr}) ${inner.arrayExpr}.forEach((${inner.param}, __innerIdx${uid}) => {`)
|
|
176
|
-
lines.push(`${indent} const __innerEl${uid} = __ic${uid}.children[__innerIdx${uid}]`)
|
|
177
|
-
lines.push(`${indent} if (!__innerEl${uid}) return`)
|
|
178
|
-
// Body-entry statements: inner-`.map()` preamble locals (raw, since the
|
|
179
|
-
// forEach param is the literal item — #1064). Emitted before component
|
|
180
|
-
// and event setup so their prop getters / handlers resolve the locals.
|
|
181
|
-
for (const stmt of emit.preludeStatements) {
|
|
182
|
-
lines.push(`${indent} ${stmt}`)
|
|
183
|
-
}
|
|
184
|
-
if (emit.rawKey) {
|
|
185
|
-
lines.push(`${indent} __innerEl${uid}.setAttribute('${keyAttrName(inner.keyDepth)}', String(${emit.rawKey}))`)
|
|
186
|
-
}
|
|
187
|
-
emitComponentAndEventSetup(
|
|
188
|
-
lines,
|
|
189
|
-
`${indent} `,
|
|
190
|
-
`__innerEl${uid}`,
|
|
191
|
-
[...emit.components],
|
|
192
|
-
[...emit.events],
|
|
193
|
-
inner.outerLoopParam,
|
|
194
|
-
inner.outerLoopParamBindings,
|
|
195
|
-
)
|
|
196
|
-
if (inner.childLevels.length > 0) {
|
|
197
|
-
stringifyInnerLoops(lines, inner.childLevels, `${indent} `, pc)
|
|
198
|
-
}
|
|
199
|
-
// Imperative ref callbacks for static inner loops — fire once per
|
|
200
|
-
// forEach iteration (#1244). Static arrays don't reactively re-iterate,
|
|
201
|
-
// so this is effectively a one-shot per item.
|
|
202
|
-
emitLoopChildRefs(lines, emit.childRefs, {
|
|
203
|
-
indent: `${indent} `,
|
|
204
|
-
elVar: `__innerEl${uid}`,
|
|
205
|
-
bodyIsMultiRoot: false,
|
|
206
|
-
})
|
|
207
|
-
lines.push(`${indent}}) }`)
|
|
208
|
-
}
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
import { emitRefCall, varSlotId } from '../../utils.ts'
|
|
32
|
-
import {
|
|
32
|
+
import { emitDedupedAttrUpdate, DEDUP_STORE_DECL } from '../../emit-reactive.ts'
|
|
33
33
|
import type { InsertPlan, InsertArm, ArmBody, ScopeRef } from '../plan/types.ts'
|
|
34
34
|
import { stringifyBranchLoops } from './branch-loop.ts'
|
|
35
35
|
import { emitListenerLine } from './event-listener.ts'
|
|
@@ -163,10 +163,12 @@ function emitArmBody(
|
|
|
163
163
|
const v = varSlotId(slotId)
|
|
164
164
|
const elVar = `__ra_${v}`
|
|
165
165
|
lines.push(`${indent}{ const ${elVar} = qsa(__branchScope, '[bf="${slotId}"]')`)
|
|
166
|
+
lines.push(`${indent}${DEDUP_STORE_DECL}`)
|
|
166
167
|
lines.push(`${indent}if (${elVar}) {`)
|
|
168
|
+
let ordinal = 0
|
|
167
169
|
for (const attr of attrs) {
|
|
168
170
|
lines.push(`${indent} __disposers.push(createDisposableEffect(() => {`)
|
|
169
|
-
for (const stmt of
|
|
171
|
+
for (const stmt of emitDedupedAttrUpdate(elVar, attr.attrName, attr.expression, attr, ordinal++)) {
|
|
170
172
|
lines.push(`${indent} ${stmt}`)
|
|
171
173
|
}
|
|
172
174
|
lines.push(`${indent} }${bindingBfId(slotId)}))`)
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
*/
|
|
61
61
|
|
|
62
62
|
import { isBooleanAttr } from '../../../html-constants.ts'
|
|
63
|
-
import {
|
|
63
|
+
import { emitDedupedAttrUpdate, dedupGuard } from '../../emit-reactive.ts'
|
|
64
64
|
import { toHtmlAttrName } from '../../utils.ts'
|
|
65
65
|
import type { SkeletonSlotPaths } from '../../html-template.ts'
|
|
66
66
|
import { claimPlanLiteral, type ClaimSlotSpec } from './claim-plan.ts'
|
|
@@ -458,11 +458,13 @@ function emitConditional(
|
|
|
458
458
|
lines.push(`${ind}} }`)
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
461
|
+
// `dedupGuard` (the `entry.last`-backed dedup test) and the actual guarded
|
|
462
|
+
// write now live in `emit-reactive.ts` as `emitDedupedAttrUpdate` (#2869) —
|
|
463
|
+
// shared with every eager reactive-attr emission site. This module's only
|
|
464
|
+
// remaining substrate-specific piece is WHERE `__l` comes from: the
|
|
465
|
+
// reconciler's `entry.last` (see the per-mode `applyItem`/`applyOuter`
|
|
466
|
+
// callers below), not a closure-local `const __l = []`, because those are
|
|
467
|
+
// separate invocations with no shared closure of their own to own it.
|
|
466
468
|
|
|
467
469
|
function emitAttrBinding(
|
|
468
470
|
lines: string[],
|
|
@@ -474,21 +476,16 @@ function emitAttrBinding(
|
|
|
474
476
|
// plain read. An ADOPTED row starts with an EMPTY `refs` array and each
|
|
475
477
|
// binding claims its OWN slot on first use — see `elementAccess`.
|
|
476
478
|
const target = mode === 'create' ? `__r[${a.refIndex}]` : elementAccess(a)
|
|
477
|
-
lines.push(`${ind}{ const __t = ${target}`)
|
|
478
|
-
lines.push(`${ind}if (__t) {`)
|
|
479
|
-
lines.push(`${ind} const __x = ${a.wrappedExpression}`)
|
|
480
479
|
const guard = mode === 'create'
|
|
481
480
|
? null
|
|
482
481
|
: mode === 'item'
|
|
483
482
|
? dedupGuard(a.ordinal)
|
|
484
483
|
: `__seed ? (${seedDiffersExpr('__t', a)}) : (${dedupGuard(a.ordinal)})`
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
for (const stmt of
|
|
488
|
-
lines.push(`${
|
|
484
|
+
lines.push(`${ind}{ const __t = ${target}`)
|
|
485
|
+
lines.push(`${ind}if (__t) {`)
|
|
486
|
+
for (const stmt of emitDedupedAttrUpdate('__t', a.attrName, a.wrappedExpression, a.meta, a.ordinal, guard)) {
|
|
487
|
+
lines.push(`${ind} ${stmt}`)
|
|
489
488
|
}
|
|
490
|
-
if (guard) lines.push(`${ind} }`)
|
|
491
|
-
lines.push(`${ind} __l[${a.ordinal}] = __x`)
|
|
492
489
|
lines.push(`${ind}} }`)
|
|
493
490
|
}
|
|
494
491
|
|