@symbiote-native/engine 1.3.0 → 1.4.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/build/accessibility-props.d.ts +0 -11
- package/build/accessibility-props.js +30 -68
- package/build/animated/graph.js +1 -1
- package/build/animated/leaf-lifecycle.js +2 -2
- package/build/asset-source-resolver.d.ts +2 -0
- package/build/asset-source-resolver.js +13 -0
- package/build/back-handler/index.d.ts +1 -5
- package/build/back-handler/index.js +0 -6
- package/build/debug.js +8 -22
- package/build/dispatch.js +3 -9
- package/build/events/delivery.d.ts +9 -0
- package/build/events/delivery.js +143 -0
- package/build/events/index.js +199 -660
- package/build/events/names.d.ts +24 -0
- package/build/events/names.js +80 -0
- package/build/events/press.d.ts +20 -0
- package/build/events/press.js +89 -0
- package/build/events/responder.d.ts +6 -0
- package/build/events/responder.js +124 -0
- package/build/fabric-props.js +74 -179
- package/build/fabric.d.ts +0 -13
- package/build/fabric.js +18 -38
- package/build/host-access.d.ts +1 -128
- package/build/host-access.js +96 -205
- package/build/host-behavior.d.ts +0 -100
- package/build/host-behavior.js +125 -311
- package/build/image-loader.js +10 -23
- package/build/image-source-resolver.js +3 -7
- package/build/image-source-write.d.ts +0 -11
- package/build/image-source-write.js +14 -34
- package/build/imperative.d.ts +2 -28
- package/build/imperative.js +60 -93
- package/build/index.d.ts +6 -2
- package/build/index.js +29 -39
- package/build/mutation-buffer.d.ts +3 -177
- package/build/mutation-buffer.js +162 -316
- package/build/native-engine.d.ts +6 -102
- package/build/native-engine.js +60 -141
- package/build/native-events.js +9 -18
- package/build/native-tree-host.d.ts +0 -21
- package/build/native-tree-host.js +15 -31
- package/build/node-events.d.ts +11 -0
- package/build/node-events.js +145 -0
- package/build/node-instance.d.ts +8 -0
- package/build/node-instance.js +168 -0
- package/build/node-props.d.ts +13 -0
- package/build/node-props.js +131 -0
- package/build/node-route.d.ts +2 -0
- package/build/node-route.js +151 -0
- package/build/node-style.d.ts +15 -0
- package/build/node-style.js +214 -0
- package/build/node-tree.d.ts +6 -0
- package/build/node-tree.js +159 -0
- package/build/node-types.d.ts +70 -0
- package/build/node-types.js +36 -0
- package/build/node.d.ts +7 -309
- package/build/node.js +9 -1564
- package/build/post-commit.js +3 -8
- package/build/process-aspect-ratio.js +3 -7
- package/build/process-background-longhands.js +10 -19
- package/build/process-filter.js +11 -19
- package/build/process-font-variant.js +3 -7
- package/build/registry.d.ts +0 -33
- package/build/registry.js +22 -57
- package/build/report-error.js +4 -18
- package/build/structured-style.d.ts +0 -9
- package/build/structured-style.js +16 -31
- package/build/styles.js +3 -6
- package/build/surface.d.ts +0 -26
- package/build/surface.js +29 -76
- package/build/text-input-state.js +4 -8
- package/build/touch-history.js +5 -11
- package/build/tree-host.d.ts +7 -270
- package/build/tree-host.js +63 -153
- package/build/view-config.js +17 -37
- package/cpp/SymbioteEngineBindings.cpp +19 -18
- package/cpp/SymbioteTree.cpp +81 -156
- package/cpp/SymbioteTree.h +6 -0
- package/package.json +2 -2
package/build/host-access.js
CHANGED
|
@@ -1,39 +1,21 @@
|
|
|
1
|
-
// Host access — the
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// and §7b.
|
|
20
|
-
//
|
|
21
|
-
// So these accessors exist to give adapters the navigation their seams require WITHOUT handing them
|
|
22
|
-
// the node's fields. There are no fields left to hand them: since 2026-09-08 every one of these is a
|
|
23
|
-
// read into the TREE HOST (`tree-host.ts`) — native on device, the TypeScript applier headlessly —
|
|
24
|
-
// because JS holds no tree at all. The historical note: every seam used to read `node.parent` /
|
|
25
|
-
// `node.children` directly (8 reads in Solid's renderer, 12 in Vue's, 13 in Angular's), which
|
|
26
|
-
// couples each adapter to a shape that no longer exists on this side of the wire.
|
|
27
|
-
//
|
|
28
|
-
// Each read FLUSHES the mutation buffer first. A reconciler navigates the tree it is mid-way through
|
|
29
|
-
// BUILDING, so the host has to be told about the ops recorded since the last commit before it can
|
|
30
|
-
// answer. That is what `flushOps` is for, and it is why these are not simply the host's own methods.
|
|
31
|
-
//
|
|
32
|
-
// A survey of the three adapters that navigate (Vue, Angular, Solid) confirmed none can answer from
|
|
33
|
-
// state it already holds — Vue's `RendererOptions` callbacks cannot see the vnode tree, Solid's
|
|
34
|
-
// `universal.js` deliberately re-derives from the host rather than trust its own record, and Angular
|
|
35
|
-
// calls `parentNode` exactly where its TNode/LView does not know. Pushing this into the adapters
|
|
36
|
-
// would build three JS trees instead of the one being removed.
|
|
1
|
+
// Host access — the read half of a DOM, which Fabric does not ship. Mostly navigation, plus the
|
|
2
|
+
// two value reads a seam needs (textOf, propOf) — named for the whole because this is the surface
|
|
3
|
+
// an adapter may ask a node about, and keeping value reads out would push them back to raw fields.
|
|
4
|
+
// Four of five framework renderer seams navigate the host on their hot paths, and it's their
|
|
5
|
+
// contract, not our choice: Solid, Vue and Angular all declare parent/sibling navigation
|
|
6
|
+
// callbacks. Only React needs none — it navigates its own fibers.
|
|
7
|
+
// In a browser the DOM answers these; Fabric cannot. nativeFabricUIManager exposes no structural
|
|
8
|
+
// read, and RN's NativeDOM answers against the current revision — a node created, moved or removed
|
|
9
|
+
// but not yet committed answers empty or null, exactly the state a mid-build reconciler is in.
|
|
10
|
+
// So these accessors give adapters the navigation their seams require without handing them the
|
|
11
|
+
// node's fields: every one reads into the tree host (tree-host.ts) — native on device, a
|
|
12
|
+
// TypeScript applier headlessly — because JS holds no tree at all.
|
|
13
|
+
// Each read flushes the mutation buffer first: a reconciler navigates the tree it is mid-way
|
|
14
|
+
// through building, so the host must be told about ops recorded since the last commit before it
|
|
15
|
+
// can answer — that's what flushOps is for.
|
|
16
|
+
// None of the three navigating adapters (Vue, Angular, Solid) can answer from state they already
|
|
17
|
+
// hold on their own side — pushing this into the adapters would build three JS trees instead of
|
|
18
|
+
// the one being removed.
|
|
37
19
|
import { flushOps, settleBeforeFlush, treeHost } from './tree-host.js';
|
|
38
20
|
import { hasPendingPlacement } from './mutation-buffer.js';
|
|
39
21
|
import { hasAnimatedBindings } from './animated/host-binding.js';
|
|
@@ -42,68 +24,45 @@ import { functionPropOf, functionPropsOf, isSymbioteNode, RAW_TEXT_COMPONENT, SU
|
|
|
42
24
|
// common answer during a build: solid asks 2 000 times on a 1 000-row create and every answer is
|
|
43
25
|
// this.
|
|
44
26
|
const NO_CHILDREN = [];
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
* the `SymbioteSurface` object, which the surface's anchor node is not. `SURFACE_COMPONENT` is the
|
|
52
|
-
* sentinel that stops the answer there.
|
|
53
|
-
*
|
|
54
|
-
* So `undefined` is not the same question as "is this node attached".
|
|
55
|
-
*
|
|
56
|
-
* IT DOES NOT ALWAYS DRAIN, unlike its neighbours. A node's parent link changes only through an op
|
|
57
|
-
* that names it as the CHILD, so a node the pending batch has not placed already has its final
|
|
58
|
-
* answer standing in the host — see `hasPendingPlacement`. Angular's 1 000-row create asks this
|
|
59
|
-
* 1 000 times (its `addLViewToLContainer` calls `renderer.parentNode` once per embedded view) and
|
|
60
|
-
* exactly ONE of those reads was about a node the batch had touched.
|
|
61
|
-
*/
|
|
27
|
+
// The node's parent, or undefined for a node that sits directly under a surface. A top-level node
|
|
28
|
+
// answers undefined even though a surface IS a node in the host's tree — three adapters depend on
|
|
29
|
+
// that exact miss, comparing the result against the SymbioteSurface object it is not.
|
|
30
|
+
// Does not always drain, unlike its neighbours: a node's parent link changes only through an op
|
|
31
|
+
// that names it as the child, so a node the pending batch hasn't placed already has its final
|
|
32
|
+
// answer standing in the host (see hasPendingPlacement).
|
|
62
33
|
export function parentOf(node) {
|
|
63
|
-
// Asked
|
|
64
|
-
//
|
|
65
|
-
//
|
|
34
|
+
// Asked first and outside the gate: an adapter holding a coalesced write has ops not in the
|
|
35
|
+
// buffer yet, so the gate can't judge this node until they are. A listener re-placing this very
|
|
36
|
+
// node lands it in hasPendingPlacement below, read after the fact.
|
|
66
37
|
settleBeforeFlush();
|
|
67
38
|
if (hasPendingPlacement(node))
|
|
68
39
|
flushOps();
|
|
69
40
|
const parent = treeHost()?.parentOf(node);
|
|
70
|
-
// A runtime guard, not a cast: the host stores handles as bare objects, and the brand is what
|
|
71
|
-
// one of them is ours. It also refuses anything a foreign host might hand back.
|
|
41
|
+
// A runtime guard, not a cast: the host stores handles as bare objects, and the brand is what
|
|
42
|
+
// says one of them is ours. It also refuses anything a foreign host might hand back.
|
|
72
43
|
if (!isSymbioteNode(parent))
|
|
73
44
|
return undefined;
|
|
74
45
|
return parent.component === SURFACE_COMPONENT ? undefined : parent;
|
|
75
46
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
* Anchors are structural bookkeeping — the commit skips them — but they are not invisible to
|
|
80
|
-
* traversal, and hiding them here would desync a framework runtime from the tree it built:
|
|
81
|
-
* solid-js/universal keeps its own record of what it inserted and re-derives positions through these
|
|
82
|
-
* lookups, so a node it placed must be a node it can find.
|
|
83
|
-
*/
|
|
47
|
+
// The node's children, including anchors. Anchors are structural bookkeeping (the commit skips
|
|
48
|
+
// them) but not invisible to traversal — solid-js/universal re-derives positions through these
|
|
49
|
+
// lookups, so a node it placed must be a node it can find.
|
|
84
50
|
export function childrenOf(node) {
|
|
85
|
-
// A
|
|
86
|
-
// drains the buffer into the host, so a question whose answer is
|
|
87
|
-
// in two and costs a crossing.
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
// `mayHaveChildren` is raised by the two structural recorders in `node.ts` and never lowered, so
|
|
91
|
-
// FALSE is a certainty and TRUE only means "ask". See its declaration for why it is not a tree.
|
|
51
|
+
// A read is a batch boundary, which is what makes this guard worth a field: flushOps below
|
|
52
|
+
// drains the buffer into the host, so a question whose answer is empty still cuts the op stream
|
|
53
|
+
// in two and costs a crossing.
|
|
54
|
+
// mayHaveChildren is raised by the two structural recorders in node.ts and never lowered, so
|
|
55
|
+
// false is a certainty and true only means "ask". See its declaration for why it's not a tree.
|
|
92
56
|
if (!node.mayHaveChildren)
|
|
93
57
|
return NO_CHILDREN;
|
|
94
58
|
flushOps();
|
|
95
59
|
return treeHost()?.childrenOf(node).filter(isSymbioteNode) ?? [];
|
|
96
60
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
* teardown is the one lifecycle event whose size is the tree's (see `ITreeHost`).
|
|
103
|
-
*
|
|
104
|
-
* A `SURFACE_COMPONENT` parent reads as `undefined` here exactly as it does in `parentOf`, so the
|
|
105
|
-
* two agree element for element.
|
|
106
|
-
*/
|
|
61
|
+
// Every node's parent, positionally, in one crossing — parentOf for a list. Engine-internal, like
|
|
62
|
+
// subtreesOf below: a framework seam only ever needs the singular form, one step at a time. These
|
|
63
|
+
// two answer the question only teardown asks, whose size is the tree's (see ITreeHost).
|
|
64
|
+
// A SURFACE_COMPONENT parent reads as undefined here exactly as it does in parentOf, so the two
|
|
65
|
+
// agree element for element.
|
|
107
66
|
export function parentsOf(nodes) {
|
|
108
67
|
flushOps();
|
|
109
68
|
const parents = treeHost()?.parentsOf(nodes);
|
|
@@ -118,37 +77,25 @@ export function parentsOf(nodes) {
|
|
|
118
77
|
/** Each root and every descendant, pre-order, anchors included — all of it in ONE crossing. */
|
|
119
78
|
export function subtreesOf(roots) {
|
|
120
79
|
flushOps();
|
|
121
|
-
// The
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
// because removing it means either an `as` or declaring `ITreeHost.subtreesOf` to hand back our
|
|
125
|
-
// own type, and a pluggable host is exactly what that `object` boundary is for.
|
|
80
|
+
// The .filter is type narrowing, not a defensive check, and it stays: removing it means either
|
|
81
|
+
// an `as` or declaring ITreeHost.subtreesOf to hand back our own type, and a pluggable host is
|
|
82
|
+
// exactly what that object boundary is for.
|
|
126
83
|
return treeHost()?.subtreesOf(roots).filter(isSymbioteNode) ?? [];
|
|
127
84
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
* The gate is the ANIMATED one, not a behavior one: a binding is per node and carries no tag, so an
|
|
132
|
-
* app that animates needs every node back and gets the full walk. Nothing else narrows, because
|
|
133
|
-
* nothing else is charged per node of a removed subtree.
|
|
134
|
-
*/
|
|
85
|
+
// The same walk narrowed to what a teardown must visit — see ITreeHost.teardownSubtreesOf. The
|
|
86
|
+
// gate is the animated one, not a behavior one: a binding is per node and carries no tag, so an
|
|
87
|
+
// app that animates needs every node back and gets the full walk.
|
|
135
88
|
export function teardownSubtreesOf(roots) {
|
|
136
89
|
if (hasAnimatedBindings())
|
|
137
90
|
return subtreesOf(roots);
|
|
138
91
|
flushOps();
|
|
139
92
|
return treeHost()?.teardownSubtreesOf(roots).filter(isSymbioteNode) ?? [];
|
|
140
93
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
* responder's scope. Measured at 18 crossings per event on a depth-8 chain before the two phases
|
|
147
|
-
* shared a walk, 9 after, and 1 through here.
|
|
148
|
-
*
|
|
149
|
-
* Surfaces are dropped the same way `parentOf` drops them, by component, so a caller sees the same
|
|
150
|
-
* chain it would have built by walking.
|
|
151
|
-
*/
|
|
94
|
+
// The node and every ancestor above it, deepest first, in one crossing. parentOf per level is a
|
|
95
|
+
// crossing per level, and the event path needs this chain for every event (capture reversed,
|
|
96
|
+
// bubble forward), plus again on every drag frame for the responder's scope.
|
|
97
|
+
// Surfaces are dropped the same way parentOf drops them, by component, so a caller sees the same
|
|
98
|
+
// chain it would have built by walking.
|
|
152
99
|
export function ancestorsOf(node) {
|
|
153
100
|
flushOps();
|
|
154
101
|
const chain = treeHost()?.ancestorsOf(node) ?? [];
|
|
@@ -162,19 +109,11 @@ export function ancestorsOf(node) {
|
|
|
162
109
|
}
|
|
163
110
|
return out;
|
|
164
111
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
* `while (removed = getFirstChild(parent)) removeNode(parent, removed)` — so the old spelling read a
|
|
171
|
-
* list of N, then N-1, then N-2, building and discarding every handle each time. Measured on a
|
|
172
|
-
* 2 000-row Solid `Clear` (`solid-clear-scaling.itest.tsx`): **2 001 001 handles** crossed to remove
|
|
173
|
-
* two thousand children, N(N+1)/2 to the unit, against the ~2 000 the work needs.
|
|
174
|
-
*
|
|
175
|
-
* The `mayHaveChildren` fast path is kept for the same reason `childrenOf` has it: FALSE is a
|
|
176
|
-
* certainty, so a leaf answers without a drain and without a crossing.
|
|
177
|
-
*/
|
|
112
|
+
// The first child, anchors included, or undefined for a leaf. One host call, not
|
|
113
|
+
// childrenOf(node)[0] — that spelling is quadratic: solid-js/universal's cleanChildren empties a
|
|
114
|
+
// parent in a loop, so reading the list to take its head crosses a handle per remaining child.
|
|
115
|
+
// The mayHaveChildren fast path is kept for the same reason childrenOf has it: false is a
|
|
116
|
+
// certainty, so a leaf answers without a drain and without a crossing.
|
|
178
117
|
export function firstChildOf(node) {
|
|
179
118
|
if (!node.mayHaveChildren)
|
|
180
119
|
return undefined;
|
|
@@ -182,69 +121,41 @@ export function firstChildOf(node) {
|
|
|
182
121
|
const child = treeHost()?.firstChildOf(node);
|
|
183
122
|
return isSymbioteNode(child) ? child : undefined;
|
|
184
123
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
// The previous spelling built every sibling to read one of them, and a keyed patch calls this
|
|
196
|
-
// once per row: measured on vue, appending 1 000 rows to 1 000 standing crossed 1 002 001 handles
|
|
197
|
-
// and removing them 2 002 002 — quadratic in the list, with every handle a host object built,
|
|
198
|
-
// filtered and discarded. The host holds the list and can scan it in place.
|
|
199
|
-
//
|
|
200
|
-
// `surface` is no longer read and stays in the signature because three adapters pass it: a
|
|
201
|
-
// top-level node's parent IS the surface node in the host's tree, so the host answers that case
|
|
202
|
-
// without being told which surface. The old code needed it only because `parentOf` masks a
|
|
203
|
-
// surface parent to `undefined` and there was then nothing left to ask.
|
|
124
|
+
// The next sibling, or undefined at the end of the list. `_surface` stays in the signature only
|
|
125
|
+
// because three adapters still pass it, though it's no longer read (see below).
|
|
126
|
+
export function nextSiblingOf(node, _surface) {
|
|
127
|
+
// One host call, not parentOf plus a whole child list — the old spelling built every sibling to
|
|
128
|
+
// read one of them, quadratic on a keyed patch calling this once per row. The host holds the
|
|
129
|
+
// list and can scan it in place.
|
|
130
|
+
// `surface` is unread because a top-level node's parent IS the surface node in the host's tree,
|
|
131
|
+
// so the host answers without being told which surface — needed only because parentOf masks a
|
|
132
|
+
// surface parent to undefined, leaving nothing else to ask.
|
|
204
133
|
flushOps();
|
|
205
134
|
const sibling = treeHost()?.nextSiblingOf(node);
|
|
206
135
|
return isSymbioteNode(sibling) ? sibling : undefined;
|
|
207
136
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
* The distinction is load-bearing for adapters that ask "can I write a string into this": a raw
|
|
212
|
-
* text node answers FALSE here, and an anchor does too. Use `isRawTextNode` for that question.
|
|
213
|
-
*/
|
|
137
|
+
// Whether the node is a text container (<Text>), not whether it holds a string. Load-bearing for
|
|
138
|
+
// "can I write a string into this": a raw text node answers false here, and so does an anchor —
|
|
139
|
+
// use isRawTextNode for that question.
|
|
214
140
|
export function isTextContainer(node) {
|
|
215
141
|
return node.isText;
|
|
216
142
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
* This is the question `solid-js/universal`'s `insertExpression` actually asks before calling
|
|
221
|
-
* replaceText, and answering it with `isTextContainer` would be wrong in both directions: a
|
|
222
|
-
* `<Text>` is a container that holds no string of its own, and the empty-string ANCHOR that
|
|
223
|
-
* cleanChildren leaves to hold a position is not writable either. An anchor is excluded here by
|
|
224
|
-
* construction, since its component is the `#anchor` sentinel.
|
|
225
|
-
*/
|
|
143
|
+
// Whether the node is a raw text node — one a string can be written into. The question
|
|
144
|
+
// solid-js/universal's insertExpression actually asks before replaceText: isTextContainer would
|
|
145
|
+
// answer wrong both ways, since a <Text> holds no string of its own and an anchor isn't writable.
|
|
226
146
|
export function isRawTextNode(node) {
|
|
227
147
|
return node.component === RAW_TEXT_COMPONENT;
|
|
228
148
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
*
|
|
234
|
-
* NOT stable across a node's life: a primitive whose native view depends on a prop (`TextInput`'s
|
|
235
|
-
* `multiline`) changes view without changing identity. Read it, never cache it.
|
|
236
|
-
*/
|
|
149
|
+
// The Fabric view name this node currently resolves to (RCTView, RCTText, RCTRawText, the #anchor
|
|
150
|
+
// sentinel...). Exposed because Solid and Angular both branch on it directly.
|
|
151
|
+
// Not stable across a node's life — a primitive whose native view depends on a prop changes view
|
|
152
|
+
// without changing identity, so read it, never cache it.
|
|
237
153
|
export function componentOf(node) {
|
|
238
154
|
return node.component;
|
|
239
155
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
* Exists for the DIAGNOSTIC path rather than the render path: a seam that rejects a bare string
|
|
244
|
-
* outside a `<Text>` wants to name the offending text in its error, and reading `node.props.text`
|
|
245
|
-
* to do so is the last thing keeping that seam coupled to the node's shape. Vue's
|
|
246
|
-
* `setElementText` reads the same value for a real reason, so this is not a one-caller accessor.
|
|
247
|
-
*/
|
|
156
|
+
// The string a raw-text node currently holds, or undefined for any other node. Exists for the
|
|
157
|
+
// diagnostic path rather than the render path: a seam rejecting a bare string outside a <Text>
|
|
158
|
+
// wants to name the offending text without coupling to node.props.text's shape.
|
|
248
159
|
export function textOf(node) {
|
|
249
160
|
if (node.component !== RAW_TEXT_COMPONENT)
|
|
250
161
|
return undefined;
|
|
@@ -252,32 +163,18 @@ export function textOf(node) {
|
|
|
252
163
|
const text = treeHost()?.propOf(node, 'text');
|
|
253
164
|
return typeof text === 'string' ? text : undefined;
|
|
254
165
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
* shim reaches for `el.props`, which is the last field read keeping a non-seam file coupled to the
|
|
262
|
-
* node's shape.
|
|
263
|
-
*
|
|
264
|
-
* Deliberately NOT a general property bag escape hatch: it answers what is on the node, which for
|
|
265
|
-
* `style` after a class merge is the `[classStyle, explicitStyle]` ARRAY rather than the author's
|
|
266
|
-
* object. `getExplicitStyle` exists for that question and this is not a substitute for it.
|
|
267
|
-
*/
|
|
166
|
+
// The value currently standing on a prop, undefined if none is set. The one accessor here that
|
|
167
|
+
// reads a value rather than the tree: Vue's v-model shim must read the standing onValueChange
|
|
168
|
+
// handler before composing its own, without coupling to el.props's shape.
|
|
169
|
+
// Deliberately not a general property bag escape hatch: it answers what is on the node, which for
|
|
170
|
+
// style after a class merge is the [classStyle, explicitStyle] array, not the author's object —
|
|
171
|
+
// getExplicitStyle exists for that question and this is not a substitute for it.
|
|
268
172
|
const NO_PROPS = {};
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
* asking for `onPress` must see the one the app wrote rather than the `undefined` the host was
|
|
275
|
-
* handed in its place.
|
|
276
|
-
*
|
|
277
|
-
* A COPY when anything was stashed, the host's own object when nothing was — which is nearly every
|
|
278
|
-
* node. Same caveat as `propOf`: `style` after a class merge is the `[classStyle, explicitStyle]`
|
|
279
|
-
* array, not the author's object.
|
|
280
|
-
*/
|
|
173
|
+
// Every prop standing on the node, function props included — the bag a payload fold reads. Two
|
|
174
|
+
// sources, since a function never crossed the wire: the host holds values, JS holds the callbacks
|
|
175
|
+
// it couldn't, and a fold asking for onPress must see what the app wrote, not host-side undefined.
|
|
176
|
+
// A copy when anything was stashed, the host's own object when nothing was (nearly every node).
|
|
177
|
+
// Same caveat as propOf: style after a class merge is the array, not the author's object.
|
|
281
178
|
export function propsOf(node) {
|
|
282
179
|
flushOps();
|
|
283
180
|
const stored = treeHost()?.propsOf(node) ?? NO_PROPS;
|
|
@@ -289,17 +186,11 @@ export function propsOf(node) {
|
|
|
289
186
|
merged[key] = value;
|
|
290
187
|
return merged;
|
|
291
188
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
* folds and the style hoist. The two answer different questions and a test has to pick: "did the
|
|
298
|
-
* adapter write `inputMode`" is `propsOf`, and "did that reach native as `keyboardType`" is this.
|
|
299
|
-
*
|
|
300
|
-
* Only the native host can answer it — see `ITreeHost.committedPayloadOf`. Under the recording host
|
|
301
|
-
* it throws, on purpose, naming the itest suite as where the question belongs.
|
|
302
|
-
*/
|
|
189
|
+
// A test read: the payload the last commit handed Fabric for this node, undefined before one.
|
|
190
|
+
// propsOf above is the props as the ops named them — what the adapter said. This is what the
|
|
191
|
+
// payload builder made of them, after every fold and the style hoist.
|
|
192
|
+
// Only the native host can answer it — see ITreeHost.committedPayloadOf. Under the recording host
|
|
193
|
+
// it throws, on purpose, naming the itest suite as where the question belongs.
|
|
303
194
|
export function committedPayloadOf(node) {
|
|
304
195
|
flushOps();
|
|
305
196
|
return treeHost()?.committedPayloadOf(node);
|
package/build/host-behavior.d.ts
CHANGED
|
@@ -1,21 +1,4 @@
|
|
|
1
1
|
import type { ISymbioteNode } from './node';
|
|
2
|
-
/**
|
|
3
|
-
* A pure props -> props mapping a behavior applies on the way to the Fabric payload.
|
|
4
|
-
*
|
|
5
|
-
* It exists because a tag has no component body, and a wrapper's body is where the per-primitive
|
|
6
|
-
* prop FOLDS live — TextInput's W3C aliases (`inputMode` -> `keyboardType`, `readOnly` ->
|
|
7
|
-
* `editable`), Pressable's `disabled` -> `accessibilityState`. Every one of those was silently
|
|
8
|
-
* dropped the moment the wrapper went: the raw alias reached Fabric as a key no ViewConfig
|
|
9
|
-
* declares, so nothing threw and nothing rendered differently in a headless test — only the device
|
|
10
|
-
* showed a numeric keyboard that never appeared.
|
|
11
|
-
*
|
|
12
|
-
* NOT a hook on `setProp`, for the same reason `afterCommit` is not: `setProp` is the hottest path
|
|
13
|
-
* in the engine. This runs once per node per payload build, and only for a node whose behavior
|
|
14
|
-
* supplied one.
|
|
15
|
-
*
|
|
16
|
-
* MUST be pure and MUST NOT mutate its input — `node.props` is the live bag, and the folds beside
|
|
17
|
-
* this one return their input by identity when there is nothing to do.
|
|
18
|
-
*/
|
|
19
2
|
export type IPayloadFold = (props: Readonly<Record<string, unknown>>) => Record<string, unknown>;
|
|
20
3
|
export type IClaimMode = 'beside' | 'wrap';
|
|
21
4
|
export interface IHostBehavior {
|
|
@@ -33,64 +16,13 @@ export interface IHostBehavior {
|
|
|
33
16
|
onOwnedListenerChange?(node: ISymbioteNode, name: string, wired: boolean): void;
|
|
34
17
|
afterCommit?(node: ISymbioteNode): void;
|
|
35
18
|
detach(node: ISymbioteNode): void;
|
|
36
|
-
/**
|
|
37
|
-
* This primitive's own prop folds. See IPayloadFold.
|
|
38
|
-
*
|
|
39
|
-
* NO PRODUCTION BEHAVIOR DECLARES ONE since 2026-09-18 — the sticky header's was the last, and it
|
|
40
|
-
* is `foldStickyHeaderProps` in `SymbioteFabricProps.cpp` now. So this reads as a leftover, and
|
|
41
|
-
* the question this codebase asks of one is what it REACHES rather than who uses it today. Two
|
|
42
|
-
* answers, and either alone would keep it:
|
|
43
|
-
*
|
|
44
|
-
* - It is the JS ARM of every cost measurement in `tag-rule-cost.itest.ts`. That file prices a
|
|
45
|
-
* rule by running it on both sides of the wire with the payloads asserted equal key by key
|
|
46
|
-
* first; without a fold there is no second arm, and the ~9-31 us-per-node figures every port in
|
|
47
|
-
* this migration was justified by could not be taken again.
|
|
48
|
-
* - It is the declared seam a third-party behavior would extend. A tag rule is ours to write in
|
|
49
|
-
* C++; a package that ships its own primitive has no such option.
|
|
50
|
-
*
|
|
51
|
-
* What it is NOT any more is where a Symbiote primitive puts its platform props. A new one that
|
|
52
|
-
* reaches for this is one whose rule belongs in the engine — read `SymbioteFabricProps.h` first.
|
|
53
|
-
*/
|
|
54
19
|
readonly foldPayload?: IPayloadFold;
|
|
55
|
-
/**
|
|
56
|
-
* Resolve `source` / `defaultSource` / `loadingIndicatorSource` on the way IN — Image's, and only
|
|
57
|
-
* Image's. See `image-source-write.ts`.
|
|
58
|
-
*
|
|
59
|
-
* It is declared here rather than inferred from the tag because it is a statement about what the
|
|
60
|
-
* primitive's props MEAN, which is exactly what a behavior is for. And it is a flag rather than a
|
|
61
|
-
* function: `routeProp` is the hottest path in the engine, so what it can afford per write is a
|
|
62
|
-
* boolean read on the node, not a call into a behavior.
|
|
63
|
-
*/
|
|
64
20
|
readonly resolvesImageSources?: boolean;
|
|
65
|
-
/**
|
|
66
|
-
* Flips `id`/`nativeID` precedence to nativeID-over-id — TouchableWithoutFeedback's, and only
|
|
67
|
-
* its. See `routeIdAlias` in `node.ts` for why the flip exists and which vendor file demands it.
|
|
68
|
-
*/
|
|
69
21
|
readonly nativeIdWinsOverId?: boolean;
|
|
70
22
|
}
|
|
71
23
|
export declare function registerHostBehavior(component: string, behavior: IHostBehavior): void;
|
|
72
24
|
export declare function hostBehaviorFor(tag: string): IHostBehavior | undefined;
|
|
73
25
|
export declare function hasHostBehaviors(): boolean;
|
|
74
|
-
/**
|
|
75
|
-
* Has a behavior ever ATTACHED to a node, as opposed to a behavior TYPE having been registered?
|
|
76
|
-
*
|
|
77
|
-
* `hasBehaviors` answers the second, and it is on from module load in every app: registering
|
|
78
|
-
* `Pressable` arms it whether or not one is ever mounted. It gates `createElement`'s attach probe
|
|
79
|
-
* correctly — a node has to be offered to the registry to find out. It gates the TEARDOWN SWEEP
|
|
80
|
-
* wrongly, and that is expensive: the sweep crosses every removed node into JS, which on a
|
|
81
|
-
* 1 000-row clear is ten thousand handles. Measured on `build-release`
|
|
82
|
-
* (`teardown-sweep-cost.itest.ts`): 1.8 ms with the sweep off against 6.3 ms with it on, i.e. 3.2x,
|
|
83
|
-
* all of it inside the commit. `Clear` is the one row where stock React Native beats every adapter.
|
|
84
|
-
*
|
|
85
|
-
* Nothing the sweep does can matter before the first attach, and the four collections say so:
|
|
86
|
-
* `node.hostBehavior` is written only by `attachHostBehavior`; `awaitingCommit` and `committedEachTime` are
|
|
87
|
-
* written only inside a `behavior.` branch; `parked` only by `detachAnimatedProps`, which has its
|
|
88
|
-
* own gate. The one remaining effect is marking `isTornDown`, which exists so a later re-insert knows
|
|
89
|
-
* to re-arm — and there is nothing to re-arm.
|
|
90
|
-
*
|
|
91
|
-
* MONOTONE, deliberately: it turns on and never off, so it needs no accounting on a `WeakMap` that
|
|
92
|
-
* has no size and no destructor. Being late is the only way it can be wrong, and it cannot be late.
|
|
93
|
-
*/
|
|
94
26
|
export declare function hasAttachedBehaviors(): boolean;
|
|
95
27
|
export declare function slotPropNameFor(node: ISymbioteNode, key: string): string | undefined;
|
|
96
28
|
export declare function slotTakesChildren(node: ISymbioteNode): boolean;
|
|
@@ -99,48 +31,16 @@ export declare function derivedNodesOf(owner: ISymbioteNode): readonly ISymbiote
|
|
|
99
31
|
export declare function notifyOwnedListenerChange(node: ISymbioteNode, name: string, wired: boolean): void;
|
|
100
32
|
export declare function notifyChildInserted(node: ISymbioteNode, child: ISymbioteNode): void;
|
|
101
33
|
export declare function claimModeFor(node: ISymbioteNode, component: string): IClaimMode | undefined;
|
|
102
|
-
/**
|
|
103
|
-
* Every owner key feeds the slot. The spelling for a `cloneElement` primitive, whose slot is not
|
|
104
|
-
* derived from a named set at all — see `IHostBehavior.slotDerived`.
|
|
105
|
-
*
|
|
106
|
-
* A sentinel in the SAME array rather than a second field, so `slotDerivesFrom` stays one lookup and
|
|
107
|
-
* a behavior that wants both spellings cannot express a contradiction.
|
|
108
|
-
*/
|
|
109
34
|
export declare const SLOT_DERIVED_ALL = "*";
|
|
110
35
|
export declare function slotDerivesFrom(node: ISymbioteNode, key: string): boolean;
|
|
111
36
|
export declare function ownsListener(node: ISymbioteNode, name: string): boolean;
|
|
112
37
|
export declare function stashAppListener(node: ISymbioteNode, name: string, listener: unknown): void;
|
|
113
38
|
export declare function appListenerFor(node: ISymbioteNode, name: string): unknown;
|
|
114
39
|
export declare function attachHostBehavior(node: ISymbioteNode, tag: string): void;
|
|
115
|
-
/**
|
|
116
|
-
* Run the deferred half of every behavior whose node has now been committed. Called from the commit
|
|
117
|
-
* path immediately after `completeRoot`, where fresh Fabric tags have just been assigned.
|
|
118
|
-
*
|
|
119
|
-
* `isCommitted` is passed in for the same no-cycle reason — `committedOf` lives in `commit.ts`. A
|
|
120
|
-
* still-uncommitted node stays in the set: a create superseded before it ever reached Fabric waits
|
|
121
|
-
* for the commit that lands it, and `detachSubtree` drops it if that commit never comes.
|
|
122
|
-
*/
|
|
123
40
|
export declare function runDeferredAttaches(isCommitted: (node: ISymbioteNode) => boolean): void;
|
|
124
|
-
/**
|
|
125
|
-
* Arm a node's recurring hook for the next commit.
|
|
126
|
-
*
|
|
127
|
-
* Called from `setProp` — gated there on `node.hasCommitHook`, a boolean field beside
|
|
128
|
-
* `hasAriaAlias` on the same hidden class, so a node without a recurring hook pays one load and
|
|
129
|
-
* one branch per write and never reaches this.
|
|
130
|
-
*/
|
|
131
41
|
export declare function noteCommitHookNodeChanged(node: ISymbioteNode): void;
|
|
132
42
|
export declare function runCommittedHooks(isCommitted: (node: ISymbioteNode) => boolean): void;
|
|
133
43
|
export declare function markDetachCandidate(node: ISymbioteNode): void;
|
|
134
|
-
/**
|
|
135
|
-
* Whether the sweep has anything to do — asked BEFORE its arguments are built.
|
|
136
|
-
*
|
|
137
|
-
* The sweep's own first line already returns on an empty candidate set, and that was not enough:
|
|
138
|
-
* its caller passes `surface.children`, which is a GETTER that crosses to the host, allocates the
|
|
139
|
-
* whole top-level list and filters it into a second array. On a surface holding four thousand rows
|
|
140
|
-
* that ran on every commit, including the ones with nothing to sweep, because an argument is
|
|
141
|
-
* evaluated before the guard inside the callee can decline. Same shape as the `dlog` arguments that
|
|
142
|
-
* cost Angular 5-10% while emitting nothing.
|
|
143
|
-
*/
|
|
144
44
|
export declare function hasDetachCandidates(): boolean;
|
|
145
45
|
export declare function sweepDetachedBehaviors(topLevel: readonly ISymbioteNode[], onDetached: (node: ISymbioteNode) => void): void;
|
|
146
46
|
export declare function teardownSubtree(node: ISymbioteNode, onDetached: (node: ISymbioteNode) => void): void;
|