@barefootjs/jsx 0.34.0 → 0.35.1
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/analyzer.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1072 -903
- 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-branch-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-component-loop.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 +11 -0
- 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-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-plain-row.d.ts +52 -0
- package/dist/ir-to-client-js/control-flow/plan/build-plain-row.d.ts.map +1 -0
- package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts +5 -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/lazy-conditional.d.ts +6 -7
- package/dist/ir-to-client-js/control-flow/plan/lazy-conditional.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/lazy-preamble.d.ts +5 -7
- package/dist/ir-to-client-js/control-flow/plan/lazy-preamble.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/lazy-row-eligibility.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/plan/loop.d.ts +10 -6
- package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/shared.d.ts +11 -9
- package/dist/ir-to-client-js/control-flow/shared.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/composite-loop.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/csr-substitute.d.ts +16 -0
- package/dist/ir-to-client-js/csr-substitute.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/emit-registration.d.ts.map +1 -1
- package/dist/ir-to-client-js/html-template.d.ts +2 -2
- package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
- package/dist/ir-to-client-js/imports.d.ts +2 -2
- package/dist/ir-to-client-js/imports.d.ts.map +1 -1
- package/dist/ir-to-client-js/index.d.ts.map +1 -1
- package/dist/ir-to-client-js/markup-slots.d.ts +21 -0
- package/dist/ir-to-client-js/markup-slots.d.ts.map +1 -0
- 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/safe-html.d.ts +158 -0
- package/dist/ir-to-client-js/safe-html.d.ts.map +1 -0
- 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/ir-to-client-js/utils.d.ts +34 -1
- package/dist/ir-to-client-js/utils.d.ts.map +1 -1
- package/dist/jsx-to-ir.d.ts.map +1 -1
- package/dist/prop-rewrite.d.ts +8 -3
- package/dist/prop-rewrite.d.ts.map +1 -1
- package/dist/props-binding.d.ts +22 -1
- package/dist/props-binding.d.ts.map +1 -1
- package/dist/reactivity-checker.d.ts +18 -0
- package/dist/reactivity-checker.d.ts.map +1 -1
- package/dist/ssr-defaults.d.ts.map +1 -1
- package/dist/types.d.ts +5 -10
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +138 -22
- package/src/__tests__/binding-scope-ratchet.test.ts +25 -2
- package/src/__tests__/build-plain-row-consolidation.test.ts +120 -0
- package/src/__tests__/child-prop-fallback-wrap.test.ts +4 -2
- package/src/__tests__/client-template-escape-soundness.test.ts +538 -0
- package/src/__tests__/compiler-stress-1244.test.ts +8 -8
- package/src/__tests__/csr-template-loop-shadowing.test.ts +4 -2
- package/src/__tests__/flatmap-segments.test.ts +1 -1
- 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__/lazy-conditional.test.ts +52 -12
- package/src/__tests__/lazy-preamble.test.ts +11 -11
- package/src/__tests__/lazy-row-eligibility.test.ts +8 -3
- package/src/__tests__/loop-child-reactive-attr-const-shadow.test.ts +25 -3
- package/src/__tests__/loop-index-reactivity.test.ts +208 -0
- package/src/__tests__/loop-item-conditional-codegen.test.ts +4 -2
- package/src/__tests__/map-arbitrary-body.test.ts +1 -1
- package/src/__tests__/map-body-brand-checker.test.ts +176 -0
- package/src/__tests__/nested-loop-index-param.test.ts +41 -8
- 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 +16 -6
- package/src/__tests__/preamble-branch-arm-rerun.test.ts +135 -0
- package/src/__tests__/reactive-attrs-in-map.test.ts +13 -10
- package/src/__tests__/rewrite-destructured-props.test.ts +37 -0
- package/src/__tests__/safe-html-splice-door.test.ts +61 -0
- package/src/__tests__/ssr-defaults.test.ts +59 -0
- package/src/__tests__/style-css-var-reactive.test.ts +4 -1
- package/src/analyzer-context.ts +12 -10
- package/src/analyzer.ts +4 -0
- package/src/index.ts +8 -2
- package/src/ir-to-client-js/collect-elements.ts +126 -32
- package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +20 -28
- package/src/ir-to-client-js/control-flow/plan/build-component-loop.ts +9 -7
- package/src/ir-to-client-js/control-flow/plan/build-composite-loop.ts +12 -6
- package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +36 -59
- package/src/ir-to-client-js/control-flow/plan/build-lazy-row.ts +25 -4
- package/src/ir-to-client-js/control-flow/plan/build-loop-child-arm.ts +36 -9
- package/src/ir-to-client-js/control-flow/plan/build-loop.ts +35 -35
- package/src/ir-to-client-js/control-flow/plan/build-plain-row.ts +116 -0
- package/src/ir-to-client-js/control-flow/plan/build-reactive-effects.ts +23 -8
- package/src/ir-to-client-js/control-flow/plan/inner-loop.ts +24 -45
- package/src/ir-to-client-js/control-flow/plan/lazy-conditional.ts +11 -10
- package/src/ir-to-client-js/control-flow/plan/lazy-preamble.ts +11 -13
- package/src/ir-to-client-js/control-flow/plan/lazy-row-eligibility.ts +13 -3
- package/src/ir-to-client-js/control-flow/plan/loop-child-arm.ts +14 -0
- package/src/ir-to-client-js/control-flow/plan/loop.ts +10 -6
- package/src/ir-to-client-js/control-flow/shared.ts +40 -17
- package/src/ir-to-client-js/control-flow/stringify/composite-loop.ts +2 -1
- 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 +22 -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/csr-substitute.ts +29 -0
- package/src/ir-to-client-js/emit-reactive.ts +92 -6
- package/src/ir-to-client-js/emit-registration.ts +2 -4
- package/src/ir-to-client-js/html-template.ts +67 -245
- package/src/ir-to-client-js/imports.ts +3 -0
- package/src/ir-to-client-js/index.ts +4 -4
- package/src/ir-to-client-js/markup-slots.ts +25 -0
- package/src/ir-to-client-js/reactivity.ts +73 -27
- package/src/ir-to-client-js/safe-html.ts +243 -0
- package/src/ir-to-client-js/types.ts +19 -3
- package/src/ir-to-client-js/utils.ts +58 -10
- package/src/jsx-to-ir.ts +136 -24
- package/src/prop-rewrite.ts +8 -4
- package/src/props-binding.ts +37 -1
- package/src/reactivity-checker.ts +60 -0
- package/src/ssr-defaults.ts +45 -0
- package/src/types.ts +5 -10
- package/src/__tests__/text-slot-escaping.test.ts +0 -65
|
@@ -113,7 +113,10 @@ describe('reactive CSS custom properties (#135)', () => {
|
|
|
113
113
|
|
|
114
114
|
// Both reactive sinks live in the init body — one assigning the DOM
|
|
115
115
|
// `disabled` property, the other writing the `aria-busy` attribute.
|
|
116
|
-
|
|
116
|
+
// The write reads a dedup-guarded temp (`__x`, #2869), not `busy()`
|
|
117
|
+
// inline — the computed value is still `busy()`'s result either way.
|
|
118
|
+
expect(content).toMatch(/const __x = busy\(\)/)
|
|
119
|
+
expect(content).toMatch(/\.disabled\s*=\s*!!\(__x\)/)
|
|
117
120
|
expect(content).toContain("setAttribute('aria-busy'")
|
|
118
121
|
})
|
|
119
122
|
})
|
package/src/analyzer-context.ts
CHANGED
|
@@ -26,6 +26,7 @@ import type {
|
|
|
26
26
|
} from './types.ts'
|
|
27
27
|
import { type ExcludeRange, collectAllTypeRanges, reconstructWithoutTypes } from './strip-types.ts'
|
|
28
28
|
import type { CallbackBodyAcceptor } from './adapters/interface.ts'
|
|
29
|
+
import { nodeContainsJsx } from './reactivity-checker.ts'
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* Deferred info for BF043 (props destructuring warning).
|
|
@@ -318,10 +319,17 @@ export function createAnalyzerContext(
|
|
|
318
319
|
// travel as structured segments, never as raw text. Every getJS call
|
|
319
320
|
// site shares this contract; the env gate keeps the subtree walk off
|
|
320
321
|
// the production hot path (the trichotomy harness enables it). Scoped
|
|
321
|
-
// to error
|
|
322
|
-
// take degraded fallback paths whose
|
|
323
|
-
// the invariant this trips on is
|
|
324
|
-
|
|
322
|
+
// to compiles with no `error`-severity diagnostic: a compile that
|
|
323
|
+
// already refused loudly may take degraded fallback paths whose
|
|
324
|
+
// artifacts are gated by the error — the invariant this trips on is
|
|
325
|
+
// the SILENT leak. A `warning` (e.g. BF043 props-destructuring) does
|
|
326
|
+
// not gate anything, so it must not silently disarm this assertion too
|
|
327
|
+
// (#2867) — only `error` severity does.
|
|
328
|
+
if (
|
|
329
|
+
process.env.BF_ASSERT_NO_JSX_IN_GETJS === '1' &&
|
|
330
|
+
!this.errors.some((e) => e.severity === 'error') &&
|
|
331
|
+
nodeContainsJsx(node)
|
|
332
|
+
) {
|
|
325
333
|
throw new Error(
|
|
326
334
|
'getJS() called on a JSX-bearing node — raw JSX must never be spliced ' +
|
|
327
335
|
'into emitted output. Carry mixed content as structured segments ' +
|
|
@@ -336,12 +344,6 @@ export function createAnalyzerContext(
|
|
|
336
344
|
}
|
|
337
345
|
}
|
|
338
346
|
|
|
339
|
-
/** Subtree JSX check for the test-gated getJS assertion above. */
|
|
340
|
-
function nodeContainsJsx(node: ts.Node): boolean {
|
|
341
|
-
if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node)) return true
|
|
342
|
-
return ts.forEachChild(node, nodeContainsJsx) ?? false
|
|
343
|
-
}
|
|
344
|
-
|
|
345
347
|
// =============================================================================
|
|
346
348
|
// Source Location Helper
|
|
347
349
|
// =============================================================================
|
package/src/analyzer.ts
CHANGED
|
@@ -1998,6 +1998,9 @@ const CLIENT_EXPORTS = new Set([
|
|
|
1998
1998
|
'forwardProps', 'unwrap', '__slot',
|
|
1999
1999
|
'createContext', 'useContext', 'provideContext',
|
|
2000
2000
|
'createPortal', 'isSSRPortal', 'findSiblingSlot', 'cleanupPortalPlaceholder',
|
|
2001
|
+
// Floating-element position tracking (#2848) — same runtime-only shape
|
|
2002
|
+
// as the portal entries above.
|
|
2003
|
+
'trackPosition',
|
|
2001
2004
|
// Request-scoped environment signal factory (router v0.5) — `createSignal`-
|
|
2002
2005
|
// shaped, recognised structurally (#2057) so its getter is just a signal
|
|
2003
2006
|
// getter; the compiler lowers the reader value per adapter via the signal's
|
|
@@ -4221,6 +4224,7 @@ export const BROWSER_ONLY_CLIENT_APIS = new Set([
|
|
|
4221
4224
|
'isSSRPortal',
|
|
4222
4225
|
'findSiblingSlot',
|
|
4223
4226
|
'cleanupPortalPlaceholder',
|
|
4227
|
+
'trackPosition',
|
|
4224
4228
|
])
|
|
4225
4229
|
|
|
4226
4230
|
function importsBrowserOnlyClientApi(ctx: AnalyzerContext): boolean {
|
package/src/index.ts
CHANGED
|
@@ -11,10 +11,16 @@ export type { CompileResult, CompileOptions, CompileOptionsWithAdapter, FileOutp
|
|
|
11
11
|
// SSR template-variable defaults (manifest seeds for stash-based adapters)
|
|
12
12
|
export { extractSsrDefaults, deriveStashFromDefaults } from './ssr-defaults.ts'
|
|
13
13
|
|
|
14
|
-
// Shared props-destructure binding + alias-map helpers (#2524)
|
|
15
|
-
export { propsDestructureBinding, buildPropAliasMap, isIdentifierName } from './props-binding.ts'
|
|
14
|
+
// Shared props-destructure binding + alias-map helpers (#2524, #2788)
|
|
15
|
+
export { propsDestructureBinding, buildPropAliasMap, isIdentifierName, resolveBodyDestructuredPropAliases } from './props-binding.ts'
|
|
16
16
|
export type { SsrDefault } from './ssr-defaults.ts'
|
|
17
17
|
|
|
18
|
+
// Bare local-const alias-hop resolution for a signal/memo getter name
|
|
19
|
+
// (`const items__alias = items`) — #2778's CSR-template fix, reused (not
|
|
20
|
+
// re-walked) by SSR-side alias resolution (#2813: DSL stash seeding,
|
|
21
|
+
// Go's zero-arg-call field routing).
|
|
22
|
+
export { resolveGetterAliases, collectAliasableGetterNames } from './ir-to-client-js/csr-substitute.ts'
|
|
23
|
+
|
|
18
24
|
// Import-alias resolution (#2777 client-JS registry key; #2822 SSR cross-template
|
|
19
25
|
// call names) — a local alias -> declared/exported name map built from
|
|
20
26
|
// `ir.metadata.imports`. DSL adapters build this once per compile and look up
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { type IRNode, type IRElement, type IRComponent, type IRLoop, type IRProp, pickAttrMetaFromIR } from '../types.ts'
|
|
6
6
|
import type { ClientJsContext, ConditionalBranchChildComponent, ConditionalBranchReactiveAttr, BranchLoop, ConditionalBranchTextEffect, ConditionalElement, LoopChildBindings, LoopChildBranchSummary, LoopChildConditional, LoopOffset, NestedLoop } from './types.ts'
|
|
7
|
-
import { attrValueToString, freeIdsFromRefs, quotePropName, PROPS_PARAM } from './utils.ts'
|
|
7
|
+
import { attrValueToString, freeIdsFromRefs, quotePropName, PROPS_PARAM, type LoopParamSpec } from './utils.ts'
|
|
8
8
|
import { classifyReactivity, decideWrapForAttr, decideWrapForChildProp, decideWrapFromAstFlags, collectEventHandlersFromIR, collectConditionalBranchEvents, collectConditionalBranchRefs, collectConditionalBranchChildComponents, collectLoopChildEventsWithNesting, collectLoopChildReactiveAttrs, collectLoopChildReactiveTexts, collectLoopChildRefs, emptyLoopChildBindings, buildLoopRowScope, anyNameIn } from './reactivity.ts'
|
|
9
9
|
import { irToHtmlTemplate, irToPlaceholderTemplate, irChildrenToJsExpr, jsxChildrenPropGetterExpr, buildLoopSkeletonTemplate, computeSkeletonSlotPaths, renderFlatMapClientBody, renderFlatMapProjectionClientBody, flatMapCallbackHasKeyedLeaf, type SkeletonSlotPaths } from './html-template.ts'
|
|
10
10
|
import { detectRootNamespaceWrapTag } from './control-flow/stringify/template-parse.ts'
|
|
@@ -12,8 +12,8 @@ import { expandDynamicPropValue, expandConstantForReactivity, resolveRestSpreadO
|
|
|
12
12
|
import { extractFreeIdentifiersFromText } from './csr-substitute.ts'
|
|
13
13
|
import { walkIR, stopAt } from './walker.ts'
|
|
14
14
|
import { buildLoopChainExpr } from '../loop-chain.ts'
|
|
15
|
-
import { identifierPattern } from '../identifier-pattern.ts'
|
|
16
15
|
import { classifyDOMProp } from '@barefootjs/shared'
|
|
16
|
+
import type { BindingScope } from '../scope/binding-scope.ts'
|
|
17
17
|
|
|
18
18
|
/** Expressions that render nothing (0 DOM nodes) — `&&` / `?:` empty branches. */
|
|
19
19
|
const EMPTY_RENDER_EXPRS = new Set(['null', 'undefined', 'false', "''", '""', '``'])
|
|
@@ -272,7 +272,7 @@ export const branchInnerLoopOptions: CollectInnerLoopsOptions = {
|
|
|
272
272
|
export function collectInnerLoops(
|
|
273
273
|
nodes: IRNode[],
|
|
274
274
|
siblingOffsets: Map<IRLoop, IRNode[]>,
|
|
275
|
-
outerLoopParam?: string,
|
|
275
|
+
outerLoopParam?: string | LoopParamSpec,
|
|
276
276
|
ctx?: ClientJsContext,
|
|
277
277
|
options?: CollectInnerLoopsOptions,
|
|
278
278
|
): NestedLoop[] {
|
|
@@ -280,12 +280,29 @@ export function collectInnerLoops(
|
|
|
280
280
|
parentSlotId: string | null
|
|
281
281
|
depth: number
|
|
282
282
|
insideCond: boolean
|
|
283
|
+
/**
|
|
284
|
+
* Every ancestor loop's own item/index binding, innermost-first
|
|
285
|
+
* (#2861) — `undefined` above the outermost loop. Threaded (not just
|
|
286
|
+
* captured once from `outerLoopParam`) so a THIRD-level-deep loop's row
|
|
287
|
+
* sees BOTH its grandparent's and its immediate parent's bindings, not
|
|
288
|
+
* only the outermost one: each `loop:` visit below pushes its own
|
|
289
|
+
* frame via `buildLoopRowScope(..., scope.bindingScope)` before
|
|
290
|
+
* descending into that loop's own children.
|
|
291
|
+
*/
|
|
292
|
+
bindingScope: BindingScope | undefined
|
|
283
293
|
}
|
|
284
294
|
const result: NestedLoop[] = []
|
|
285
295
|
const flat = options?.flatBranchMode === true
|
|
286
296
|
const fixedDepth = options?.templateDepth
|
|
287
297
|
const collectBindings = options?.collectItemBindings === true
|
|
288
|
-
|
|
298
|
+
// Normalize to a spec regardless of which shape the caller passed (#2868
|
|
299
|
+
// callers always pass a full spec; a bare string carries no bindings/index
|
|
300
|
+
// of its own) — one shape feeds both the reactivity scope below and
|
|
301
|
+
// `loopParamsForTemplate` inside the `loop:` visitor.
|
|
302
|
+
const outerSpec: LoopParamSpec | undefined =
|
|
303
|
+
typeof outerLoopParam === 'string' ? { param: outerLoopParam } : outerLoopParam
|
|
304
|
+
const outerScope = buildLoopRowScope(outerSpec?.param, outerSpec?.bindings, undefined, outerSpec?.index)
|
|
305
|
+
const initialScope: Scope = { parentSlotId: null, depth: 0, insideCond: false, bindingScope: outerScope }
|
|
289
306
|
|
|
290
307
|
for (const root of nodes) {
|
|
291
308
|
walkIR<Scope>(root, initialScope, {
|
|
@@ -308,14 +325,10 @@ export function collectInnerLoops(
|
|
|
308
325
|
// Pass loopParams so expressions are wrapped at generation time (not post-hoc regex).
|
|
309
326
|
// Forward destructured bindings (#951) so inner-loop template
|
|
310
327
|
// references to the destructured locals are rewritten.
|
|
311
|
-
const loopParamsForTemplate =
|
|
312
|
-
? [
|
|
328
|
+
const loopParamsForTemplate = outerSpec
|
|
329
|
+
? [outerSpec, { param: n.param, bindings: n.paramBindings, index: n.index }]
|
|
313
330
|
: undefined
|
|
314
331
|
const template = n.children.map(c => irToPlaceholderTemplate(c, undefined, emitDepth, loopParamsForTemplate)).join('')
|
|
315
|
-
// Check if array expression references the outer loop param
|
|
316
|
-
const refsOuter = outerLoopParam
|
|
317
|
-
? identifierPattern(outerLoopParam).test(n.array)
|
|
318
|
-
: false
|
|
319
332
|
// Per-item bindings for inner loop body, collected uniformly when
|
|
320
333
|
// ctx is available: reactiveTexts / reactiveAttrs / refs are each
|
|
321
334
|
// classified against the loop's OWN param via `classifyReactivity`,
|
|
@@ -349,8 +362,8 @@ export function collectInnerLoops(
|
|
|
349
362
|
// guarantees the branch's marker is mounted the moment this
|
|
350
363
|
// effect first runs (issue-2706-nested-loop-conditional-slot
|
|
351
364
|
// .test.ts's original repro).
|
|
352
|
-
bindings.reactiveTexts.push(...collectLoopChildReactiveTexts(child, ctx, n.param, n.paramBindings, true, innerPreambleNames, n.index))
|
|
353
|
-
bindings.reactiveAttrs.push(...collectLoopChildReactiveAttrs(child, ctx, n.param, n.paramBindings, true, innerPreambleNames, n.index))
|
|
365
|
+
bindings.reactiveTexts.push(...collectLoopChildReactiveTexts(child, ctx, n.param, n.paramBindings, true, innerPreambleNames, n.index, scope.bindingScope))
|
|
366
|
+
bindings.reactiveAttrs.push(...collectLoopChildReactiveAttrs(child, ctx, n.param, n.paramBindings, true, innerPreambleNames, n.index, scope.bindingScope))
|
|
354
367
|
bindings.refs.push(...collectLoopChildRefs(child))
|
|
355
368
|
}
|
|
356
369
|
bindings.conditionals.push(...collectLoopChildConditionals(
|
|
@@ -361,6 +374,12 @@ export function collectInnerLoops(
|
|
|
361
374
|
n.paramBindings,
|
|
362
375
|
innerPreambleNames,
|
|
363
376
|
n.index,
|
|
377
|
+
// Render a branch's HTML against the FULL ancestor chain (outer
|
|
378
|
+
// loop(s) + this loop's own param), not just this loop's own
|
|
379
|
+
// param — an inner-loop conditional arm can read an outer
|
|
380
|
+
// loop's item/index too (#2868).
|
|
381
|
+
loopParamsForTemplate,
|
|
382
|
+
scope.bindingScope,
|
|
364
383
|
))
|
|
365
384
|
}
|
|
366
385
|
|
|
@@ -412,7 +431,6 @@ export function collectInnerLoops(
|
|
|
412
431
|
containerSlotId: scope.parentSlotId,
|
|
413
432
|
template,
|
|
414
433
|
preamble: n.preamble,
|
|
415
|
-
refsOuterParam: refsOuter,
|
|
416
434
|
childComponents,
|
|
417
435
|
insideConditional: !flat && scope.insideCond ? true : undefined,
|
|
418
436
|
offset: flat ? undefined : resolveLoopOffset(siblingOffsets.get(n)),
|
|
@@ -420,7 +438,14 @@ export function collectInnerLoops(
|
|
|
420
438
|
})
|
|
421
439
|
// Branch-mode callers handle deeper nesting via their own collection paths.
|
|
422
440
|
if (!flat) {
|
|
423
|
-
|
|
441
|
+
// #2861: push THIS loop's own item/index frame onto the scope
|
|
442
|
+
// stack before descending, so a loop found deeper still (depth 2+)
|
|
443
|
+
// sees every ancestor's bindings, not just the outermost one.
|
|
444
|
+
descend({
|
|
445
|
+
...scope,
|
|
446
|
+
depth: scope.depth + 1,
|
|
447
|
+
bindingScope: buildLoopRowScope(n.param, n.paramBindings, innerPreambleNames, n.index, scope.bindingScope),
|
|
448
|
+
})
|
|
424
449
|
}
|
|
425
450
|
},
|
|
426
451
|
// fragment / provider / async auto-descend with the same scope.
|
|
@@ -455,7 +480,12 @@ function decideLoopRendering(
|
|
|
455
480
|
// loop. These only surface for static arrays (gated at the call site via
|
|
456
481
|
// `isStaticArray && innerLoops.length`) so dynamic child-component loops —
|
|
457
482
|
// which render through `createComponent` — are unaffected.
|
|
458
|
-
const innerLoops = collectInnerLoops(
|
|
483
|
+
const innerLoops = collectInnerLoops(
|
|
484
|
+
loop.children,
|
|
485
|
+
siblingOffsets,
|
|
486
|
+
{ param: loop.param, bindings: loop.paramBindings, index: loop.index },
|
|
487
|
+
ctx,
|
|
488
|
+
)
|
|
459
489
|
const hasInnerLoops = (innerLoops?.length ?? 0) > 0
|
|
460
490
|
const useElementReconciliation =
|
|
461
491
|
!loop.childComponent && !loop.isStaticArray && (hasNestedComps || hasInnerLoops)
|
|
@@ -727,6 +757,7 @@ export function collectElements(
|
|
|
727
757
|
: decideLoopRendering(l, siblingOffsets, ctx)
|
|
728
758
|
|
|
729
759
|
let template = ''
|
|
760
|
+
let templateIndexed: string | undefined
|
|
730
761
|
let staticItemTemplate: string | undefined
|
|
731
762
|
let skeletonTemplate: string | undefined
|
|
732
763
|
let skeletonPaths: SkeletonSlotPaths | undefined
|
|
@@ -755,10 +786,34 @@ export function collectElements(
|
|
|
755
786
|
// avoiding post-hoc regex wrapping that corrupts literal attribute values.
|
|
756
787
|
// Forward destructured bindings (#951) so references like `cfg.color`
|
|
757
788
|
// in the emitted template literal are rewritten to `__bfItem()[1].color`.
|
|
789
|
+
// NOT `index: l.index` here (#2859 follow-up): this exact template
|
|
790
|
+
// string is reused verbatim by the LAZY row plan when this loop is
|
|
791
|
+
// lazy-eligible (`LazyRowPlanData`'s clone source, and — via
|
|
792
|
+
// `mapPreambleWrapped` — its `preambleStatements`) — `mapArrayLazy`'s
|
|
793
|
+
// `createRow(entry, index)` still hands the row a plain NUMBER
|
|
794
|
+
// (map-array-lazy.ts is untouched by #2859), so baking `i()` in here
|
|
795
|
+
// would call a number. Lazy-row eligibility only refuses a REACTIVE
|
|
796
|
+
// binding that references the index — a non-reactive use like a
|
|
797
|
+
// `data-key` built from the index is not gated the same way, so this
|
|
798
|
+
// can't assume "lazy-eligible ⇒ no index in the template" either.
|
|
799
|
+
// `buildPlainLoopPlan`/`buildBranchLoopPlan` apply the index wrap
|
|
800
|
+
// themselves, AFTER lazy eligibility is decided, only when it's `null`.
|
|
758
801
|
const loopParamSpec = [{ param: l.param, bindings: l.paramBindings }]
|
|
759
802
|
template = useElementReconciliation
|
|
760
803
|
? irToPlaceholderTemplate(l.children[0], resolveRestSpreadNames(ctx), 0, loopParamSpec)
|
|
761
804
|
: irToHtmlTemplate(l.children[0], resolveRestSpreadNames(ctx), 0, loopParamSpec)
|
|
805
|
+
// Second structured render, WITH the index wrapped too — used by
|
|
806
|
+
// `build-plain-row.ts` once lazy eligibility rules out the lazy row
|
|
807
|
+
// plan (which needs `template` above, index-unwrapped, verbatim).
|
|
808
|
+
// Rendered here (IR level) rather than via a post-hoc regex pass
|
|
809
|
+
// over `template`, which can corrupt a tag name colliding with the
|
|
810
|
+
// index identifier (`<i>` -> `<i()>`, #2868).
|
|
811
|
+
if (l.index) {
|
|
812
|
+
const loopParamSpecIndexed = [{ param: l.param, bindings: l.paramBindings, index: l.index }]
|
|
813
|
+
templateIndexed = useElementReconciliation
|
|
814
|
+
? irToPlaceholderTemplate(l.children[0], resolveRestSpreadNames(ctx), 0, loopParamSpecIndexed)
|
|
815
|
+
: irToHtmlTemplate(l.children[0], resolveRestSpreadNames(ctx), 0, loopParamSpecIndexed)
|
|
816
|
+
}
|
|
762
817
|
// Static-array loops emit a `forEach((param, idx) => ...)` whose body
|
|
763
818
|
// references the destructured param directly — `__bfItem()` is not in
|
|
764
819
|
// scope there. Build a second template that skips the loop-param
|
|
@@ -815,6 +870,7 @@ export function collectElements(
|
|
|
815
870
|
iterationShape: l.iterationShape,
|
|
816
871
|
objectIteration: l.objectIteration,
|
|
817
872
|
template,
|
|
873
|
+
templateIndexed,
|
|
818
874
|
staticItemTemplate,
|
|
819
875
|
skeletonTemplate,
|
|
820
876
|
skeletonPaths,
|
|
@@ -1165,6 +1221,11 @@ function collectBranchLoops(
|
|
|
1165
1221
|
// keeping the template consistent with reactive effect expressions that
|
|
1166
1222
|
// use `param()` to read the current item value.
|
|
1167
1223
|
let childTemplate: string
|
|
1224
|
+
let childTemplateIndexed: string | undefined
|
|
1225
|
+
// NOT `index: n.index` here — see the identical note on `loopParamSpec`
|
|
1226
|
+
// above; `BranchLoop.template` is reused verbatim by the lazy row plan
|
|
1227
|
+
// too (`callSite: 'branch-plain'`), and `buildBranchLoopPlan` applies
|
|
1228
|
+
// the index wrap itself once lazy eligibility is known (#2859 follow-up).
|
|
1168
1229
|
const branchLoopParamSpec = [{ param: n.param, bindings: n.paramBindings }]
|
|
1169
1230
|
if (projectionInner) {
|
|
1170
1231
|
childTemplate = '' // descriptor renderItem builds from d.h, not a row template
|
|
@@ -1173,6 +1234,15 @@ function collectBranchLoops(
|
|
|
1173
1234
|
} else {
|
|
1174
1235
|
childTemplate = n.children.map(c => irToHtmlTemplate(c, undefined, 0, branchLoopParamSpec)).join('')
|
|
1175
1236
|
}
|
|
1237
|
+
// Second structured render, WITH the index wrapped too — see the
|
|
1238
|
+
// identical `templateIndexed` note on the top-level loop path above
|
|
1239
|
+
// (#2868).
|
|
1240
|
+
if (n.index && !projectionInner) {
|
|
1241
|
+
const branchLoopParamSpecIndexed = [{ param: n.param, bindings: n.paramBindings, index: n.index }]
|
|
1242
|
+
childTemplateIndexed = useElementReconciliation && n.children[0]
|
|
1243
|
+
? irToPlaceholderTemplate(n.children[0], restNames, 0, branchLoopParamSpecIndexed)
|
|
1244
|
+
: n.children.map(c => irToHtmlTemplate(c, undefined, 0, branchLoopParamSpecIndexed)).join('')
|
|
1245
|
+
}
|
|
1176
1246
|
|
|
1177
1247
|
// Collect per-item bindings (events, reactive attrs/texts, refs,
|
|
1178
1248
|
// conditionals) for ALL branch loops — simple loops also need
|
|
@@ -1198,6 +1268,7 @@ function collectBranchLoops(
|
|
|
1198
1268
|
iterationShape: n.iterationShape,
|
|
1199
1269
|
objectIteration: n.objectIteration,
|
|
1200
1270
|
template: childTemplate,
|
|
1271
|
+
templateIndexed: childTemplateIndexed,
|
|
1201
1272
|
containerSlotId: containerSlot,
|
|
1202
1273
|
preamble: n.preamble,
|
|
1203
1274
|
preambleRegions: n.preambleRegions,
|
|
@@ -1425,24 +1496,41 @@ export function collectLoopChildConditionals(
|
|
|
1425
1496
|
* `collectLoopChildBindings`'s doc comment (Copilot review on #2595).
|
|
1426
1497
|
*/
|
|
1427
1498
|
loopIndex?: string | null,
|
|
1499
|
+
/**
|
|
1500
|
+
* The full ancestor loop-param chain to render branch HTML against
|
|
1501
|
+
* (outermost first), when known — e.g. `[outerSpec, innerSpec]` for a
|
|
1502
|
+
* conditional inside an inner `.map()`. Defaults to just
|
|
1503
|
+
* `[{ param: loopParam, bindings: loopParamBindings, index: loopIndex }]`
|
|
1504
|
+
* (this function's own single-loop scope) when omitted, which is correct
|
|
1505
|
+
* for every caller except `collectInnerLoops`'s inner-loop handler, which
|
|
1506
|
+
* passes the full chain so an inner loop's conditional arm can also
|
|
1507
|
+
* reference an OUTER loop's item/index (#2868). Rendering the branch's
|
|
1508
|
+
* HTML against the full chain at IR time — rather than re-wrapping the
|
|
1509
|
+
* assembled HTML string with a word-boundary regex afterward — is also
|
|
1510
|
+
* what makes it safe for a branch root tag name to collide with a
|
|
1511
|
+
* param/index identifier (`<i>`, `<b>`, …): `irToHtmlTemplate` only wraps
|
|
1512
|
+
* JS expression positions, never tag names.
|
|
1513
|
+
*/
|
|
1514
|
+
loopParams?: ReadonlyArray<string | LoopParamSpec>,
|
|
1515
|
+
/** Enclosing loop's own scope, for a NESTED loop (#2861) — see `buildLoopRowScope`. */
|
|
1516
|
+
parentScope?: BindingScope,
|
|
1428
1517
|
): LoopChildConditional[] {
|
|
1429
1518
|
const conditionals: LoopChildConditional[] = []
|
|
1430
|
-
const scope = buildLoopRowScope(loopParam, loopParamBindings, preambleNames, loopIndex)
|
|
1519
|
+
const scope = buildLoopRowScope(loopParam, loopParamBindings, preambleNames, loopIndex, parentScope)
|
|
1431
1520
|
|
|
1432
1521
|
// Widen the source-level "references loop param" check so destructured
|
|
1433
1522
|
// callbacks fire too — the pattern text `[, cfg]` never word-matches on
|
|
1434
1523
|
// bare `cfg` but individual binding names do (#951). Consumes a
|
|
1435
1524
|
// pre-computed `Set<string>` of free identifiers (#1267) rather than
|
|
1436
1525
|
// running word-boundary regex on the expression text.
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
}
|
|
1526
|
+
//
|
|
1527
|
+
// #2861: also checks the loop's INDEX binding, not just item/destructure
|
|
1528
|
+
// — `scope.valueBoundNames()` already unions all three (the same query
|
|
1529
|
+
// Phase 1's `referencesLoopParam` reads to grant a slot at all), so a
|
|
1530
|
+
// condition referencing only the index (e.g. `{i % 2 === 0 ? … : null}`)
|
|
1531
|
+
// no longer dies at this pre-gate before `classifyReactivity` even runs.
|
|
1532
|
+
const refsAnyBindingViaFreeIds = (freeIds: ReadonlySet<string>): boolean =>
|
|
1533
|
+
scope !== undefined && anyNameIn(scope.valueBoundNames(), freeIds)
|
|
1446
1534
|
|
|
1447
1535
|
walkIR(node, null, {
|
|
1448
1536
|
// element / fragment / component / provider auto-descend with same scope.
|
|
@@ -1473,13 +1561,13 @@ export function collectLoopChildConditionals(
|
|
|
1473
1561
|
preambleNames !== undefined &&
|
|
1474
1562
|
preambleNames.size > 0 &&
|
|
1475
1563
|
anyNameIn(expanded.freeIds ?? extractFreeIdentifiersFromText(expanded.expr), preambleNames)
|
|
1476
|
-
// Loop-param conditionals are reactive via per-item signal
|
|
1477
|
-
// classifyReactivity sees
|
|
1478
|
-
|
|
1564
|
+
// Loop-param/loop-index conditionals are reactive via per-item signal
|
|
1565
|
+
// accessors; classifyReactivity sees all three paths (signal/memo/prop
|
|
1566
|
+
// + loop-param + loop-index, #2861).
|
|
1567
|
+
if (!readsPreamble && classifyReactivity(expanded.expr, ctx, scope, expanded.freeIds).kind === 'none') return
|
|
1479
1568
|
|
|
1480
|
-
const loopParamsForCond =
|
|
1481
|
-
? [{ param: loopParam, bindings: loopParamBindings }]
|
|
1482
|
-
: undefined
|
|
1569
|
+
const loopParamsForCond =
|
|
1570
|
+
loopParams ?? (loopParam ? [{ param: loopParam, bindings: loopParamBindings, index: loopIndex }] : undefined)
|
|
1483
1571
|
// `__slots` matches the closure variable emitted by
|
|
1484
1572
|
// `stringifyLoopChildConditional` — Child-position interpolations
|
|
1485
1573
|
// get wrapped in `__bfSlot(EXPR, __slots)` so live `Node` returns
|
|
@@ -1520,7 +1608,13 @@ function summarizeLoopChildBranch(
|
|
|
1520
1608
|
/** Enclosing loop's index param name — see `collectLoopChildConditionals` (Copilot review on #2595). */
|
|
1521
1609
|
loopIndex?: string | null,
|
|
1522
1610
|
): LoopChildBranchSummary {
|
|
1523
|
-
const inner = collectInnerLoops(
|
|
1611
|
+
const inner = collectInnerLoops(
|
|
1612
|
+
[node],
|
|
1613
|
+
siblingOffsets,
|
|
1614
|
+
loopParam ? { param: loopParam, bindings: loopParamBindings, index: loopIndex } : undefined,
|
|
1615
|
+
ctx,
|
|
1616
|
+
branchInnerLoopOptions,
|
|
1617
|
+
)
|
|
1524
1618
|
return {
|
|
1525
1619
|
childComponents: collectConditionalBranchChildComponents(node),
|
|
1526
1620
|
innerLoops: inner.length > 0 ? inner : undefined,
|
|
@@ -11,14 +11,13 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type { BranchLoop } from '../../types.ts'
|
|
14
|
-
import { buildChainedArrayExpr, varSlotId
|
|
14
|
+
import { buildChainedArrayExpr, varSlotId } from '../../utils.ts'
|
|
15
15
|
import { buildBranchCompositePlan } from './build-composite-loop.ts'
|
|
16
16
|
import { buildBranchLoopDelegationPlan } from './build-event-delegation.ts'
|
|
17
17
|
import { buildReactiveEffectsPlan } from './build-reactive-effects.ts'
|
|
18
|
-
import {
|
|
18
|
+
import { buildPlainRowCore } from './build-plain-row.ts'
|
|
19
19
|
import type { LazyRowScopeInfo } from './lazy-row-eligibility.ts'
|
|
20
|
-
import {
|
|
21
|
-
import { renderPreamble, irToHtmlTemplate } from '../../html-template.ts'
|
|
20
|
+
import { loopKeyFn, buildChildRefBindings } from '../shared.ts'
|
|
22
21
|
import type {
|
|
23
22
|
BranchCompositeLoopPlan,
|
|
24
23
|
BranchLoopPlan,
|
|
@@ -44,7 +43,6 @@ export function buildBranchLoopPlan(
|
|
|
44
43
|
return composite
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(loop.param, loop.paramBindings)
|
|
48
46
|
const hasReactiveEffects = loop.bindings.reactiveAttrs.length > 0
|
|
49
47
|
|| loop.bindings.reactiveTexts.length > 0
|
|
50
48
|
|| loop.bindings.conditionals.length > 0
|
|
@@ -54,14 +52,18 @@ export function buildBranchLoopPlan(
|
|
|
54
52
|
const arrayExpr = fm
|
|
55
53
|
? `(${buildChainedArrayExpr(loop)}).flatMap(${fm.params} => ${fm.body})`
|
|
56
54
|
: buildChainedArrayExpr(loop)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
: ''
|
|
64
|
-
|
|
55
|
+
|
|
56
|
+
// Shared "wrap item → decide lazy → wrap index" core (#2859 follow-up) —
|
|
57
|
+
// see `build-plain-row.ts`'s module docstring for the full rationale.
|
|
58
|
+
const core = buildPlainRowCore({
|
|
59
|
+
loop,
|
|
60
|
+
arrayExpr,
|
|
61
|
+
callSite: 'branch-plain',
|
|
62
|
+
flatMapLeafItem: Boolean(fm),
|
|
63
|
+
anchored: false,
|
|
64
|
+
scope: lazyScope,
|
|
65
|
+
})
|
|
66
|
+
const { paramHead, paramUnwrap, indexParam, preambleRegions, lazyRow } = core
|
|
65
67
|
const plan: BranchPlainLoopPlan = {
|
|
66
68
|
kind: 'plain',
|
|
67
69
|
rowConstruction: 'string-template',
|
|
@@ -78,21 +80,10 @@ export function buildBranchLoopPlan(
|
|
|
78
80
|
indexParam,
|
|
79
81
|
// Wrap loop-param references to signal-accessor form so the preamble
|
|
80
82
|
// matches the template literal's already-wrapped reads (#1065).
|
|
81
|
-
mapPreambleWrapped,
|
|
83
|
+
mapPreambleWrapped: core.mapPreambleWrapped,
|
|
82
84
|
// Lazy row graph (§9, L3) — undefined for every ineligible loop.
|
|
83
|
-
lazyRow
|
|
84
|
-
|
|
85
|
-
arrayExpr,
|
|
86
|
-
indexParam,
|
|
87
|
-
paramUnwrap,
|
|
88
|
-
mapPreambleWrapped,
|
|
89
|
-
preambleRegionCount: preambleRegions.length,
|
|
90
|
-
callSite: 'branch-plain',
|
|
91
|
-
flatMapLeafItem: Boolean(fm),
|
|
92
|
-
anchored: false,
|
|
93
|
-
scope: lazyScope,
|
|
94
|
-
}) ?? undefined,
|
|
95
|
-
template: loop.template,
|
|
85
|
+
lazyRow,
|
|
86
|
+
template: core.template,
|
|
96
87
|
reactiveEffects: hasReactiveEffects
|
|
97
88
|
? buildReactiveEffectsPlan({
|
|
98
89
|
attrs: loop.bindings.reactiveAttrs,
|
|
@@ -100,11 +91,12 @@ export function buildBranchLoopPlan(
|
|
|
100
91
|
conditionals: loop.bindings.conditionals,
|
|
101
92
|
loopParam: loop.param,
|
|
102
93
|
loopParamBindings: loop.paramBindings,
|
|
94
|
+
loopIndex: loop.index,
|
|
103
95
|
profileComponentName,
|
|
104
96
|
})
|
|
105
97
|
: null,
|
|
106
98
|
eventDelegation: buildBranchLoopDelegationPlan(loop, cv, profileComponentName),
|
|
107
|
-
childRefs: buildChildRefBindings(loop.bindings.refs, loop.param, loop.paramBindings),
|
|
99
|
+
childRefs: buildChildRefBindings(loop.bindings.refs, loop.param, loop.paramBindings, loop.index),
|
|
108
100
|
preambleRegions,
|
|
109
101
|
bodyIsMultiRoot: loop.bodyIsMultiRoot ?? false,
|
|
110
102
|
profileLoopId: profileComponentName ? `${profileComponentName}#binding:${containerSlotId}` : undefined,
|
|
@@ -34,8 +34,8 @@ import { internalInvariant } from '../../../errors.ts'
|
|
|
34
34
|
/** @internal — prefer `buildLoopPlan`. */
|
|
35
35
|
export function buildComponentLoopPlan(elem: TopLevelLoop, profileComponentName?: string): ComponentLoopPlan {
|
|
36
36
|
const { name } = elem.childComponent!
|
|
37
|
-
const propsExpr = buildComponentPropsExpr(elem.childComponent!, elem.param)
|
|
38
|
-
const keyExpr = wrapLoopParamAsAccessor(elem.key || '__idx', elem.param, elem.paramBindings)
|
|
37
|
+
const propsExpr = buildComponentPropsExpr(elem.childComponent!, elem.param, undefined, elem.index)
|
|
38
|
+
const keyExpr = wrapLoopParamAsAccessor(elem.key || '__idx', elem.param, elem.paramBindings, elem.index)
|
|
39
39
|
const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(elem.param, elem.paramBindings)
|
|
40
40
|
|
|
41
41
|
// A component-root loop's preamble is JS-only by construction: Phase 1
|
|
@@ -47,7 +47,7 @@ export function buildComponentLoopPlan(elem: TopLevelLoop, profileComponentName?
|
|
|
47
47
|
// that let a JSX-bearing preamble through is missing, not this line.
|
|
48
48
|
const mapPreambleWrapped = elem.preamble
|
|
49
49
|
? renderPreamble(elem.preamble, {
|
|
50
|
-
transformJs: text => wrapLoopParamAsAccessor(text, elem.param, elem.paramBindings),
|
|
50
|
+
transformJs: text => wrapLoopParamAsAccessor(text, elem.param, elem.paramBindings, elem.index),
|
|
51
51
|
renderLeaf: () => {
|
|
52
52
|
internalInvariant(false, 'component-root loop received a JSX-bearing preamble — Phase 1 should have refused it')
|
|
53
53
|
},
|
|
@@ -62,13 +62,14 @@ export function buildComponentLoopPlan(elem: TopLevelLoop, profileComponentName?
|
|
|
62
62
|
: false
|
|
63
63
|
const rawChildrenExpr = isTextOnly ? irChildrenToJsExpr(comp.children!) : null
|
|
64
64
|
const childrenFreeIds = isTextOnly && comp.children ? irChildrenFreeIds(comp.children) : undefined
|
|
65
|
-
const childrenRefsLoop = rawChildrenExpr != null && childrenFreeIds != null
|
|
65
|
+
const childrenRefsLoop = rawChildrenExpr != null && childrenFreeIds != null
|
|
66
|
+
&& (childrenFreeIds.has(elem.param) || (!!elem.index && childrenFreeIds.has(elem.index)))
|
|
66
67
|
return {
|
|
67
68
|
componentName: comp.name,
|
|
68
69
|
selector: buildCompSelector(comp),
|
|
69
|
-
propsExpr: buildComponentPropsExpr(comp, elem.param),
|
|
70
|
+
propsExpr: buildComponentPropsExpr(comp, elem.param, undefined, elem.index),
|
|
70
71
|
childrenTextEffect: childrenRefsLoop
|
|
71
|
-
? { wrappedChildren: wrapLoopParamAsAccessor(rawChildrenExpr!, elem.param, elem.paramBindings) }
|
|
72
|
+
? { wrappedChildren: wrapLoopParamAsAccessor(rawChildrenExpr!, elem.param, elem.paramBindings, elem.index) }
|
|
72
73
|
: null,
|
|
73
74
|
}
|
|
74
75
|
})
|
|
@@ -98,7 +99,7 @@ export function buildComponentLoopPlan(elem: TopLevelLoop, profileComponentName?
|
|
|
98
99
|
// the per-item factory has no `__el` handle to invoke. Still required
|
|
99
100
|
// by the type so the structural invariant (every variant has a
|
|
100
101
|
// `childRefs`) is preserved; populated as empty.
|
|
101
|
-
childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings),
|
|
102
|
+
childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings, elem.index),
|
|
102
103
|
profileLoopId: profileComponentName ? `${profileComponentName}#binding:${elem.slotId}` : undefined,
|
|
103
104
|
childConditionalEffects: hasChildConds
|
|
104
105
|
? buildReactiveEffectsPlan({
|
|
@@ -107,6 +108,7 @@ export function buildComponentLoopPlan(elem: TopLevelLoop, profileComponentName?
|
|
|
107
108
|
conditionals: elem.bindings.conditionals,
|
|
108
109
|
loopParam: elem.param,
|
|
109
110
|
loopParamBindings: elem.paramBindings,
|
|
111
|
+
loopIndex: elem.index,
|
|
110
112
|
profileComponentName,
|
|
111
113
|
})
|
|
112
114
|
: null,
|
|
@@ -31,7 +31,7 @@ export function buildTopLevelCompositePlan(elem: TopLevelLoop, profileComponentN
|
|
|
31
31
|
const nestedComps = elem.nestedComponents!
|
|
32
32
|
const depthLevels = buildDepthLevels(elem.innerLoops ?? [], nestedComps, elem.bindings.events)
|
|
33
33
|
const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(elem.param, elem.paramBindings)
|
|
34
|
-
const wrap = (expr: string) => wrapLoopParamAsAccessor(expr, elem.param, elem.paramBindings)
|
|
34
|
+
const wrap = (expr: string) => wrapLoopParamAsAccessor(expr, elem.param, elem.paramBindings, elem.index)
|
|
35
35
|
|
|
36
36
|
const outerCompsByDepth = nestedComps.filter(c => !c.loopDepth || c.loopDepth === 0)
|
|
37
37
|
|
|
@@ -48,21 +48,23 @@ export function buildTopLevelCompositePlan(elem: TopLevelLoop, profileComponentN
|
|
|
48
48
|
mapPreambleWrapped: elem.preamble
|
|
49
49
|
? renderPreamble(elem.preamble, {
|
|
50
50
|
transformJs: wrap,
|
|
51
|
-
renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: elem.param, bindings: elem.paramBindings }], undefined),
|
|
51
|
+
renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: elem.param, bindings: elem.paramBindings, index: elem.index }], undefined),
|
|
52
52
|
})
|
|
53
53
|
: '',
|
|
54
54
|
template: elem.template,
|
|
55
55
|
outerComps: filterCondCompsOut(outerCompsByDepth, elem.bindings.conditionals),
|
|
56
56
|
outerEvents: elem.bindings.events.filter(ev => ev.nestedLoops.length === 0),
|
|
57
|
-
childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings),
|
|
57
|
+
childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings, elem.index),
|
|
58
58
|
innerLoops: buildInnerLoopsPlan({
|
|
59
59
|
levels: depthLevels,
|
|
60
60
|
parentElVar: '__el',
|
|
61
61
|
outerLoopParam: elem.param,
|
|
62
62
|
outerLoopParamBindings: elem.paramBindings,
|
|
63
|
+
outerLoopIndex: elem.index,
|
|
63
64
|
}),
|
|
64
65
|
loopParam: elem.param,
|
|
65
66
|
loopParamBindings: elem.paramBindings,
|
|
67
|
+
loopIndex: elem.index,
|
|
66
68
|
reactiveEffects: hasReactive(elem)
|
|
67
69
|
? buildReactiveEffectsPlan({
|
|
68
70
|
attrs: elem.bindings.reactiveAttrs,
|
|
@@ -70,6 +72,7 @@ export function buildTopLevelCompositePlan(elem: TopLevelLoop, profileComponentN
|
|
|
70
72
|
conditionals: elem.bindings.conditionals,
|
|
71
73
|
loopParam: elem.param,
|
|
72
74
|
loopParamBindings: elem.paramBindings,
|
|
75
|
+
loopIndex: elem.index,
|
|
73
76
|
profileComponentName,
|
|
74
77
|
})
|
|
75
78
|
: null,
|
|
@@ -88,7 +91,7 @@ export function buildBranchCompositePlan(loop: BranchLoop, cv: string, profileCo
|
|
|
88
91
|
const childEvents = loop.bindings.events
|
|
89
92
|
const depthLevels = buildDepthLevels(innerLoops, nestedComps, childEvents)
|
|
90
93
|
const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(loop.param, loop.paramBindings)
|
|
91
|
-
const wrap = (expr: string) => wrapLoopParamAsAccessor(expr, loop.param, loop.paramBindings)
|
|
94
|
+
const wrap = (expr: string) => wrapLoopParamAsAccessor(expr, loop.param, loop.paramBindings, loop.index)
|
|
92
95
|
|
|
93
96
|
const outerCompsByDepth = nestedComps.filter(c => !c.loopDepth || c.loopDepth === 0)
|
|
94
97
|
|
|
@@ -108,21 +111,23 @@ export function buildBranchCompositePlan(loop: BranchLoop, cv: string, profileCo
|
|
|
108
111
|
mapPreambleWrapped: loop.preamble
|
|
109
112
|
? renderPreamble(loop.preamble, {
|
|
110
113
|
transformJs: wrap,
|
|
111
|
-
renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: loop.param, bindings: loop.paramBindings }], undefined),
|
|
114
|
+
renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: loop.param, bindings: loop.paramBindings, index: loop.index }], undefined),
|
|
112
115
|
})
|
|
113
116
|
: '',
|
|
114
117
|
template: loop.template,
|
|
115
118
|
outerComps: filterCondCompsOut(outerCompsByDepth, loop.bindings.conditionals),
|
|
116
119
|
outerEvents: childEvents.filter(ev => ev.nestedLoops.length === 0),
|
|
117
|
-
childRefs: buildChildRefBindings(loop.bindings.refs, loop.param, loop.paramBindings),
|
|
120
|
+
childRefs: buildChildRefBindings(loop.bindings.refs, loop.param, loop.paramBindings, loop.index),
|
|
118
121
|
innerLoops: buildInnerLoopsPlan({
|
|
119
122
|
levels: depthLevels,
|
|
120
123
|
parentElVar: '__el',
|
|
121
124
|
outerLoopParam: loop.param,
|
|
122
125
|
outerLoopParamBindings: loop.paramBindings,
|
|
126
|
+
outerLoopIndex: loop.index,
|
|
123
127
|
}),
|
|
124
128
|
loopParam: loop.param,
|
|
125
129
|
loopParamBindings: loop.paramBindings,
|
|
130
|
+
loopIndex: loop.index,
|
|
126
131
|
reactiveEffects: hasReactiveBranch(loop)
|
|
127
132
|
? buildReactiveEffectsPlan({
|
|
128
133
|
attrs: loop.bindings.reactiveAttrs,
|
|
@@ -130,6 +135,7 @@ export function buildBranchCompositePlan(loop: BranchLoop, cv: string, profileCo
|
|
|
130
135
|
conditionals: loop.bindings.conditionals,
|
|
131
136
|
loopParam: loop.param,
|
|
132
137
|
loopParamBindings: loop.paramBindings,
|
|
138
|
+
loopIndex: loop.index,
|
|
133
139
|
profileComponentName,
|
|
134
140
|
})
|
|
135
141
|
: null,
|