@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.
Files changed (110) hide show
  1. package/android/CMakeLists.txt +31 -1
  2. package/android/build.gradle +4 -1
  3. package/build/accessibility-info/index.android.js +21 -17
  4. package/build/accessibility-info/index.ios.js +2 -0
  5. package/build/accessibility-info/shared.d.ts +1 -1
  6. package/build/accessibility-props.d.ts +0 -11
  7. package/build/accessibility-props.js +30 -68
  8. package/build/alert/shared.d.ts +1 -1
  9. package/build/alert/shared.js +3 -7
  10. package/build/animated/graph.js +1 -1
  11. package/build/animated/leaf-lifecycle.js +2 -2
  12. package/build/app-state/index.d.ts +1 -1
  13. package/build/app-state/index.js +35 -27
  14. package/build/asset-source-resolver.d.ts +2 -0
  15. package/build/asset-source-resolver.js +13 -0
  16. package/build/back-handler/index.d.ts +7 -6
  17. package/build/back-handler/index.js +19 -16
  18. package/build/debug.js +8 -22
  19. package/build/dispatch.js +3 -9
  20. package/build/events/index.js +12 -3
  21. package/build/fabric-props.js +75 -180
  22. package/build/fabric.d.ts +2 -8
  23. package/build/fabric.js +27 -33
  24. package/build/host-access.d.ts +1 -128
  25. package/build/host-access.js +96 -205
  26. package/build/host-behavior.d.ts +1 -100
  27. package/build/host-behavior.js +125 -311
  28. package/build/image-loader.d.ts +4 -2
  29. package/build/image-loader.js +24 -48
  30. package/build/image-source-resolver.js +3 -7
  31. package/build/image-source-write.d.ts +1 -12
  32. package/build/image-source-write.js +28 -36
  33. package/build/imperative.d.ts +0 -28
  34. package/build/imperative.js +42 -92
  35. package/build/index.d.ts +3 -2
  36. package/build/index.js +30 -43
  37. package/build/invariant.d.ts +1 -0
  38. package/build/invariant.js +10 -0
  39. package/build/keyboard/index.js +11 -32
  40. package/build/linking/index.android.js +5 -3
  41. package/build/linking/shared.d.ts +1 -1
  42. package/build/linking/shared.js +16 -19
  43. package/build/mutation-buffer.d.ts +0 -177
  44. package/build/mutation-buffer.js +137 -308
  45. package/build/native-engine.d.ts +0 -102
  46. package/build/native-engine.js +38 -98
  47. package/build/native-events.d.ts +5 -0
  48. package/build/native-events.js +30 -18
  49. package/build/native-tree-host.d.ts +0 -21
  50. package/build/native-tree-host.js +14 -31
  51. package/build/node.d.ts +0 -212
  52. package/build/node.js +354 -774
  53. package/build/permissions-android/index.android.d.ts +59 -0
  54. package/build/permissions-android/index.android.js +47 -0
  55. package/build/permissions-android/index.d.ts +1 -115
  56. package/build/permissions-android/index.ios.d.ts +59 -0
  57. package/build/permissions-android/index.ios.js +31 -0
  58. package/build/permissions-android/index.js +3 -184
  59. package/build/permissions-android/shared.d.ts +63 -0
  60. package/build/permissions-android/shared.js +66 -0
  61. package/build/platform/index.android.js +3 -5
  62. package/build/platform/index.ios.js +3 -3
  63. package/build/platform/shared.d.ts +4 -0
  64. package/build/platform/shared.js +9 -0
  65. package/build/platform-color/index.android.d.ts +4 -0
  66. package/build/platform-color/index.android.js +7 -0
  67. package/build/platform-color/index.d.ts +2 -20
  68. package/build/platform-color/index.js +1 -45
  69. package/build/platform-color/shared.d.ts +21 -0
  70. package/build/platform-color/shared.js +41 -0
  71. package/build/post-commit.js +3 -8
  72. package/build/process-aspect-ratio.js +3 -7
  73. package/build/process-background-longhands.js +10 -19
  74. package/build/process-filter.js +11 -19
  75. package/build/process-font-variant.js +3 -7
  76. package/build/registry.d.ts +0 -33
  77. package/build/registry.js +22 -57
  78. package/build/report-error.js +4 -18
  79. package/build/settings/index.android.d.ts +6 -0
  80. package/build/settings/index.android.js +21 -0
  81. package/build/settings/index.d.ts +1 -8
  82. package/build/settings/index.ios.d.ts +8 -0
  83. package/build/settings/index.ios.js +122 -0
  84. package/build/settings/index.js +3 -122
  85. package/build/share/index.android.js +9 -32
  86. package/build/share/index.ios.js +14 -15
  87. package/build/share/shared.d.ts +4 -2
  88. package/build/share/shared.js +7 -10
  89. package/build/status-bar/index.android.js +1 -1
  90. package/build/status-bar/index.ios.js +4 -3
  91. package/build/structured-style.d.ts +0 -9
  92. package/build/structured-style.js +16 -31
  93. package/build/styles.js +3 -6
  94. package/build/surface.d.ts +0 -26
  95. package/build/surface.js +29 -76
  96. package/build/text-input-state.js +4 -8
  97. package/build/toast-android/index.android.d.ts +10 -0
  98. package/build/toast-android/index.android.js +108 -0
  99. package/build/toast-android/index.d.ts +1 -10
  100. package/build/toast-android/index.ios.d.ts +10 -0
  101. package/build/toast-android/index.ios.js +19 -0
  102. package/build/toast-android/index.js +3 -108
  103. package/build/touch-history.js +5 -11
  104. package/build/tree-host.d.ts +0 -270
  105. package/build/tree-host.js +63 -153
  106. package/build/view-config.js +17 -37
  107. package/cpp/SymbioteFabricProps.cpp +204 -120
  108. package/cpp/SymbioteFabricProps.h +5 -0
  109. package/cpp/SymbioteTree.cpp +20 -6
  110. package/package.json +2 -2
@@ -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;
@@ -1,39 +1,21 @@
1
- // Host access — the READ half of a DOM, which Fabric does not ship.
2
- //
3
- // Mostly navigation, plus the two value reads a seam needs (`textOf`, `propOf`). Named for the
4
- // whole rather than for navigation alone: this is the surface an adapter is allowed to ask a node
5
- // about, and keeping value reads out of it would only push them back to raw field access, which is
6
- // the thing being removed.
7
- //
8
- // Four of five framework renderer seams navigate the host on their hot paths, and it is their
9
- // contract, not our choice: Solid's nodeOps declare getParentNode / getFirstChild / getNextSibling,
10
- // Vue's RendererOptions declare parentNode / nextSibling, Angular's Renderer2 declares the same
11
- // pair, and Svelte's compiled output reaches firstChild / nextSibling as real prototype getters.
12
- // React is the only one that needs none, because it navigates its own fibers.
13
- //
14
- // In a browser the DOM answers these. Fabric cannot: `nativeFabricUIManager` exposes no structural
15
- // read at all, and RN's `NativeDOM` (which does expose getChildNodes / getParentNode) answers
16
- // against the CURRENT REVISION — so a node that is created, moved or removed but not yet committed
17
- // answers empty or null. A reconciler navigates the tree it is mid-way through BUILDING, which is
18
- // exactly the state no committed revision holds. See the `symbiote-fabric-cxx-surface` skill, §1a
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
- * The node's parent, or `undefined` for a node that sits directly under a surface.
47
- *
48
- * A top-level node answers `undefined` even though a surface IS a node in the host's tree, and the
49
- * three adapters depend on that exact miss: Angular reads `null` as "defer, `<ng-content>` will place
50
- * this", while Vue and Solid spell `?? surface` at their call sites and compare the result against
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 FIRST and outside the gate: an adapter holding a coalesced write has ops that are not in
64
- // the buffer yet, so the gate cannot judge this node until they are. A listener that re-places
65
- // this very node lands it in `hasPendingPlacement` below, which is then read after the fact.
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 says
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
- * The node's children, INCLUDING anchors.
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 READ IS A BATCH BOUNDARY, which is what makes this guard worth a field. `flushOps` below
86
- // drains the buffer into the host, so a question whose answer is EMPTY still cuts the op stream
87
- // in two and costs a crossing. Measured on solid's 1 000-row create: 2 000 child-list reads, every
88
- // one returning zero handles, and 2 002 drains of a buffer that should have crossed twice.
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
- * Every node's parent, positionally, in ONE crossing — `parentOf` for a list.
99
- *
100
- * Engine-internal, like `subtreesOf` below, and for the same reason: what a framework seam needs is
101
- * the singular form, one step at a time. These two answer the question only TEARDOWN asks, and
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 `.filter` is the type NARROWING, not a defensive check, and it costs ~0.6 ms of the 5.5 ms
122
- // a thousand-row clear spends in the engine — measured on `build-release` by returning the host's
123
- // array unnarrowed (`teardown-sweep-cost.itest.ts`, rest of the sweep 2.75 -> 2.16 ms). It stays,
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
- * The same walk narrowed to what a teardown must visit — see `ITreeHost.teardownSubtreesOf`.
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
- * The node and every ancestor above it, deepest first, in ONE crossing.
143
- *
144
- * `parentOf` per level is a crossing per level, and the event path needs this chain for every
145
- * event — capture reads it reversed, bubble forward — plus again on every frame of a drag, for the
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
- * The first child, anchors included, or `undefined` for a leaf.
167
- *
168
- * ONE HOST CALL, not `childrenOf(node)[0]`, and the difference is a complexity class rather than a
169
- * constant. `solid-js/universal`'s `cleanChildren` empties a parent with
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
- * The next sibling, or `undefined` at the end of the list.
187
- *
188
- * `surface` is required to answer for a TOP-LEVEL node, which has no parent to read the sibling
189
- * list from — the surface owns that list instead. Passing it for a parented node is harmless and
190
- * ignored, so a caller with one active surface can pass it unconditionally.
191
- */
192
- export function nextSiblingOf(node, surface) {
193
- // ONE host call, not `parentOf` plus a whole child list.
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
- * Whether the node is a TEXT CONTAINER (`<Text>`), not whether it holds a string.
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
- * Whether the node is a RAW TEXT node — one a string can be written into.
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
- * The Fabric view name this node currently resolves to (`RCTView`, `RCTText`, `RCTRawText`, the
231
- * `#anchor` sentinel …). Exposed because two seams branch on it — Solid to answer `isTextNode`,
232
- * Angular to recognise its own anchor hosts — and both read `node.component` directly today.
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
- * The string a raw-text node currently holds, or `undefined` for any other node.
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
- * The value currently standing on a prop, `undefined` if none is set.
257
- *
258
- * The one accessor here that reads a VALUE rather than the tree, and it earns its place from a
259
- * real caller: Vue's `v-model` shim has to compose with whatever `onValueChange` the author
260
- * already bound, so it must read the standing handler before writing its own. Without this the
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
- * Every prop standing on the node, function props included.
271
- *
272
- * The bag a payload fold reads. Two sources, because a function never crossed the wire (`writeProp`,
273
- * node.ts): the host holds the values it could take, JS holds the callbacks it could not, and a fold
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
- * A TEST read: the PAYLOAD the last commit handed Fabric for this node, `undefined` before one.
294
- *
295
- * `propsOf` above is the props AS THE OPS NAMED THEM — what the adapter said. This is what the
296
- * payload builder MADE of them, after the aria fold, the behavior's own fold, the component-keyed
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);