@barefootjs/jsx 0.33.0 → 0.33.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.d.ts.map +1 -1
- package/dist/errors.d.ts +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/expression-parser.d.ts +14 -0
- package/dist/expression-parser.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +268 -79
- package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
- 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-loop-child-arm.d.ts +8 -0
- 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-reactive-effects.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/inner-loop.d.ts +12 -0
- package/dist/ir-to-client-js/control-flow/plan/inner-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/inner-loop.d.ts +1 -0
- 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.map +1 -1
- package/dist/ir-to-client-js/element-refs.d.ts.map +1 -1
- 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 +7 -7
- 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/phases/provider-and-child-inits.d.ts.map +1 -1
- package/dist/ir-to-client-js/prop-handling.d.ts +33 -0
- package/dist/ir-to-client-js/prop-handling.d.ts.map +1 -1
- package/dist/ir-to-client-js/reactivity.d.ts +5 -0
- package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
- package/dist/ir-to-client-js/rewrite-props-object.d.ts +36 -8
- package/dist/ir-to-client-js/rewrite-props-object.d.ts.map +1 -1
- package/dist/ir-to-client-js/types.d.ts +21 -0
- package/dist/ir-to-client-js/types.d.ts.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +23 -6
- package/src/__tests__/binding-scope-ratchet.test.ts +5 -1
- package/src/__tests__/child-components-in-map.test.ts +11 -3
- package/src/__tests__/client-js-generation.test.ts +48 -1
- package/src/__tests__/inline-jsx-callback.test.ts +55 -0
- package/src/__tests__/ir-jsx-props.test.ts +148 -0
- package/src/__tests__/issue-2705-branch-inner-loop-container.test.ts +91 -0
- package/src/__tests__/issue-2723-prop-alias-reactivity.test.ts +124 -0
- package/src/__tests__/markup-prop-brand.test.ts +49 -0
- package/src/__tests__/nested-loop-conditional.test.ts +20 -11
- package/src/__tests__/return-through-local-var.test.ts +269 -0
- package/src/__tests__/rewrite-props-object.test.ts +41 -4
- package/src/analyzer.ts +71 -0
- package/src/errors.ts +17 -1
- package/src/expression-parser.ts +26 -0
- package/src/index.ts +1 -1
- package/src/ir-to-client-js/collect-elements.ts +49 -45
- package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +19 -0
- package/src/ir-to-client-js/control-flow/plan/build-loop-child-arm.ts +22 -3
- package/src/ir-to-client-js/control-flow/plan/build-reactive-effects.ts +5 -2
- package/src/ir-to-client-js/control-flow/plan/inner-loop.ts +12 -0
- package/src/ir-to-client-js/control-flow/stringify/inner-loop.ts +9 -0
- package/src/ir-to-client-js/control-flow/stringify/lazy-row.ts +7 -1
- package/src/ir-to-client-js/element-refs.ts +8 -0
- package/src/ir-to-client-js/emit-reactive.ts +91 -23
- package/src/ir-to-client-js/emit-registration.ts +26 -7
- package/src/ir-to-client-js/generate-init.ts +1 -1
- package/src/ir-to-client-js/html-template.ts +8 -8
- package/src/ir-to-client-js/imports.ts +5 -0
- package/src/ir-to-client-js/index.ts +10 -4
- package/src/ir-to-client-js/phases/provider-and-child-inits.ts +5 -1
- package/src/ir-to-client-js/prop-handling.ts +94 -0
- package/src/ir-to-client-js/reactivity.ts +59 -0
- package/src/ir-to-client-js/rewrite-props-object.ts +50 -10
- package/src/ir-to-client-js/types.ts +21 -0
- package/src/jsx-to-ir.ts +204 -9
- package/src/types.ts +14 -0
package/src/jsx-to-ir.ts
CHANGED
|
@@ -1066,7 +1066,37 @@ function buildIRRoot(analyzer: AnalyzerContext): IRNode | null {
|
|
|
1066
1066
|
// scope; the inner IR must not double-mark a nested element as root.
|
|
1067
1067
|
ctx.isRoot = false
|
|
1068
1068
|
const ir = transformJsxExpression(jsxReturn, ctx)
|
|
1069
|
-
if (ir === null)
|
|
1069
|
+
if (ir === null) {
|
|
1070
|
+
// BF027 (#2720): a bare identifier at return position that names a
|
|
1071
|
+
// local `const`/`let` PROVEN to hold JSX (`jsxConstants` — pure JSX
|
|
1072
|
+
// literal; `inlineableJsxConsts` — a JSX-shaped ternary/`&&`/`||`/`??`)
|
|
1073
|
+
// is the "returned JSX through a local variable" shape. Return position
|
|
1074
|
+
// deliberately does not resolve identifiers the way JSX-child position
|
|
1075
|
+
// does (see the #547/#1409 inlining above), so the scalar-leaf fallback
|
|
1076
|
+
// would otherwise drop this component with zero files and zero
|
|
1077
|
+
// diagnostics. Scoped to identifiers already proven JSX-holding by
|
|
1078
|
+
// those two maps so an ordinary non-JSX return (`return 42`, `return
|
|
1079
|
+
// someHelperResult`) — including from a PascalCase-but-not-a-component
|
|
1080
|
+
// export the analyzer's syntactic component detector still matches —
|
|
1081
|
+
// stays silent exactly as before.
|
|
1082
|
+
if (
|
|
1083
|
+
ts.isIdentifier(jsxReturn) &&
|
|
1084
|
+
(analyzer.jsxConstants.has(jsxReturn.text) || analyzer.inlineableJsxConsts.has(jsxReturn.text))
|
|
1085
|
+
) {
|
|
1086
|
+
analyzer.errors.push(createError(
|
|
1087
|
+
ErrorCodes.RETURN_VALUE_NOT_JSX,
|
|
1088
|
+
getSourceLocation(jsxReturn, analyzer.sourceFile, analyzer.filePath),
|
|
1089
|
+
{
|
|
1090
|
+
message:
|
|
1091
|
+
`Component '${analyzer.componentName ?? '(unknown)'}' return value is not recognized ` +
|
|
1092
|
+
`as JSX — return the JSX expression directly instead of binding it to a local variable ` +
|
|
1093
|
+
`first (\`return ${jsxReturn.text}\` after \`const ${jsxReturn.text} = <jsx/>\` is not ` +
|
|
1094
|
+
`resolved at return position).`,
|
|
1095
|
+
},
|
|
1096
|
+
))
|
|
1097
|
+
}
|
|
1098
|
+
return null
|
|
1099
|
+
}
|
|
1070
1100
|
return wrapInScopeElement(ir)
|
|
1071
1101
|
}
|
|
1072
1102
|
|
|
@@ -1914,6 +1944,52 @@ function unwrapHoistedFragment(node: IRNode): IRNode {
|
|
|
1914
1944
|
return { ...only, needsScope: true }
|
|
1915
1945
|
}
|
|
1916
1946
|
|
|
1947
|
+
// #2732: a `needsScopeComment` fragment's five hydration markers move to
|
|
1948
|
+
// the wrapping comment, but `data-key` needs to stay on an element (see
|
|
1949
|
+
// `IRElement.carriesDataKey`'s docstring for why). Mark the first ELEMENT
|
|
1950
|
+
// among `children` — immutably (`{ ...el, carriesDataKey: true }`), matching
|
|
1951
|
+
// the rest of this file's post-hoc IR tagging (e.g. `unwrapHoistedFragment`)
|
|
1952
|
+
// rather than mutating the child in place.
|
|
1953
|
+
function markDataKeyCarrier(children: IRNode[]): IRNode[] {
|
|
1954
|
+
for (let i = 0; i < children.length; i++) {
|
|
1955
|
+
const marked = markCarrierIn(children[i])
|
|
1956
|
+
if (!marked) continue
|
|
1957
|
+
const out = children.slice()
|
|
1958
|
+
out[i] = marked
|
|
1959
|
+
return out
|
|
1960
|
+
}
|
|
1961
|
+
return children
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
/**
|
|
1965
|
+
* The marked copy of `node` if this subtree can render an element in first
|
|
1966
|
+
* position, or null if it cannot.
|
|
1967
|
+
*
|
|
1968
|
+
* Descends through `conditional` because a fragment whose only top-level
|
|
1969
|
+
* child is a ternary or `&&` is still a single-visual-root row —
|
|
1970
|
+
* `{done ? <li class="done"/> : <li/>}` renders exactly one `<li>`. A flat
|
|
1971
|
+
* `children.findIndex(c => c.type === 'element')` returns -1 there and
|
|
1972
|
+
* silently reproduces #2732's own symptom for a shape the fix was supposed
|
|
1973
|
+
* to cover.
|
|
1974
|
+
*
|
|
1975
|
+
* BOTH branches are marked, not just one: they are mutually exclusive at
|
|
1976
|
+
* render time, so whichever is taken carries the key, and marking only
|
|
1977
|
+
* `whenTrue` would drop it exactly when the condition is false.
|
|
1978
|
+
*/
|
|
1979
|
+
function markCarrierIn(node: IRNode): IRNode | null {
|
|
1980
|
+
if (node.type === 'element') {
|
|
1981
|
+
return { ...(node as IRElement), carriesDataKey: true }
|
|
1982
|
+
}
|
|
1983
|
+
if (node.type === 'conditional') {
|
|
1984
|
+
const cond = node as IRConditional
|
|
1985
|
+
const whenTrue = markCarrierIn(cond.whenTrue)
|
|
1986
|
+
const whenFalse = markCarrierIn(cond.whenFalse)
|
|
1987
|
+
if (!whenTrue && !whenFalse) return null
|
|
1988
|
+
return { ...cond, whenTrue: whenTrue ?? cond.whenTrue, whenFalse: whenFalse ?? cond.whenFalse }
|
|
1989
|
+
}
|
|
1990
|
+
return null
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1917
1993
|
function transformFragment(
|
|
1918
1994
|
node: ts.JsxFragment,
|
|
1919
1995
|
ctx: TransformContext
|
|
@@ -1938,7 +2014,7 @@ function transformFragment(
|
|
|
1938
2014
|
|
|
1939
2015
|
return {
|
|
1940
2016
|
type: 'fragment',
|
|
1941
|
-
children,
|
|
2017
|
+
children: needsScopeComment ? markDataKeyCarrier(children) : children,
|
|
1942
2018
|
transparent: isTransparent || undefined,
|
|
1943
2019
|
needsScopeComment,
|
|
1944
2020
|
loc: getSourceLocation(node, ctx.sourceFile, ctx.filePath),
|
|
@@ -6999,6 +7075,100 @@ function getStringValue(node: ts.Expression): string | null {
|
|
|
6999
7075
|
// Component Props Processing
|
|
7000
7076
|
// =============================================================================
|
|
7001
7077
|
|
|
7078
|
+
/**
|
|
7079
|
+
* Strip every legal-in-.tsx TRANSPARENT TS wrapper around an expression —
|
|
7080
|
+
* parens, `as`, `satisfies`, and postfix non-null `!` — repeatedly, so a
|
|
7081
|
+
* stack of them (`(x as any)!`) unwraps in one call. None of these change
|
|
7082
|
+
* the RUNTIME value; they are compile-time-only annotations TypeScript
|
|
7083
|
+
* erases, so a caller checking "is this JSX" (or "does this ternary/array
|
|
7084
|
+
* wrap JSX") must see through all of them to avoid false negatives (#2703
|
|
7085
|
+
* Copilot review on #2667: `header={cond ? (<a/> as any) : (<b/> as any)}`
|
|
7086
|
+
* unwrapped only parens, so the `as`-wrapped shape slipped past the naked-
|
|
7087
|
+
* wrapper refusal and still spliced raw JSX into the client bundle).
|
|
7088
|
+
* Angle-bracket type assertions (`<any>x`) are illegal in `.tsx` — the
|
|
7089
|
+
* `<` is always JSX — so there is no fourth wrapper kind to handle here.
|
|
7090
|
+
*/
|
|
7091
|
+
function unwrapTransparentTsWrappers(node: ts.Expression): ts.Expression {
|
|
7092
|
+
let n = node
|
|
7093
|
+
while (
|
|
7094
|
+
ts.isParenthesizedExpression(n) ||
|
|
7095
|
+
ts.isAsExpression(n) ||
|
|
7096
|
+
ts.isSatisfiesExpression(n) ||
|
|
7097
|
+
ts.isNonNullExpression(n)
|
|
7098
|
+
) {
|
|
7099
|
+
n = n.expression
|
|
7100
|
+
}
|
|
7101
|
+
return n
|
|
7102
|
+
}
|
|
7103
|
+
|
|
7104
|
+
/**
|
|
7105
|
+
* Whether `node` (a `ConditionalExpression` or `ArrayLiteralExpression`
|
|
7106
|
+
* already confirmed by the caller) has JSX syntax somewhere inside it —
|
|
7107
|
+
* a ternary arm that is (or recursively resolves to) a JSX element/
|
|
7108
|
+
* fragment, or an array element that is one. Used by #2667's naked-
|
|
7109
|
+
* wrapper refusal: a ternary/array prop initializer with NO JSX inside
|
|
7110
|
+
* (`disabled={cond ? a : b}`, `items={[a, b]}`) is ordinary and must keep
|
|
7111
|
+
* compiling exactly as before — only the JSX-carrying shape is refused.
|
|
7112
|
+
*
|
|
7113
|
+
* Deliberately narrow, mirroring `expressionContainsJsx`'s sibling scope
|
|
7114
|
+
* in `analyzer.ts`-adjacent code: only descends through transparent TS
|
|
7115
|
+
* wrappers (`unwrapTransparentTsWrappers`), `ConditionalExpression` arms,
|
|
7116
|
+
* and `ArrayLiteralExpression` elements (spread elements unwrapped too)
|
|
7117
|
+
* — the two wrapper shapes the issue names, each itself possibly
|
|
7118
|
+
* TS-wrapped (`(cond ? <a/> : <b/>) as any`, `cond ? (<a/> as any) : <b/>`,
|
|
7119
|
+
* #2703). It does not chase JSX through arbitrary call arguments, object
|
|
7120
|
+
* literals, or logical expressions; those are out of this refusal's
|
|
7121
|
+
* scope and stay on the pre-existing (working, non-JSX) expression path.
|
|
7122
|
+
*/
|
|
7123
|
+
function expressionWrapsJsx(node: ts.Expression): boolean {
|
|
7124
|
+
const n = unwrapTransparentTsWrappers(node)
|
|
7125
|
+
if (ts.isJsxElement(n) || ts.isJsxSelfClosingElement(n) || ts.isJsxFragment(n)) return true
|
|
7126
|
+
if (ts.isConditionalExpression(n)) {
|
|
7127
|
+
return expressionWrapsJsx(n.whenTrue) || expressionWrapsJsx(n.whenFalse)
|
|
7128
|
+
}
|
|
7129
|
+
if (ts.isArrayLiteralExpression(n)) {
|
|
7130
|
+
return n.elements.some((el) => expressionWrapsJsx(ts.isSpreadElement(el) ? el.expression : el))
|
|
7131
|
+
}
|
|
7132
|
+
return false
|
|
7133
|
+
}
|
|
7134
|
+
|
|
7135
|
+
/**
|
|
7136
|
+
* Refuse a component prop whose initializer is a ternary/array literally
|
|
7137
|
+
* wrapping JSX (#2667) — see `expressionWrapsJsx`'s docstring and the call
|
|
7138
|
+
* site above for why this can't fall through to the plain `expression`
|
|
7139
|
+
* AttrValue path (raw JSX syntax would splice into the emitted client
|
|
7140
|
+
* JS) and why the fragment-wrap escape is NOT offered here (BF021 message
|
|
7141
|
+
* below explains the unsoundness inline; see #2667's tracking issue for
|
|
7142
|
+
* the full door-inventory finding).
|
|
7143
|
+
*/
|
|
7144
|
+
function reportNakedJsxWrapperProp(
|
|
7145
|
+
ctx: TransformContext,
|
|
7146
|
+
attr: ts.JsxAttribute,
|
|
7147
|
+
propName: string,
|
|
7148
|
+
jsxExpr: ts.Expression,
|
|
7149
|
+
): void {
|
|
7150
|
+
const shape = ts.isConditionalExpression(jsxExpr) ? 'a ternary' : 'an array literal'
|
|
7151
|
+
ctx.analyzer.errors.push(
|
|
7152
|
+
createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(attr, ctx.sourceFile, ctx.filePath), {
|
|
7153
|
+
message:
|
|
7154
|
+
`Prop '${propName}' is ${shape} wrapping JSX (${jsxExpr.getText(ctx.sourceFile)}). ` +
|
|
7155
|
+
`This shape is not compiled — only a JSX element/fragment given DIRECTLY as the prop value is.`,
|
|
7156
|
+
suggestion: {
|
|
7157
|
+
message:
|
|
7158
|
+
`Move the conditional/array out of the prop position: compute it in a local ` +
|
|
7159
|
+
`const and pass it as the component's children instead of a named prop ` +
|
|
7160
|
+
`(e.g. const ${propName} = ${jsxExpr.getText(ctx.sourceFile)}; <Comp>{${propName}}</Comp>). ` +
|
|
7161
|
+
`Wrapping the ternary/array in a fragment at the prop position ` +
|
|
7162
|
+
`(${propName}={<>{${jsxExpr.getText(ctx.sourceFile)}}</>}) is NOT a safe escape here: it compiles, ` +
|
|
7163
|
+
`but the child's own reactive prop getter receives the branch's HTML unbranded and re-escapes it as ` +
|
|
7164
|
+
`text on the child's very next reactive run, corrupting the DOM (a narrower gap #2651's door ` +
|
|
7165
|
+
`inventory left open — tracked separately).`,
|
|
7166
|
+
escape: [{ kind: 'rewrite' }],
|
|
7167
|
+
},
|
|
7168
|
+
}),
|
|
7169
|
+
)
|
|
7170
|
+
}
|
|
7171
|
+
|
|
7002
7172
|
function processComponentProps(
|
|
7003
7173
|
attributes: ts.JsxAttributes,
|
|
7004
7174
|
ctx: TransformContext
|
|
@@ -7015,14 +7185,14 @@ function processComponentProps(
|
|
|
7015
7185
|
|
|
7016
7186
|
const name = attr.name.getText(ctx.sourceFile)
|
|
7017
7187
|
|
|
7018
|
-
// JSX element/fragment as prop value: controls={<select />}
|
|
7019
|
-
// controls={(<div/>)}
|
|
7020
|
-
//
|
|
7188
|
+
// JSX element/fragment as prop value: controls={<select />},
|
|
7189
|
+
// controls={(<div/>)}, or controls={<div/> as any} (#2703 — the
|
|
7190
|
+
// entity is still directly JSX; `as`/`satisfies`/`!` are type-only
|
|
7191
|
+
// and erased, so this must classify identically to the bare form).
|
|
7192
|
+
// Carried as a `jsx-children` AttrValue variant so adapters render
|
|
7193
|
+
// the JSX inline rather than passing a string.
|
|
7021
7194
|
if (attr.initializer && ts.isJsxExpression(attr.initializer) && attr.initializer.expression) {
|
|
7022
|
-
|
|
7023
|
-
while (ts.isParenthesizedExpression(jsxExpr)) {
|
|
7024
|
-
jsxExpr = jsxExpr.expression
|
|
7025
|
-
}
|
|
7195
|
+
const jsxExpr = unwrapTransparentTsWrappers(attr.initializer.expression)
|
|
7026
7196
|
if (ts.isJsxElement(jsxExpr) || ts.isJsxSelfClosingElement(jsxExpr) || ts.isJsxFragment(jsxExpr)) {
|
|
7027
7197
|
const prevInsideComponentChildren = ctx.insideComponentChildren
|
|
7028
7198
|
ctx.insideComponentChildren = true
|
|
@@ -7037,6 +7207,31 @@ function processComponentProps(
|
|
|
7037
7207
|
continue
|
|
7038
7208
|
}
|
|
7039
7209
|
}
|
|
7210
|
+
|
|
7211
|
+
// #2667: a ternary or array LITERALLY WRAPPING JSX at this prop
|
|
7212
|
+
// position (`header={cond ? <a/> : <b/>}`, `header={[<a/>, <b/>]}`)
|
|
7213
|
+
// is neither the direct-element shape above nor a plain value
|
|
7214
|
+
// expression — it only classifies as `jsx-children` when the JSX is
|
|
7215
|
+
// hoisted behind a fragment (`header={<>{cond ? <a/> : <b/>}</>}`,
|
|
7216
|
+
// `unwrapHoistedFragment` above). Left undetected, this falls to the
|
|
7217
|
+
// plain `expression` path below, which stringifies the initializer's
|
|
7218
|
+
// SOURCE TEXT — literal JSX syntax spliced into the emitted client
|
|
7219
|
+
// JS (invalid at runtime; the #2651 door inventory's discovery).
|
|
7220
|
+
// Refuse loudly instead of guessing a lowering: the fragment-wrap
|
|
7221
|
+
// escape this diagnostic once considered recommending turns out to
|
|
7222
|
+
// be unsound for the same shape (`isSingleElementJsxChildren`'s
|
|
7223
|
+
// docstring in `ir-to-client-js/collect-elements.ts` — a
|
|
7224
|
+
// conditional-in-fragment reaches `initChild`'s getter UNbranded,
|
|
7225
|
+
// corrupting the child's DOM the moment its own reactive effect
|
|
7226
|
+
// first reads the prop), so only the children-passthrough escape is
|
|
7227
|
+
// offered.
|
|
7228
|
+
if (
|
|
7229
|
+
(ts.isConditionalExpression(jsxExpr) || ts.isArrayLiteralExpression(jsxExpr)) &&
|
|
7230
|
+
expressionWrapsJsx(jsxExpr)
|
|
7231
|
+
) {
|
|
7232
|
+
reportNakedJsxWrapperProp(ctx, attr, name, jsxExpr)
|
|
7233
|
+
continue
|
|
7234
|
+
}
|
|
7040
7235
|
}
|
|
7041
7236
|
|
|
7042
7237
|
let value = getAttributeValue(attr, ctx)
|
package/src/types.ts
CHANGED
|
@@ -347,6 +347,20 @@ export interface IRElement {
|
|
|
347
347
|
children: IRNode[]
|
|
348
348
|
slotId: string | null
|
|
349
349
|
needsScope: boolean
|
|
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
|
|
350
364
|
/**
|
|
351
365
|
* Page-lifecycle boundary id for an element lowered from `<Region>`
|
|
352
366
|
* (spec/router.md). Set only on region host elements; adapters emit it as
|