@symbiote-native/solid 1.0.0 → 3.0.0
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/README.md +159 -86
- package/babel-preset.cjs +0 -5
- package/build/components/activity-indicator-props.d.ts +5 -0
- package/build/components/activity-indicator-props.js +1 -0
- package/build/components/button-props.d.ts +5 -0
- package/build/components/button-props.js +1 -0
- package/build/components/flat-list/index.jsx +4 -4
- package/build/components/{image-background.d.ts → image-background-props.d.ts} +2 -3
- package/build/components/image-background-props.js +1 -0
- package/build/components/image-props.d.ts +5 -0
- package/build/components/image-props.js +6 -0
- package/build/components/{input-accessory-view.d.ts → input-accessory-view-props.d.ts} +1 -2
- package/build/components/input-accessory-view-props.js +5 -0
- package/build/components/keyboard-avoiding-view.jsx +15 -6
- package/build/components/modal.jsx +6 -6
- package/build/components/{pressable.d.ts → pressable-props.d.ts} +10 -7
- package/build/components/pressable-props.js +13 -0
- package/build/components/{refresh-control.d.ts → refresh-control-props.d.ts} +3 -3
- package/build/components/refresh-control-props.js +7 -0
- package/build/components/safe-area-view-props.d.ts +10 -0
- package/build/components/safe-area-view-props.js +5 -0
- package/build/components/{scroll-view/shared.d.ts → scroll-view-props.d.ts} +7 -13
- package/build/components/scroll-view-props.js +29 -0
- package/build/components/switch-props.d.ts +6 -0
- package/build/components/switch-props.js +5 -0
- package/build/components/text-input-props.d.ts +8 -0
- package/build/components/text-input-props.js +1 -0
- package/build/components/{text.d.ts → text-props.d.ts} +3 -3
- package/build/components/text-props.js +11 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +9 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback-props.js +1 -0
- package/build/components/touchable-props.d.ts +20 -0
- package/build/components/touchable-props.js +1 -0
- package/build/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +7 -0
- package/build/components/touchable-without-feedback/touchable-without-feedback-props.js +1 -0
- package/build/components/{view.d.ts → view-props.d.ts} +2 -3
- package/build/components/view-props.js +11 -0
- package/build/components/virtualized-list/index.android.js +5 -4
- package/build/components/virtualized-list/index.ios.js +6 -6
- package/build/components/virtualized-list/shared.d.ts +1 -3
- package/build/components/virtualized-list/shared.jsx +95 -102
- package/build/components/virtualized-list/sticky-header.d.ts +6 -0
- package/build/components/{scroll-view → virtualized-list}/sticky-header.jsx +28 -36
- package/build/components.d.ts +20 -30
- package/build/components.js +6 -33
- package/build/create-portal/index.jsx +3 -3
- package/build/descriptor-to-solid.js +1 -1
- package/build/host-instance.js +2 -2
- package/build/index.d.ts +1 -0
- package/build/index.js +11 -3
- package/build/jsx-runtime.d.ts +39 -2
- package/build/modules/animated/index.d.ts +26 -23
- package/build/modules/animated/index.js +16 -22
- package/build/modules/image.d.ts +3 -0
- package/build/modules/image.js +12 -0
- package/build/register.d.ts +1 -1
- package/build/register.js +12 -28
- package/build/renderer.js +74 -61
- package/build/utils/stable-keys.d.ts +1 -1
- package/build/utils/stable-keys.js +6 -5
- package/metro-css-parser.cjs +2 -1
- package/package.json +6 -11
- package/babel-lower-host-primitives.cjs +0 -323
- package/build/components/activity-indicator/index.android.d.ts +0 -2
- package/build/components/activity-indicator/index.android.js +0 -11
- package/build/components/activity-indicator/index.d.ts +0 -1
- package/build/components/activity-indicator/index.ios.d.ts +0 -2
- package/build/components/activity-indicator/index.ios.js +0 -10
- package/build/components/activity-indicator/index.js +0 -4
- package/build/components/activity-indicator/shared.d.ts +0 -8
- package/build/components/activity-indicator/shared.js +0 -51
- package/build/components/button.d.ts +0 -7
- package/build/components/button.jsx +0 -45
- package/build/components/image-background.jsx +0 -74
- package/build/components/image.d.ts +0 -9
- package/build/components/image.js +0 -75
- package/build/components/input-accessory-view.jsx +0 -39
- package/build/components/pressable.jsx +0 -211
- package/build/components/refresh-control.jsx +0 -36
- package/build/components/safe-area-view.d.ts +0 -11
- package/build/components/safe-area-view.jsx +0 -24
- package/build/components/scroll-view/index.android.d.ts +0 -2
- package/build/components/scroll-view/index.android.js +0 -11
- package/build/components/scroll-view/index.d.ts +0 -2
- package/build/components/scroll-view/index.ios.d.ts +0 -2
- package/build/components/scroll-view/index.ios.js +0 -8
- package/build/components/scroll-view/index.js +0 -9
- package/build/components/scroll-view/shared.jsx +0 -334
- package/build/components/scroll-view/sticky-header.d.ts +0 -18
- package/build/components/switch/index.android.d.ts +0 -2
- package/build/components/switch/index.android.js +0 -12
- package/build/components/switch/index.d.ts +0 -1
- package/build/components/switch/index.ios.d.ts +0 -2
- package/build/components/switch/index.ios.js +0 -11
- package/build/components/switch/index.js +0 -4
- package/build/components/switch/shared.d.ts +0 -11
- package/build/components/switch/shared.js +0 -87
- package/build/components/text-input.d.ts +0 -10
- package/build/components/text-input.js +0 -290
- package/build/components/text.jsx +0 -27
- package/build/components/touchable/index.d.ts +0 -22
- package/build/components/touchable/index.jsx +0 -247
- package/build/components/touchable-native-feedback/index.d.ts +0 -16
- package/build/components/touchable-native-feedback/index.jsx +0 -68
- package/build/components/view.jsx +0 -49
- package/build/state-style.d.ts +0 -1
- package/build/state-style.js +0 -13
package/build/renderer.js
CHANGED
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
// babel.transformSync over representative JSX (elements, dynamic text, <Show>, <For>, spread,
|
|
18
18
|
// ref) rather than read off the docs.
|
|
19
19
|
import { createRenderer } from 'solid-js/universal';
|
|
20
|
-
import { appendChild, createAnchor, createElement as createEngineElement, createRawText, dlog, insertBefore, removeChild as removeEngineChild, routeProp, setNodePressed, setProp as setEngineProp, setText as setEngineText,
|
|
21
|
-
import { descriptorFor } from '@symbiote-native/components';
|
|
20
|
+
import { appendChild, createAnchor, createElement as createEngineElement, createRawText, dlog, firstChildOf, insertBefore, isRawTextNode, isTextContainer, nextSiblingOf, parentOf, removeChild as removeEngineChild, routeProp, setNodePressed, setProp as setEngineProp, setText as setEngineText, textOf, toPublicInstance, SymbioteSurface, propOf, } from '@symbiote-native/engine';
|
|
21
|
+
import { descriptorFor, TEXT_INPUT_MULTILINE_TAG, } from '@symbiote-native/components';
|
|
22
22
|
function isSurface(node) {
|
|
23
23
|
return node instanceof SymbioteSurface;
|
|
24
24
|
}
|
|
25
25
|
function isRawText(node) {
|
|
26
|
-
return !isSurface(node) && node
|
|
26
|
+
return !isSurface(node) && isRawTextNode(node);
|
|
27
27
|
}
|
|
28
28
|
// One active surface per process. This is FORCED here, not chosen: the compiled-JSX contract above
|
|
29
29
|
// means every mutation arrives through module-level functions with no surface argument, so there is
|
|
@@ -50,11 +50,6 @@ function requestCommit() {
|
|
|
50
50
|
}
|
|
51
51
|
activeSurface.requestCommit();
|
|
52
52
|
}
|
|
53
|
-
function siblingsOf(node) {
|
|
54
|
-
if (node.parent !== undefined)
|
|
55
|
-
return node.parent.children;
|
|
56
|
-
return activeSurface?.children ?? [];
|
|
57
|
-
}
|
|
58
53
|
// Hoisted out of nodeOps and exported because descriptor-to-solid.ts needs the same text-update
|
|
59
54
|
// path for a Descriptor's string child, and createRenderer() only hands back the 11 names the JSX
|
|
60
55
|
// compiler imports — replaceText is not among them. Going through here rather than calling the
|
|
@@ -84,10 +79,7 @@ function asText(value) {
|
|
|
84
79
|
// has to DETACH its anchor host from the portal target on cleanup. Going through here rather than
|
|
85
80
|
// calling the engine's removeChild directly is what keeps the mutation paired with requestCommit(),
|
|
86
81
|
// and keeps the surface-vs-node branch in one place.
|
|
87
|
-
// The
|
|
88
|
-
// rather than the pressed styling. A `<Pressable>` the lowering left as a component still owns its
|
|
89
|
-
// own press machine, and one call here puts the node into its pressed state so `.btn:active`
|
|
90
|
-
// applies exactly as it would on a lowered tag.
|
|
82
|
+
// The `:active` half: one call here puts the node into its pressed state so `.btn:active` applies.
|
|
91
83
|
//
|
|
92
84
|
// Exported paired with requestCommit() for the same reason removeNode below is: the press arrives
|
|
93
85
|
// from a NATIVE EVENT, outside any renderer mutation, so nothing else schedules a commit. React's
|
|
@@ -110,45 +102,35 @@ export function removeNode(parent, node) {
|
|
|
110
102
|
removeEngineChild(parent, node);
|
|
111
103
|
requestCommit();
|
|
112
104
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
for (const [key, fold] of TEXT_FOLDS)
|
|
119
|
-
setEngineProp(node, key, fold(undefined));
|
|
120
|
-
}
|
|
121
|
-
// Seeding at CREATE is not enough, and the gap is device-only. A framework that clears a prop it
|
|
122
|
-
// set earlier hands us an explicit `undefined` at PATCH time, and the default has to come BACK
|
|
123
|
-
// rather than stay cleared — RN treats a missing prop and an explicit undefined alike, and only a
|
|
124
|
-
// literal `false` opts out of allowFontScaling. Two Map lookups on text nodes only, and none at
|
|
125
|
-
// all on a View, so it stays off the hot path.
|
|
126
|
-
function foldTextValue(node, key, value) {
|
|
127
|
-
if (!node.isText)
|
|
128
|
-
return value;
|
|
129
|
-
const fold = TEXT_FOLDS.get(key);
|
|
130
|
-
return fold === undefined ? value : fold(value);
|
|
131
|
-
}
|
|
132
|
-
// The alias fold, at the RENDERER and not only in the transform — the defect class Angular paid for
|
|
133
|
-
// twice on 2026-08-31. A lowered element inherits nothing the component wrapper did, and the
|
|
134
|
-
// compile-time rename in `babel-lower-host-primitives.cjs` covers exactly the call sites the
|
|
135
|
-
// transform REWROTE: `<View id={x} />` is fine (the attribute name is renamed before the preset
|
|
136
|
-
// compiles it, dynamic value included), but a hand-written `<symbiote-view id="x">` is not, and it
|
|
137
|
-
// committed `id` — a key Fabric does not know — while the component committed `nativeID`. Measured
|
|
138
|
-
// by mounting both forms and diffing committed key NAMES; totals were identical and said nothing.
|
|
105
|
+
// RN's two Text defaults left this renderer on 2026-09-18, and the per-key FOLD went with them. It
|
|
106
|
+
// was the last and subtlest of the three shapes this file tried: a create-time SEED (gone a month
|
|
107
|
+
// earlier, 6 000 wasted crossings per 1 000-row create), then a substitute-on-`undefined`, then a
|
|
108
|
+
// fold per key — because `?? 'tail'` has to catch a null too, and substituting only on `undefined`
|
|
109
|
+
// meant `<text ellipsizeMode={null}>` committed null, device-only and silent.
|
|
139
110
|
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
111
|
+
// All three were answering a question the layer below now answers for everyone: the rule reads the
|
|
112
|
+
// AUTHORED bag at payload time (`foldTextDefaults`, `SymbioteFabricProps.cpp`), where a null, an
|
|
113
|
+
// explicit `undefined` and an absent prop are all simply "not a value the author chose". The
|
|
114
|
+
// null case that cost this file two revisions is `ellipsize->isNull()` there, in one place, for
|
|
115
|
+
// every adapter.
|
|
116
|
+
// The `id` -> `nativeID` fold and its `aliasedNodes` WeakSet left this file on 2026-09-18. The
|
|
117
|
+
// memory was the right shape and the wrong LAYER: `routeProp` carries it now, so all five adapters
|
|
118
|
+
// resolve the precedence identically instead of three of them doing it three ways.
|
|
119
|
+
// `multiline` selects between TWO Fabric views, so the TAG decides and no prop write moves a node
|
|
120
|
+
// between them. An author writing `<text-input multiline>` instead of `<text-input-multiline>` gets
|
|
121
|
+
// the single-line view with a prop no ViewConfig on it declares — two silent divergences.
|
|
142
122
|
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
123
|
+
// The shared behavior (`core/components/src/behaviors/text-input.ts`) already makes the PAYLOAD
|
|
124
|
+
// follow the tag. The complaint has to live here instead of there: `foldPayload` runs inside the
|
|
125
|
+
// commit, so a throw from it lands a tick later as an uncaught exception with no frame naming the
|
|
126
|
+
// call site — measured, a test awaiting the mount sees `nothing committed` rather than the error.
|
|
127
|
+
const MULTILINE_PROP = 'multiline';
|
|
128
|
+
function assertMultilineMatchesTag(node, value) {
|
|
129
|
+
if ((value === true) === (propOf(node, MULTILINE_PROP) === true))
|
|
130
|
+
return;
|
|
131
|
+
throw new Error(`multiline={${String(value)}} contradicts the tag: <text-input> and ` +
|
|
132
|
+
`<text-input-multiline> are different Fabric views and no prop write moves a node ` +
|
|
133
|
+
`between them. Pick the tag (a runtime choice needs a <Show> around both).`);
|
|
152
134
|
}
|
|
153
135
|
const nodeOps = {
|
|
154
136
|
createElement(tag) {
|
|
@@ -159,8 +141,10 @@ const nodeOps = {
|
|
|
159
141
|
// nothing, and the press machine silently never attaches. Registering under the Fabric name
|
|
160
142
|
// instead would be worse: every plain `View` would get a press machine.
|
|
161
143
|
const node = createEngineElement(descriptor.component, descriptor.isText, tag);
|
|
162
|
-
|
|
163
|
-
|
|
144
|
+
// Only the multiline tag is seeded: writing `multiline: false` on the single-line one would add
|
|
145
|
+
// a key the wrapper's payload does not carry, i.e. a divergence in the other direction.
|
|
146
|
+
if (tag === TEXT_INPUT_MULTILINE_TAG)
|
|
147
|
+
setEngineProp(node, MULTILINE_PROP, true);
|
|
164
148
|
// Graft the imperative public-instance API (measure / setNativeProps / focus / …) onto the raw
|
|
165
149
|
// node so a `ref` to a host element exposes it exactly like React's getPublicInstance.
|
|
166
150
|
// toPublicInstance mutates in place and returns the SAME node identity, so the engine's commit
|
|
@@ -196,11 +180,13 @@ const nodeOps = {
|
|
|
196
180
|
setProperty(node, name, value) {
|
|
197
181
|
if (isSurface(node))
|
|
198
182
|
return;
|
|
183
|
+
if (name === MULTILINE_PROP)
|
|
184
|
+
assertMultilineMatchesTag(node, value);
|
|
199
185
|
// routeProp makes the prop-vs-event decision from the node's ViewConfig (onPress on a View
|
|
200
186
|
// becomes a listener; onTintColor on a Switch stays a prop), and centralizes the class+style
|
|
201
|
-
// merge. Shared with React and Vue — never re-implement an
|
|
202
|
-
// (symbiote-engine-core §2).
|
|
203
|
-
routeProp(node,
|
|
187
|
+
// merge and the `id` -> `nativeID` rename. Shared with React and Vue — never re-implement an
|
|
188
|
+
// `onX` check here (symbiote-engine-core §2).
|
|
189
|
+
routeProp(node, name, value);
|
|
204
190
|
requestCommit();
|
|
205
191
|
},
|
|
206
192
|
insertNode(parent, node, anchor) {
|
|
@@ -210,12 +196,12 @@ const nodeOps = {
|
|
|
210
196
|
if (isSurface(node))
|
|
211
197
|
return;
|
|
212
198
|
const before = anchor !== undefined && !isSurface(anchor) ? anchor : undefined;
|
|
213
|
-
if (isRawText(node) && (isSurface(parent) || !parent
|
|
199
|
+
if (isRawText(node) && (isSurface(parent) || !isTextContainer(parent))) {
|
|
214
200
|
// Fabric has no bare-text host: RCTRawText is only valid as a <Text> child. Reached by a
|
|
215
201
|
// dynamic expression that resolves to a string outside a <Text>, e.g.
|
|
216
202
|
// `<symbiote-view>{label()}</symbiote-view>`. Failing loudly at mount beats building an
|
|
217
203
|
// invalid tree that crashes deeper in native with a far less legible error.
|
|
218
|
-
throw new Error(`Text string "${
|
|
204
|
+
throw new Error(`Text string "${textOf(node) ?? ''}" must be rendered inside a <Text>`);
|
|
219
205
|
}
|
|
220
206
|
if (isSurface(parent)) {
|
|
221
207
|
if (before !== undefined)
|
|
@@ -232,10 +218,27 @@ const nodeOps = {
|
|
|
232
218
|
requestCommit();
|
|
233
219
|
},
|
|
234
220
|
removeNode,
|
|
221
|
+
// Reverse of `getFirstChild`'s `childHost` redirect: a child under a composed primitive's slot
|
|
222
|
+
// has `node.parent` pointing at the SLOT, but `cleanChildren`'s `isParent = getParentNode(el)
|
|
223
|
+
// === parent` compares against the OWNER — the same reference the JSX-level `insert()` call was
|
|
224
|
+
// given. Left un-redirected, that comparison is false for every child of a bare `<scroll-view>`
|
|
225
|
+
// (or any other slotted primitive) with a following sibling, so `isParent` never fires,
|
|
226
|
+
// `removeNode` is never called on a full array clear, and the old children are orphaned: still
|
|
227
|
+
// committed to Fabric, still retained, and re-diffed against on the next reconciliation —
|
|
228
|
+
// reproduced as unbounded RAM growth and an increasingly slow "Clear" on the benchmark screen.
|
|
229
|
+
// One level up only: `childHost` is always a DIRECT child the owner built (see `buildStructure`),
|
|
230
|
+
// never a deeper descendant.
|
|
235
231
|
getParentNode(node) {
|
|
236
232
|
if (isSurface(node))
|
|
237
233
|
return undefined;
|
|
238
|
-
|
|
234
|
+
const parent = parentOf(node) ?? activeSurface;
|
|
235
|
+
if (parent === undefined || isSurface(parent))
|
|
236
|
+
return parent;
|
|
237
|
+
const grandparent = parentOf(parent);
|
|
238
|
+
if (grandparent !== undefined && grandparent.childHost === parent) {
|
|
239
|
+
return grandparent;
|
|
240
|
+
}
|
|
241
|
+
return parent;
|
|
239
242
|
},
|
|
240
243
|
// Anchors are NOT filtered out of this or getNextSibling, deliberately. solid-js/universal keeps
|
|
241
244
|
// its own record of which nodes it put where and re-derives positions through these two lookups
|
|
@@ -243,15 +246,25 @@ const nodeOps = {
|
|
|
243
246
|
// getFirstChild(parent) to replace text in place). Hiding a node the runtime itself inserted
|
|
244
247
|
// desyncs that record from the real tree. Anchors are invisible to FABRIC — the commit walk skips
|
|
245
248
|
// them — not to tree traversal.
|
|
249
|
+
//
|
|
250
|
+
// `childHost` redirected, same as the engine's own `appendChild`/`insertBefore`/`removeChild`
|
|
251
|
+
// (`hostFor`, core/engine/src/node.ts): a composed primitive's real children live on the slot it
|
|
252
|
+
// built, not on the owner it was asked to insert into, and `insert()`'s reconciliation reads this
|
|
253
|
+
// to know what is ALREADY there before deciding what to insert/move/remove. Reading the owner's
|
|
254
|
+
// own (single-child) array here would have every dynamic child on a bare `<scroll-view>` diffed
|
|
255
|
+
// against the wrong node — invisible on VirtualizedList, which never calls `insert()` on the
|
|
256
|
+
// owner directly, and wrong for any app writing `<scroll-view>{dynamicChildren}</scroll-view>`.
|
|
246
257
|
getFirstChild(node) {
|
|
247
|
-
|
|
258
|
+
if (isSurface(node))
|
|
259
|
+
return node.children[0];
|
|
260
|
+
return firstChildOf(node.childHost ?? node);
|
|
248
261
|
},
|
|
249
262
|
getNextSibling(node) {
|
|
250
263
|
if (isSurface(node))
|
|
251
264
|
return undefined;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return
|
|
265
|
+
// The surface is handed over because a TOP-LEVEL node has no parent to read a sibling list
|
|
266
|
+
// from — the surface owns that list. The engine returns undefined rather than guessing.
|
|
267
|
+
return nextSiblingOf(node, activeSurface);
|
|
255
268
|
},
|
|
256
269
|
};
|
|
257
270
|
// The compiled-JSX contract. See the module header: these names are imported by generated code,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function withStableKeys(props: () =>
|
|
1
|
+
export declare function withStableKeys<TProps extends object>(props: () => TProps): () => Record<string, unknown>;
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
// the actual diffing. Never hand-roll that diff instead.
|
|
13
13
|
//
|
|
14
14
|
// Lives here rather than beside either caller because BOTH reach for it: descriptorToSolid wraps
|
|
15
|
-
// a render fn's Descriptor props, and
|
|
16
|
-
//
|
|
17
|
-
//
|
|
15
|
+
// a render fn's Descriptor props, and a component that spreads a folded bag onto a bare `<view>`
|
|
16
|
+
// (Pressable, KeyboardAvoidingView) wraps its own — the same function, the same two-branch key
|
|
17
|
+
// set. This is the one capability a bare tag does NOT inherit from the wrappers that used to own
|
|
18
|
+
// it, so any Solid component folding props through a whole-object transform belongs on it too.
|
|
18
19
|
export function withStableKeys(props) {
|
|
19
20
|
const seen = new Set();
|
|
20
21
|
return () => {
|
|
@@ -22,9 +23,9 @@ export function withStableKeys(props) {
|
|
|
22
23
|
const widened = {};
|
|
23
24
|
for (const key of seen)
|
|
24
25
|
widened[key] = undefined;
|
|
25
|
-
for (const key of Object.
|
|
26
|
+
for (const [key, value] of Object.entries(next)) {
|
|
26
27
|
seen.add(key);
|
|
27
|
-
widened[key] =
|
|
28
|
+
widened[key] = value;
|
|
28
29
|
}
|
|
29
30
|
return widened;
|
|
30
31
|
};
|
package/metro-css-parser.cjs
CHANGED
|
@@ -5,4 +5,5 @@
|
|
|
5
5
|
// .cjs, not .js: this package is "type": "module", but Metro's babelTransformerPath loading needs
|
|
6
6
|
// a require()-able file. createCssMetroTransformer() is a factory — Metro needs the actual
|
|
7
7
|
// {transform, getCacheKey} object it returns, not the css-parser package barrel.
|
|
8
|
-
module.exports =
|
|
8
|
+
module.exports =
|
|
9
|
+
require('@symbiote-native/css-parser').createCssMetroTransformer();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/solid",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "SymbioteNative's Solid adapter — a solid-js/universal createRenderer driving real native iOS/Android views through the same engine as every other adapter.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -49,16 +49,11 @@
|
|
|
49
49
|
"default": "./build/jsx-runtime.js"
|
|
50
50
|
},
|
|
51
51
|
"./babel-preset": "./babel-preset.cjs",
|
|
52
|
-
"./metro-css-parser": "./metro-css-parser.cjs"
|
|
53
|
-
"./state-style": {
|
|
54
|
-
"types": "./build/state-style.d.ts",
|
|
55
|
-
"default": "./build/state-style.js"
|
|
56
|
-
}
|
|
52
|
+
"./metro-css-parser": "./metro-css-parser.cjs"
|
|
57
53
|
},
|
|
58
54
|
"files": [
|
|
59
55
|
"build",
|
|
60
56
|
"babel-preset.cjs",
|
|
61
|
-
"babel-lower-host-primitives.cjs",
|
|
62
57
|
"metro-css-parser.cjs"
|
|
63
58
|
],
|
|
64
59
|
"publishConfig": {
|
|
@@ -67,19 +62,19 @@
|
|
|
67
62
|
"dependencies": {
|
|
68
63
|
"babel-preset-solid": "^1.9.12",
|
|
69
64
|
"typescript": "~6.0.0",
|
|
70
|
-
"@symbiote-native/components": "
|
|
65
|
+
"@symbiote-native/components": "3.0.0",
|
|
71
66
|
"@symbiote-native/css-parser": "0.5.0"
|
|
72
67
|
},
|
|
73
68
|
"peerDependencies": {
|
|
74
69
|
"solid-js": ">=1.9.0",
|
|
75
70
|
"react-native": ">=0.86",
|
|
76
|
-
"@symbiote-native/engine": "^0.
|
|
71
|
+
"@symbiote-native/engine": "^1.0.0"
|
|
77
72
|
},
|
|
78
73
|
"devDependencies": {
|
|
79
74
|
"@babel/core": "^7.25.2",
|
|
80
75
|
"solid-js": "^1.9.14",
|
|
81
|
-
"@symbiote-native/engine": "0.
|
|
82
|
-
"@symbiote-native/test-utils": "0.3.
|
|
76
|
+
"@symbiote-native/engine": "1.0.0",
|
|
77
|
+
"@symbiote-native/test-utils": "0.3.1"
|
|
83
78
|
},
|
|
84
79
|
"scripts": {
|
|
85
80
|
"typecheck": "tsc --build",
|
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
// Compile-time lowering of <View>/<Text> to the intrinsic tags the renderer maps to Fabric.
|
|
2
|
-
//
|
|
3
|
-
// WHY. Solid charges a props PROXY per component: `<View>` compiles to `createComponent(View, …)`,
|
|
4
|
-
// and the component body then runs splitProps + withStableKeys + mergeProps + spread. Measured
|
|
5
|
-
// 2026-08-23 on a 4 000-row create: keys/get/getOwnPropertyDescriptor traps 16.2% of the whole
|
|
6
|
-
// create, splitProps another 6.1%. Lowered, the same element compiles to `createElement(tag)` plus
|
|
7
|
-
// one `setProp` per attribute — no Proxy, no bag object, no spread render effect. Headless A/B with
|
|
8
|
-
// both arms compiled for real and createNode asserted equal at 36 002: -24.3% min / -30.0% median.
|
|
9
|
-
//
|
|
10
|
-
// The Vue twin is the nodeTransform in adapters/vue/metro-vue-transformer.cjs, and the two share
|
|
11
|
-
// their central rule: lower ONLY a tag this file imported from us, never a bare name match, or an
|
|
12
|
-
// app's own <View> gets rewritten.
|
|
13
|
-
//
|
|
14
|
-
// THE SECOND RULE IS THE ONE THAT KEEPS IT HONEST: lower only when the WHOLE attribute set is
|
|
15
|
-
// visible. The wrapper components do real work besides forwarding — resolveAccessibilityProps
|
|
16
|
-
// folds aria-*/role into the composite accessibilityState/accessibilityValue, and `id` is RN's
|
|
17
|
-
// W3C alias for `nativeID`. Deleting the wrapper without carrying those over breaks accessibility
|
|
18
|
-
// silently. `id` is a pure rename and is done below FROM THE SHARED SPEC; the aria fold is a BAG
|
|
19
|
-
// operation, so an
|
|
20
|
-
// element carrying aria-*/role — or a spread whose keys cannot be read — is left as a component.
|
|
21
|
-
// A folded-at-compile-time aria bag is a possible follow-up (the bag exists statically, so it
|
|
22
|
-
// would cost nothing at runtime); refusing is what makes shipping this safe without it.
|
|
23
|
-
//
|
|
24
|
-
// .cjs because Babel require()s it and this package is "type": "module".
|
|
25
|
-
|
|
26
|
-
// The tag map and the per-tag folds come from the SHARED spec, not from a copy here. Four
|
|
27
|
-
// transforms carried their own before it existed and they had already disagreed on `id` (see that
|
|
28
|
-
// file's `aliases` comment). Data only — every line of AST work below stays this adapter's, and so
|
|
29
|
-
// does the whole refusal mechanism; only the SPECIFICATION is shared.
|
|
30
|
-
const {
|
|
31
|
-
HOST_PRIMITIVES,
|
|
32
|
-
REFUSAL_CATEGORIES,
|
|
33
|
-
} = require('@symbiote-native/components/host-primitives');
|
|
34
|
-
|
|
35
|
-
const SOURCE = '@symbiote-native/solid';
|
|
36
|
-
|
|
37
|
-
// name -> a projection of the shared spec. Built once at plugin load, so the per-element path stays
|
|
38
|
-
// two Map lookups.
|
|
39
|
-
//
|
|
40
|
-
// This is a WHITELIST, and it fails silently in one direction: a field the shared spec grows and
|
|
41
|
-
// this list does not copy simply never reaches the detections, which then behave as if it were
|
|
42
|
-
// absent. Cost measured 2026-08-31 — `intrinsicWhen` was implemented end to end and every
|
|
43
|
-
// multiline case still lowered to the single-line tag, because the field was dropped here. Add a
|
|
44
|
-
// field to the spec, add it here.
|
|
45
|
-
const LOWERABLE = new Map(
|
|
46
|
-
Object.entries(HOST_PRIMITIVES).map(([name, spec]) => [
|
|
47
|
-
name,
|
|
48
|
-
{
|
|
49
|
-
intrinsic: spec.intrinsic,
|
|
50
|
-
aliases: new Map(Object.entries(spec.aliases)),
|
|
51
|
-
// Every primitive refuses on the bag fold and on anything unreadable. A primitive whose
|
|
52
|
-
// STATE the template can observe refuses on more, and only the spec knows which those are —
|
|
53
|
-
// `observesState` is set for a tag the shared spec marks that way, so a transform that has
|
|
54
|
-
// not implemented the extra detections simply never sees such a tag in its map.
|
|
55
|
-
observesState: spec.observesState === true,
|
|
56
|
-
// The two-intrinsic selector (`{ prop, intrinsic }`), absent for a primitive with one tag.
|
|
57
|
-
intrinsicWhen: spec.intrinsicWhen,
|
|
58
|
-
},
|
|
59
|
-
]),
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
function attributeName(attribute) {
|
|
63
|
-
const { name } = attribute;
|
|
64
|
-
if (name.type === 'JSXIdentifier') return name.name;
|
|
65
|
-
// JSXNamespacedName — `aria-label` parses as an identifier, but `xml:lang` style names land here.
|
|
66
|
-
return `${name.namespace.name}:${name.name.name}`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Every local name bound to a lowerable export of @symbiote-native/solid in THIS file, honouring
|
|
70
|
-
// `import { View as Box }`. A file that imports nothing from us yields an empty map and is skipped
|
|
71
|
-
// whole.
|
|
72
|
-
function lowerableLocalNames(programPath) {
|
|
73
|
-
const names = new Map();
|
|
74
|
-
programPath.node.body.forEach(statement => {
|
|
75
|
-
if (
|
|
76
|
-
statement.type !== 'ImportDeclaration' ||
|
|
77
|
-
statement.source.value !== SOURCE
|
|
78
|
-
)
|
|
79
|
-
return;
|
|
80
|
-
statement.specifiers.forEach(specifier => {
|
|
81
|
-
if (specifier.type !== 'ImportSpecifier') return;
|
|
82
|
-
const imported =
|
|
83
|
-
specifier.imported.type === 'Identifier'
|
|
84
|
-
? specifier.imported.name
|
|
85
|
-
: specifier.imported.value;
|
|
86
|
-
const spec = LOWERABLE.get(imported);
|
|
87
|
-
if (spec !== undefined) names.set(specifier.local.name, spec);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
return names;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// REFUSAL_CATEGORIES.renderPropChild — a child function that TAKES the state.
|
|
94
|
-
//
|
|
95
|
-
// ARITY, not `typeof`, and that distinction is the whole rule. A zero-argument arrow is an ordinary
|
|
96
|
-
// Solid child (`JSX.Element` covers `() => Element`), exactly as `<For>`'s map fn is; only a
|
|
97
|
-
// function that accepts the state argument is a render prop. Measured over `examples/solid`: 12 of
|
|
98
|
-
// the 19 `<Pressable>` sites pass a function child and TEN of those are zero-arity, so refusing on
|
|
99
|
-
// `typeof` alone would throw away ten lowerable sites — including `ActionButton`, whose one
|
|
100
|
-
// definition is instantiated 146 times.
|
|
101
|
-
function isRenderPropChild(child) {
|
|
102
|
-
if (child.type !== 'JSXExpressionContainer') return false;
|
|
103
|
-
const { expression } = child;
|
|
104
|
-
return (
|
|
105
|
-
(expression.type === 'ArrowFunctionExpression' ||
|
|
106
|
-
expression.type === 'FunctionExpression') &&
|
|
107
|
-
expression.params.length >= 1
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// A JSX element is lowerable only if every attribute is a plain, named one we can account for —
|
|
112
|
-
// plus, for a state-observing primitive, only if nothing in it reads that state.
|
|
113
|
-
function canLower(element, spec, types) {
|
|
114
|
-
const { openingElement } = element;
|
|
115
|
-
// Named attributes only — a spread cannot be enumerated
|
|
116
|
-
// (REFUSAL_CATEGORIES.unreadableAttributeSet).
|
|
117
|
-
//
|
|
118
|
-
// The category's STATED reason — that a transform cannot fold `id` -> `nativeID` inside a bag — is
|
|
119
|
-
// DEAD here, measured 2026-08-31: this adapter also folds at runtime (`renderer.ts`,
|
|
120
|
-
// `foldAliasKey`), which sees every key whatever shape it arrived in, and a lowered
|
|
121
|
-
// `<symbiote-view {...bag} />` commits a payload byte-identical to the wrapper's
|
|
122
|
-
// (`src/spread-fold-parity.test.tsx`). The refusal stands on two OTHER grounds it never named,
|
|
123
|
-
// both measured by lifting it and compiling:
|
|
124
|
-
//
|
|
125
|
-
// <TextInput {...bag} /> -> symbiote-text-input, whatever `bag.multiline` holds. The
|
|
126
|
-
// single-line tag is a different NATIVE VIEW and no later prop
|
|
127
|
-
// write repairs it (REFUSAL_CATEGORIES.dynamicIntrinsicChoice).
|
|
128
|
-
// <Pressable {...bag} /> -> no `activeStyle` emitted. The same functional style written
|
|
129
|
-
// directly emits it; inside a bag the expansion never runs, so a
|
|
130
|
-
// pressed style silently stops existing.
|
|
131
|
-
//
|
|
132
|
-
// So: a refuted rationale is not a refuted verdict. Do not lift this because the alias half is
|
|
133
|
-
// covered — check what ELSE reads the attribute list first.
|
|
134
|
-
//
|
|
135
|
-
// `role` / `aria-*` used to refuse here too, on the
|
|
136
|
-
// grounds that their fold needs to see sibling keys and a per-key element path cannot reproduce
|
|
137
|
-
// it. That was true of the ELEMENT path and is no longer true of anything: the fold moved into
|
|
138
|
-
// the engine (`core/engine/src/accessibility-props.ts`, called from `fabricProps` — the one place
|
|
139
|
-
// the whole bag is visible on both commit paths), so a lowered element gets it exactly as a
|
|
140
|
-
// wrapped one does. Proven on the committed payload, both arms, in `src/aria-fold-parity.test.tsx`.
|
|
141
|
-
const attributesAreReadable = openingElement.attributes.every(
|
|
142
|
-
attribute => attribute.type === 'JSXAttribute',
|
|
143
|
-
);
|
|
144
|
-
if (!attributesAreReadable) return false;
|
|
145
|
-
if (!selectsIntrinsicStatically(openingElement, spec)) return false;
|
|
146
|
-
// BEFORE the `observesState` early-return, not after. It rode below it while the only ref-refusing
|
|
147
|
-
// primitive was Pressable, which observes state — a coincidence, and TextInput broke it: it
|
|
148
|
-
// observes nothing, so the early-return fired first and a `<TextInput ref>` lowered anyway. The
|
|
149
|
-
// two questions are independent, so the checks are ordered by what they ask, not by what happened
|
|
150
|
-
// to work.
|
|
151
|
-
if (refWouldChangeSurface(spec) && openingElement.attributes.some(isInstanceBinding))
|
|
152
|
-
return false;
|
|
153
|
-
// Everything below is about a primitive whose STATE the template can observe.
|
|
154
|
-
if (!spec.observesState) return true;
|
|
155
|
-
// A functional `style` NEVER refuses, and since 2026-09-01 it is not rewritten either: the
|
|
156
|
-
// expression rides through unchanged and `routeProp` resolves it at both values of `pressed`
|
|
157
|
-
// (`isStyleCallback`, `core/engine/src/node.ts`). The build-time split that used to live here was
|
|
158
|
-
// removed after measuring it — `src/state-style-cost.bench.test.ts` — because it emitted TWO
|
|
159
|
-
// writes per node where the callback emits one, so it cost run time rather than saving it.
|
|
160
|
-
return !element.children.some(isRenderPropChild);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// REFUSAL_CATEGORIES.dynamicIntrinsicChoice.
|
|
164
|
-
//
|
|
165
|
-
// A primitive whose `intrinsicWhen` is set picks between TWO Fabric views by a prop —
|
|
166
|
-
// `symbiote-text-input` vs `symbiote-text-input-multiline`, which are different native views rather
|
|
167
|
-
// than one view with a flag (`core/components/src/view/render-text-input.ts`). The transform prints
|
|
168
|
-
// a STATIC tag name, so it can only make that choice when the value is a compile-time literal.
|
|
169
|
-
//
|
|
170
|
-
// The refusal is stricter than the neighbouring ones and the difference is worth keeping in view:
|
|
171
|
-
// an unreadable ATTRIBUTE VALUE means a prop arrives wrong, and a later write can still correct it.
|
|
172
|
-
// Here the wrong NATIVE VIEW is committed, and no subsequent prop write repairs that — the node
|
|
173
|
-
// would have to be destroyed and recreated. So a value this cannot read statically keeps the
|
|
174
|
-
// component, which resolves the choice at runtime where the value is known.
|
|
175
|
-
//
|
|
176
|
-
// IDENTITY, not truthiness. The render fn is `view.multiline ? MULTILINE : SINGLE`, so truthiness
|
|
177
|
-
// is what the RUNTIME does, and describing the compile-time check that way makes `multiline={1}`
|
|
178
|
-
// look like something this should resolve. It must not: only three shapes are accepted — a bare
|
|
179
|
-
// attribute is `true`, a boolean LITERAL is itself, an absent prop is `false` — and everything else
|
|
180
|
-
// refuses, a truthy non-boolean literal included. A check justified by truthiness would let `{1}`
|
|
181
|
-
// through at the next edit, which is exactly the silently-wrong native view the refusal exists to
|
|
182
|
-
// prevent. Recorded as identity in the shared spec for the same reason.
|
|
183
|
-
function staticTruthOf(openingElement, propName) {
|
|
184
|
-
const attribute = openingElement.attributes.find(
|
|
185
|
-
candidate =>
|
|
186
|
-
candidate.type === 'JSXAttribute' &&
|
|
187
|
-
attributeName(candidate) === propName,
|
|
188
|
-
);
|
|
189
|
-
if (attribute === undefined) return false;
|
|
190
|
-
if (attribute.value === null) return true;
|
|
191
|
-
if (attribute.value.type !== 'JSXExpressionContainer') return undefined;
|
|
192
|
-
const { expression } = attribute.value;
|
|
193
|
-
return expression.type === 'BooleanLiteral' ? expression.value : undefined;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function selectsIntrinsicStatically(openingElement, spec) {
|
|
197
|
-
return (
|
|
198
|
-
spec.intrinsicWhen === undefined ||
|
|
199
|
-
staticTruthOf(openingElement, spec.intrinsicWhen.prop) !== undefined
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function intrinsicFor(openingElement, spec) {
|
|
204
|
-
if (spec.intrinsicWhen === undefined) return spec.intrinsic;
|
|
205
|
-
return staticTruthOf(openingElement, spec.intrinsicWhen.prop) === true
|
|
206
|
-
? spec.intrinsicWhen.intrinsic
|
|
207
|
-
: spec.intrinsic;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// REFUSAL_CATEGORIES.instanceBoundDirective. The criterion is NOT "does the component expose a
|
|
211
|
-
// ref" — that was a proxy, and it held only while two primitives existed. The rule is: would
|
|
212
|
-
// lowering hand the app something DIFFERENT from what the component hands it? A lowering transform
|
|
213
|
-
// is an optimisation, and an optimisation that moves the observable surface in EITHER direction is
|
|
214
|
-
// a bug. Three shapes, one rule, all readable from the components' own props types:
|
|
215
|
-
//
|
|
216
|
-
// View, Text ref?: Ref<IHostInstance> lowered yields the same node -> lower
|
|
217
|
-
// Pressable no ref declared lowering would ADD a handle -> refuse
|
|
218
|
-
// TextInput ref?: Ref<ITextInputHandle> lowering would SWAP the handle -> refuse
|
|
219
|
-
//
|
|
220
|
-
// TextInput is the case that exposed the proxy: it declares a ref, so the old criterion said
|
|
221
|
-
// "lower", and a lowered `<TextInput ref>` silently dropped `clear`, `isFocused` and
|
|
222
|
-
// `setSelection` — the component's handle replaced by a bare public instance. Costing an app three
|
|
223
|
-
// methods is exactly as wrong as granting it one it never had.
|
|
224
|
-
//
|
|
225
|
-
// PER-ADAPTER by construction, which is why this is a list here and not a field on the shared
|
|
226
|
-
// spec: what a ref yields is a fact about THIS adapter's props types, and a correct adapter can
|
|
227
|
-
// answer differently — Vue refuses on every primitive, because a Vue template ref yields the
|
|
228
|
-
// component instance rather than the host node whatever the component declares.
|
|
229
|
-
//
|
|
230
|
-
// Kept honest by `ref-refusal-matches-components.test.ts`, which re-derives the answer from each
|
|
231
|
-
// component source: a hand-written list of adapter members is what went stale three times this
|
|
232
|
-
// month.
|
|
233
|
-
// INVERTED 2026-09-01, and the inversion is the point rather than the contents. This was a
|
|
234
|
-
// denylist of intrinsics that refuse a `ref`, so every primitive added to the spec defaulted to
|
|
235
|
-
// LOWERING one — and the newest member falls out of a hand-written list every time, which is the
|
|
236
|
-
// failure mode this repo has now recorded four times. As a denylist that default is a correctness
|
|
237
|
-
// bug (a lowered element hands back an object the component never did); as an allowlist the same
|
|
238
|
-
// omission costs only coverage, which is the direction a default should fail in.
|
|
239
|
-
//
|
|
240
|
-
// A name earns its place here by the criterion in `ref-refusal-matches-components.test.ts`: the
|
|
241
|
-
// component hands a `ref` the ENGINE NODE, either by declaring `ref?: Ref<IHostInstance>` or by
|
|
242
|
-
// rendering through `descriptorToSolid`, whose `spread` calls a bag ref with the node it built. Any
|
|
243
|
-
// other answer — a different handle, or no ref at all — means lowering would move the surface.
|
|
244
|
-
const INTRINSICS_YIELDING_HOST_REF = new Set([
|
|
245
|
-
'symbiote-view',
|
|
246
|
-
'symbiote-text',
|
|
247
|
-
'symbiote-image',
|
|
248
|
-
]);
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
function refWouldChangeSurface(spec) {
|
|
252
|
-
return !INTRINSICS_YIELDING_HOST_REF.has(spec.intrinsic);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function isInstanceBinding(attribute) {
|
|
256
|
-
return (
|
|
257
|
-
attribute.type === 'JSXAttribute' && attributeName(attribute) === 'ref'
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Per TAG, not global: the spec gives each primitive its own alias map, and reading it per tag is
|
|
262
|
-
// what keeps this honest if they ever stop agreeing.
|
|
263
|
-
function renameAliasedAttributes(openingElement, aliases, types) {
|
|
264
|
-
openingElement.attributes.forEach(attribute => {
|
|
265
|
-
// A spread has no name, and the author's own spread refuses in `canLower`, so nothing this
|
|
266
|
-
// loop sees is one. It used to be reachable: the state-style pass emitted a spread for an
|
|
267
|
-
// opaque style, and that pass is gone.
|
|
268
|
-
if (attribute.type !== 'JSXAttribute') return;
|
|
269
|
-
const renamed = aliases.get(attributeName(attribute));
|
|
270
|
-
if (renamed !== undefined) attribute.name = types.jsxIdentifier(renamed);
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
module.exports = function lowerHostPrimitives({ types }) {
|
|
275
|
-
return {
|
|
276
|
-
name: 'symbiote-solid-lower-host-primitives',
|
|
277
|
-
visitor: {
|
|
278
|
-
Program(programPath) {
|
|
279
|
-
const names = lowerableLocalNames(programPath);
|
|
280
|
-
if (names.size === 0) return;
|
|
281
|
-
programPath.traverse({
|
|
282
|
-
JSXElement(elementPath) {
|
|
283
|
-
const { openingElement, closingElement } = elementPath.node;
|
|
284
|
-
const tag = openingElement.name;
|
|
285
|
-
if (tag.type !== 'JSXIdentifier') return;
|
|
286
|
-
const spec = names.get(tag.name);
|
|
287
|
-
if (spec === undefined) return;
|
|
288
|
-
// A shadowing local binding (a parameter or a `const View = …` in scope) is NOT our
|
|
289
|
-
// import, even though the name matches. Only rewrite when the binding at this use site
|
|
290
|
-
// is still the module-level import.
|
|
291
|
-
const binding = elementPath.scope.getBinding(tag.name);
|
|
292
|
-
if (binding === undefined || binding.kind !== 'module') return;
|
|
293
|
-
if (!canLower(elementPath.node, spec, types)) return;
|
|
294
|
-
// Read BEFORE the passes below rewrite attributes — `renameAliasedAttributes` could
|
|
295
|
-
// in principle rename the selector out from under the choice.
|
|
296
|
-
const intrinsic = intrinsicFor(openingElement, spec);
|
|
297
|
-
renameAliasedAttributes(openingElement, spec.aliases, types);
|
|
298
|
-
openingElement.name = types.jsxIdentifier(intrinsic);
|
|
299
|
-
if (closingElement)
|
|
300
|
-
closingElement.name = types.jsxIdentifier(intrinsic);
|
|
301
|
-
},
|
|
302
|
-
});
|
|
303
|
-
},
|
|
304
|
-
},
|
|
305
|
-
};
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
// Attached AFTER the plugin assignment on purpose: `module.exports = fn` replaces the object, so a
|
|
309
|
-
// named export written above it is silently discarded. Exists only so the test below can compare
|
|
310
|
-
// this list against the component sources.
|
|
311
|
-
module.exports.INTRINSICS_YIELDING_HOST_REF = INTRINSICS_YIELDING_HOST_REF;
|
|
312
|
-
|
|
313
|
-
// The spec fields this transform reads, named so `spec-projection-covers-fields.test.ts` can check
|
|
314
|
-
// the whitelist above against what `HOST_PRIMITIVES` actually carries. `defaults` is deliberately
|
|
315
|
-
// absent: prop defaults are seeded at RUNTIME by the renderer (`seedTextDefaults`), where a patch
|
|
316
|
-
// that clears a prop can restore them — a compile-time copy would cover only the initial render.
|
|
317
|
-
module.exports.SPEC_FIELDS_READ = [
|
|
318
|
-
'intrinsic',
|
|
319
|
-
'aliases',
|
|
320
|
-
'observesState',
|
|
321
|
-
'intrinsicWhen',
|
|
322
|
-
];
|
|
323
|
-
module.exports.SPEC_FIELDS_IGNORED = ['defaults'];
|