@symbiote-native/engine 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/CMakeLists.txt +31 -1
- package/android/build.gradle +4 -1
- package/build/accessibility-info/index.android.js +21 -17
- package/build/accessibility-info/index.ios.js +2 -0
- package/build/accessibility-info/shared.d.ts +1 -1
- package/build/accessibility-props.d.ts +0 -11
- package/build/accessibility-props.js +30 -68
- package/build/alert/shared.d.ts +1 -1
- package/build/alert/shared.js +3 -7
- package/build/animated/graph.js +1 -1
- package/build/animated/leaf-lifecycle.js +2 -2
- package/build/app-state/index.d.ts +1 -1
- package/build/app-state/index.js +35 -27
- package/build/asset-source-resolver.d.ts +2 -0
- package/build/asset-source-resolver.js +13 -0
- package/build/back-handler/index.d.ts +7 -6
- package/build/back-handler/index.js +19 -16
- package/build/debug.js +8 -22
- package/build/dispatch.js +3 -9
- package/build/events/index.js +12 -3
- package/build/fabric-props.js +75 -180
- package/build/fabric.d.ts +2 -8
- package/build/fabric.js +27 -33
- package/build/host-access.d.ts +1 -128
- package/build/host-access.js +96 -205
- package/build/host-behavior.d.ts +1 -100
- package/build/host-behavior.js +125 -311
- package/build/image-loader.d.ts +4 -2
- package/build/image-loader.js +24 -48
- package/build/image-source-resolver.js +3 -7
- package/build/image-source-write.d.ts +1 -12
- package/build/image-source-write.js +28 -36
- package/build/imperative.d.ts +0 -28
- package/build/imperative.js +42 -92
- package/build/index.d.ts +3 -2
- package/build/index.js +30 -43
- package/build/invariant.d.ts +1 -0
- package/build/invariant.js +10 -0
- package/build/keyboard/index.js +11 -32
- package/build/linking/index.android.js +5 -3
- package/build/linking/shared.d.ts +1 -1
- package/build/linking/shared.js +16 -19
- package/build/mutation-buffer.d.ts +0 -177
- package/build/mutation-buffer.js +137 -308
- package/build/native-engine.d.ts +0 -102
- package/build/native-engine.js +38 -98
- package/build/native-events.d.ts +5 -0
- package/build/native-events.js +30 -18
- package/build/native-tree-host.d.ts +0 -21
- package/build/native-tree-host.js +14 -31
- package/build/node.d.ts +0 -212
- package/build/node.js +354 -774
- package/build/permissions-android/index.android.d.ts +59 -0
- package/build/permissions-android/index.android.js +47 -0
- package/build/permissions-android/index.d.ts +1 -115
- package/build/permissions-android/index.ios.d.ts +59 -0
- package/build/permissions-android/index.ios.js +31 -0
- package/build/permissions-android/index.js +3 -184
- package/build/permissions-android/shared.d.ts +63 -0
- package/build/permissions-android/shared.js +66 -0
- package/build/platform/index.android.js +3 -5
- package/build/platform/index.ios.js +3 -3
- package/build/platform/shared.d.ts +4 -0
- package/build/platform/shared.js +9 -0
- package/build/platform-color/index.android.d.ts +4 -0
- package/build/platform-color/index.android.js +7 -0
- package/build/platform-color/index.d.ts +2 -20
- package/build/platform-color/index.js +1 -45
- package/build/platform-color/shared.d.ts +21 -0
- package/build/platform-color/shared.js +41 -0
- 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/settings/index.android.d.ts +6 -0
- package/build/settings/index.android.js +21 -0
- package/build/settings/index.d.ts +1 -8
- package/build/settings/index.ios.d.ts +8 -0
- package/build/settings/index.ios.js +122 -0
- package/build/settings/index.js +3 -122
- package/build/share/index.android.js +9 -32
- package/build/share/index.ios.js +14 -15
- package/build/share/shared.d.ts +4 -2
- package/build/share/shared.js +7 -10
- package/build/status-bar/index.android.js +1 -1
- package/build/status-bar/index.ios.js +4 -3
- 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/toast-android/index.android.d.ts +10 -0
- package/build/toast-android/index.android.js +108 -0
- package/build/toast-android/index.d.ts +1 -10
- package/build/toast-android/index.ios.d.ts +10 -0
- package/build/toast-android/index.ios.js +19 -0
- package/build/toast-android/index.js +3 -108
- package/build/touch-history.js +5 -11
- package/build/tree-host.d.ts +0 -270
- package/build/tree-host.js +63 -153
- package/build/view-config.js +17 -37
- package/cpp/SymbioteFabricProps.cpp +204 -120
- package/cpp/SymbioteFabricProps.h +5 -0
- package/cpp/SymbioteTree.cpp +20 -6
- package/package.json +2 -2
package/build/host-access.d.ts
CHANGED
|
@@ -1,145 +1,18 @@
|
|
|
1
1
|
import { type ISymbioteNode } from './node';
|
|
2
2
|
import type { SymbioteSurface } from './surface';
|
|
3
|
-
/**
|
|
4
|
-
* The node's parent, or `undefined` for a node that sits directly under a surface.
|
|
5
|
-
*
|
|
6
|
-
* A top-level node answers `undefined` even though a surface IS a node in the host's tree, and the
|
|
7
|
-
* three adapters depend on that exact miss: Angular reads `null` as "defer, `<ng-content>` will place
|
|
8
|
-
* this", while Vue and Solid spell `?? surface` at their call sites and compare the result against
|
|
9
|
-
* the `SymbioteSurface` object, which the surface's anchor node is not. `SURFACE_COMPONENT` is the
|
|
10
|
-
* sentinel that stops the answer there.
|
|
11
|
-
*
|
|
12
|
-
* So `undefined` is not the same question as "is this node attached".
|
|
13
|
-
*
|
|
14
|
-
* IT DOES NOT ALWAYS DRAIN, unlike its neighbours. A node's parent link changes only through an op
|
|
15
|
-
* that names it as the CHILD, so a node the pending batch has not placed already has its final
|
|
16
|
-
* answer standing in the host — see `hasPendingPlacement`. Angular's 1 000-row create asks this
|
|
17
|
-
* 1 000 times (its `addLViewToLContainer` calls `renderer.parentNode` once per embedded view) and
|
|
18
|
-
* exactly ONE of those reads was about a node the batch had touched.
|
|
19
|
-
*/
|
|
20
3
|
export declare function parentOf(node: ISymbioteNode): ISymbioteNode | undefined;
|
|
21
|
-
/**
|
|
22
|
-
* The node's children, INCLUDING anchors.
|
|
23
|
-
*
|
|
24
|
-
* Anchors are structural bookkeeping — the commit skips them — but they are not invisible to
|
|
25
|
-
* traversal, and hiding them here would desync a framework runtime from the tree it built:
|
|
26
|
-
* solid-js/universal keeps its own record of what it inserted and re-derives positions through these
|
|
27
|
-
* lookups, so a node it placed must be a node it can find.
|
|
28
|
-
*/
|
|
29
4
|
export declare function childrenOf(node: ISymbioteNode): readonly ISymbioteNode[];
|
|
30
|
-
/**
|
|
31
|
-
* Every node's parent, positionally, in ONE crossing — `parentOf` for a list.
|
|
32
|
-
*
|
|
33
|
-
* Engine-internal, like `subtreesOf` below, and for the same reason: what a framework seam needs is
|
|
34
|
-
* the singular form, one step at a time. These two answer the question only TEARDOWN asks, and
|
|
35
|
-
* teardown is the one lifecycle event whose size is the tree's (see `ITreeHost`).
|
|
36
|
-
*
|
|
37
|
-
* A `SURFACE_COMPONENT` parent reads as `undefined` here exactly as it does in `parentOf`, so the
|
|
38
|
-
* two agree element for element.
|
|
39
|
-
*/
|
|
40
5
|
export declare function parentsOf(nodes: readonly ISymbioteNode[]): readonly (ISymbioteNode | undefined)[];
|
|
41
6
|
/** Each root and every descendant, pre-order, anchors included — all of it in ONE crossing. */
|
|
42
7
|
export declare function subtreesOf(roots: readonly ISymbioteNode[]): readonly ISymbioteNode[];
|
|
43
|
-
/**
|
|
44
|
-
* The same walk narrowed to what a teardown must visit — see `ITreeHost.teardownSubtreesOf`.
|
|
45
|
-
*
|
|
46
|
-
* The gate is the ANIMATED one, not a behavior one: a binding is per node and carries no tag, so an
|
|
47
|
-
* app that animates needs every node back and gets the full walk. Nothing else narrows, because
|
|
48
|
-
* nothing else is charged per node of a removed subtree.
|
|
49
|
-
*/
|
|
50
8
|
export declare function teardownSubtreesOf(roots: readonly ISymbioteNode[]): readonly ISymbioteNode[];
|
|
51
|
-
/**
|
|
52
|
-
* The node and every ancestor above it, deepest first, in ONE crossing.
|
|
53
|
-
*
|
|
54
|
-
* `parentOf` per level is a crossing per level, and the event path needs this chain for every
|
|
55
|
-
* event — capture reads it reversed, bubble forward — plus again on every frame of a drag, for the
|
|
56
|
-
* responder's scope. Measured at 18 crossings per event on a depth-8 chain before the two phases
|
|
57
|
-
* shared a walk, 9 after, and 1 through here.
|
|
58
|
-
*
|
|
59
|
-
* Surfaces are dropped the same way `parentOf` drops them, by component, so a caller sees the same
|
|
60
|
-
* chain it would have built by walking.
|
|
61
|
-
*/
|
|
62
9
|
export declare function ancestorsOf(node: ISymbioteNode): readonly ISymbioteNode[];
|
|
63
|
-
/**
|
|
64
|
-
* The first child, anchors included, or `undefined` for a leaf.
|
|
65
|
-
*
|
|
66
|
-
* ONE HOST CALL, not `childrenOf(node)[0]`, and the difference is a complexity class rather than a
|
|
67
|
-
* constant. `solid-js/universal`'s `cleanChildren` empties a parent with
|
|
68
|
-
* `while (removed = getFirstChild(parent)) removeNode(parent, removed)` — so the old spelling read a
|
|
69
|
-
* list of N, then N-1, then N-2, building and discarding every handle each time. Measured on a
|
|
70
|
-
* 2 000-row Solid `Clear` (`solid-clear-scaling.itest.tsx`): **2 001 001 handles** crossed to remove
|
|
71
|
-
* two thousand children, N(N+1)/2 to the unit, against the ~2 000 the work needs.
|
|
72
|
-
*
|
|
73
|
-
* The `mayHaveChildren` fast path is kept for the same reason `childrenOf` has it: FALSE is a
|
|
74
|
-
* certainty, so a leaf answers without a drain and without a crossing.
|
|
75
|
-
*/
|
|
76
10
|
export declare function firstChildOf(node: ISymbioteNode): ISymbioteNode | undefined;
|
|
77
|
-
|
|
78
|
-
* The next sibling, or `undefined` at the end of the list.
|
|
79
|
-
*
|
|
80
|
-
* `surface` is required to answer for a TOP-LEVEL node, which has no parent to read the sibling
|
|
81
|
-
* list from — the surface owns that list instead. Passing it for a parented node is harmless and
|
|
82
|
-
* ignored, so a caller with one active surface can pass it unconditionally.
|
|
83
|
-
*/
|
|
84
|
-
export declare function nextSiblingOf(node: ISymbioteNode, surface?: SymbioteSurface): ISymbioteNode | undefined;
|
|
85
|
-
/**
|
|
86
|
-
* Whether the node is a TEXT CONTAINER (`<Text>`), not whether it holds a string.
|
|
87
|
-
*
|
|
88
|
-
* The distinction is load-bearing for adapters that ask "can I write a string into this": a raw
|
|
89
|
-
* text node answers FALSE here, and an anchor does too. Use `isRawTextNode` for that question.
|
|
90
|
-
*/
|
|
11
|
+
export declare function nextSiblingOf(node: ISymbioteNode, _surface?: SymbioteSurface): ISymbioteNode | undefined;
|
|
91
12
|
export declare function isTextContainer(node: ISymbioteNode): boolean;
|
|
92
|
-
/**
|
|
93
|
-
* Whether the node is a RAW TEXT node — one a string can be written into.
|
|
94
|
-
*
|
|
95
|
-
* This is the question `solid-js/universal`'s `insertExpression` actually asks before calling
|
|
96
|
-
* replaceText, and answering it with `isTextContainer` would be wrong in both directions: a
|
|
97
|
-
* `<Text>` is a container that holds no string of its own, and the empty-string ANCHOR that
|
|
98
|
-
* cleanChildren leaves to hold a position is not writable either. An anchor is excluded here by
|
|
99
|
-
* construction, since its component is the `#anchor` sentinel.
|
|
100
|
-
*/
|
|
101
13
|
export declare function isRawTextNode(node: ISymbioteNode): boolean;
|
|
102
|
-
/**
|
|
103
|
-
* The Fabric view name this node currently resolves to (`RCTView`, `RCTText`, `RCTRawText`, the
|
|
104
|
-
* `#anchor` sentinel …). Exposed because two seams branch on it — Solid to answer `isTextNode`,
|
|
105
|
-
* Angular to recognise its own anchor hosts — and both read `node.component` directly today.
|
|
106
|
-
*
|
|
107
|
-
* NOT stable across a node's life: a primitive whose native view depends on a prop (`TextInput`'s
|
|
108
|
-
* `multiline`) changes view without changing identity. Read it, never cache it.
|
|
109
|
-
*/
|
|
110
14
|
export declare function componentOf(node: ISymbioteNode): string;
|
|
111
|
-
/**
|
|
112
|
-
* The string a raw-text node currently holds, or `undefined` for any other node.
|
|
113
|
-
*
|
|
114
|
-
* Exists for the DIAGNOSTIC path rather than the render path: a seam that rejects a bare string
|
|
115
|
-
* outside a `<Text>` wants to name the offending text in its error, and reading `node.props.text`
|
|
116
|
-
* to do so is the last thing keeping that seam coupled to the node's shape. Vue's
|
|
117
|
-
* `setElementText` reads the same value for a real reason, so this is not a one-caller accessor.
|
|
118
|
-
*/
|
|
119
15
|
export declare function textOf(node: ISymbioteNode): string | undefined;
|
|
120
|
-
/**
|
|
121
|
-
* Every prop standing on the node, function props included.
|
|
122
|
-
*
|
|
123
|
-
* The bag a payload fold reads. Two sources, because a function never crossed the wire (`writeProp`,
|
|
124
|
-
* node.ts): the host holds the values it could take, JS holds the callbacks it could not, and a fold
|
|
125
|
-
* asking for `onPress` must see the one the app wrote rather than the `undefined` the host was
|
|
126
|
-
* handed in its place.
|
|
127
|
-
*
|
|
128
|
-
* A COPY when anything was stashed, the host's own object when nothing was — which is nearly every
|
|
129
|
-
* node. Same caveat as `propOf`: `style` after a class merge is the `[classStyle, explicitStyle]`
|
|
130
|
-
* array, not the author's object.
|
|
131
|
-
*/
|
|
132
16
|
export declare function propsOf(node: ISymbioteNode): Readonly<Record<string, unknown>>;
|
|
133
|
-
/**
|
|
134
|
-
* A TEST read: the PAYLOAD the last commit handed Fabric for this node, `undefined` before one.
|
|
135
|
-
*
|
|
136
|
-
* `propsOf` above is the props AS THE OPS NAMED THEM — what the adapter said. This is what the
|
|
137
|
-
* payload builder MADE of them, after the aria fold, the behavior's own fold, the component-keyed
|
|
138
|
-
* folds and the style hoist. The two answer different questions and a test has to pick: "did the
|
|
139
|
-
* adapter write `inputMode`" is `propsOf`, and "did that reach native as `keyboardType`" is this.
|
|
140
|
-
*
|
|
141
|
-
* Only the native host can answer it — see `ITreeHost.committedPayloadOf`. Under the recording host
|
|
142
|
-
* it throws, on purpose, naming the itest suite as where the question belongs.
|
|
143
|
-
*/
|
|
144
17
|
export declare function committedPayloadOf(node: ISymbioteNode): Readonly<Record<string, unknown>> | undefined;
|
|
145
18
|
export declare function propOf(node: ISymbioteNode, key: string): unknown;
|
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);
|