@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
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// The declarative style slots and their publication: two halves an adapter may write in either
|
|
2
|
+
// order, merged into the one array the payload carries
|
|
3
|
+
import { recordSetUnderlayShown } from './mutation-buffer.js';
|
|
4
|
+
import { EMPTY_STYLE, resolveActiveClassName, } from './style-registry/index.js';
|
|
5
|
+
import { setProp } from './node-props.js';
|
|
6
|
+
// Narrowed rather than cast: `routeProp` takes `unknown`, and a bare `typeof v === 'function'`
|
|
7
|
+
// leaves TS with `Function`, callable with anything
|
|
8
|
+
export function isStyleCallback(value) {
|
|
9
|
+
return typeof value === 'function';
|
|
10
|
+
}
|
|
11
|
+
// All slots present from the start rather than added as they are written, so a styled node keeps
|
|
12
|
+
// one hidden class instead of paying a shape transition per slot
|
|
13
|
+
export function stylePartsOf(node) {
|
|
14
|
+
return (node.styleParts ??= {
|
|
15
|
+
classStyle: undefined,
|
|
16
|
+
explicitStyle: undefined,
|
|
17
|
+
hiddenStyle: undefined,
|
|
18
|
+
className: undefined,
|
|
19
|
+
isPressed: false,
|
|
20
|
+
activeStyle: undefined,
|
|
21
|
+
activeStyleFromCallback: false,
|
|
22
|
+
published: undefined,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
// Slot 1's twin of `baseStyleOf`: the variant replaces slot 1, not slot 0, so it beats the class
|
|
26
|
+
// cascade the way the authored style does while a `:active` rule can still win slot 0 underneath
|
|
27
|
+
function explicitStyleOf(parts) {
|
|
28
|
+
return parts.isPressed && parts.activeStyle !== undefined
|
|
29
|
+
? parts.activeStyle
|
|
30
|
+
: parts.explicitStyle;
|
|
31
|
+
}
|
|
32
|
+
// `resolveActiveClassName` resolves the element's tokens PLUS `:active` through the same matcher,
|
|
33
|
+
// so the result already contains everything the base class gave. Resolved LAZILY at press time,
|
|
34
|
+
// т.к. eager would double the resolutions on every class WRITE to serve a rare state
|
|
35
|
+
function baseStyleOf(parts) {
|
|
36
|
+
return parts.isPressed && typeof parts.className === 'string'
|
|
37
|
+
? resolveActiveClassName(parts.className)
|
|
38
|
+
: parts.classStyle;
|
|
39
|
+
}
|
|
40
|
+
// What a node publishes when NOTHING resolves. Length 0 is the marker and needs no second field,
|
|
41
|
+
// т.к. `pushClassStyle` never otherwise publishes an empty array
|
|
42
|
+
const PUBLISHED_NOTHING = Object.freeze([]);
|
|
43
|
+
// `mutation-buffer.ts` interns values BY IDENTITY, so distinct-but-equal arrays across a list
|
|
44
|
+
// styled the same way cost distinct entries and separate conversions to `folly::dynamic`
|
|
45
|
+
// `WeakMap` at both levels so nothing grows unbounded. The three-slot (hidden) form is deliberately
|
|
46
|
+
// NOT cached: `display: 'none'` is rare, so a third map would be paid for on every write
|
|
47
|
+
const sharedPairByExplicit = new WeakMap();
|
|
48
|
+
const sharedPairByBase = new WeakMap();
|
|
49
|
+
// The published array for this pair, the same object every time the same two parts are handed in.
|
|
50
|
+
// `undefined` when the pair cannot be keyed, and the caller then builds its own array
|
|
51
|
+
function sharedStylePair(base, explicit) {
|
|
52
|
+
const baseIsKeyable = typeof base === 'object' && base !== null;
|
|
53
|
+
const explicitIsKeyable = typeof explicit === 'object' && explicit !== null;
|
|
54
|
+
if (base === undefined && explicitIsKeyable) {
|
|
55
|
+
const cached = sharedPairByExplicit.get(explicit);
|
|
56
|
+
if (cached !== undefined)
|
|
57
|
+
return cached;
|
|
58
|
+
const made = [base, explicit];
|
|
59
|
+
sharedPairByExplicit.set(explicit, made);
|
|
60
|
+
return made;
|
|
61
|
+
}
|
|
62
|
+
if (!baseIsKeyable)
|
|
63
|
+
return undefined;
|
|
64
|
+
if (explicit === undefined) {
|
|
65
|
+
const cached = sharedPairByBase.get(base);
|
|
66
|
+
if (Array.isArray(cached))
|
|
67
|
+
return cached;
|
|
68
|
+
if (cached === undefined) {
|
|
69
|
+
const made = [base, explicit];
|
|
70
|
+
sharedPairByBase.set(base, made);
|
|
71
|
+
return made;
|
|
72
|
+
}
|
|
73
|
+
// A base already seen WITH an explicit half holds the second-level map here, and the base-only
|
|
74
|
+
// array has nowhere to live beside it. Rare enough not to earn a third map
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
if (!explicitIsKeyable)
|
|
78
|
+
return undefined;
|
|
79
|
+
const existing = sharedPairByBase.get(base);
|
|
80
|
+
const byExplicit = existing instanceof WeakMap ? existing : new WeakMap();
|
|
81
|
+
if (existing === undefined)
|
|
82
|
+
sharedPairByBase.set(base, byExplicit);
|
|
83
|
+
// The same clash the other way round: this base is holding its base-only array, so leave it
|
|
84
|
+
if (Array.isArray(existing))
|
|
85
|
+
return undefined;
|
|
86
|
+
const cached = byExplicit.get(explicit);
|
|
87
|
+
if (cached !== undefined)
|
|
88
|
+
return cached;
|
|
89
|
+
const made = [base, explicit];
|
|
90
|
+
byExplicit.set(explicit, made);
|
|
91
|
+
return made;
|
|
92
|
+
}
|
|
93
|
+
// A plain style bag: not an array of styles, not a callback, not null
|
|
94
|
+
function isStyleRecord(value) {
|
|
95
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
96
|
+
}
|
|
97
|
+
// Is this rebuilt style the same style, key for key? A component body writing its style inline
|
|
98
|
+
// hands over a fresh object every render, equal to the one standing, which `Object.is` cannot see
|
|
99
|
+
// Shallow and conservative: a nested value reports "not the same" rather than being deep-compared,
|
|
100
|
+
// so being wrong here is slow and never incorrect
|
|
101
|
+
export function isSameShallowStyle(next, standing) {
|
|
102
|
+
// THE SAME OBJECT IS THE SAME STYLE, checked first: without it a re-push of a hoisted constant
|
|
103
|
+
// allocates two key arrays and walks them to reach the answer identity gives for free
|
|
104
|
+
if (next === standing)
|
|
105
|
+
return isStyleRecord(next);
|
|
106
|
+
if (!isStyleRecord(next) || !isStyleRecord(standing))
|
|
107
|
+
return false;
|
|
108
|
+
const keys = Object.keys(next);
|
|
109
|
+
if (keys.length !== Object.keys(standing).length)
|
|
110
|
+
return false;
|
|
111
|
+
for (const key of keys) {
|
|
112
|
+
const value = next[key];
|
|
113
|
+
if (value === undefined || isStyleRecord(value) || Array.isArray(value)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
if (!Object.is(value, standing[key]))
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
function contributesNothing(slot) {
|
|
122
|
+
return slot === undefined || slot === EMPTY_STYLE;
|
|
123
|
+
}
|
|
124
|
+
// Read through the same two resolvers as the publication: guard and publication disagreeing is a
|
|
125
|
+
// silent wrong screen
|
|
126
|
+
function hasNothingToPublish(parts) {
|
|
127
|
+
return (parts.hiddenStyle === undefined &&
|
|
128
|
+
contributesNothing(baseStyleOf(parts)) &&
|
|
129
|
+
contributesNothing(explicitStyleOf(parts)));
|
|
130
|
+
}
|
|
131
|
+
function isAlreadyPublished(parts) {
|
|
132
|
+
const published = parts.published;
|
|
133
|
+
if (published === undefined)
|
|
134
|
+
return false;
|
|
135
|
+
// The delete is already standing. Asked before the slot comparisons, т.к. an empty array would
|
|
136
|
+
// otherwise pass both on `undefined` and then fail the length check
|
|
137
|
+
if (published.length === 0)
|
|
138
|
+
return hasNothingToPublish(parts);
|
|
139
|
+
// Through the resolvers, not the raw slots: guard and publication must read slot 0 and slot 1 the
|
|
140
|
+
// same way, or a press is turned away as already-published and does nothing with nothing red
|
|
141
|
+
if (!Object.is(published[0], baseStyleOf(parts)))
|
|
142
|
+
return false;
|
|
143
|
+
if (!Object.is(published[1], explicitStyleOf(parts)))
|
|
144
|
+
return false;
|
|
145
|
+
return parts.hiddenStyle === undefined
|
|
146
|
+
? published.length === 2
|
|
147
|
+
: published.length === 3 && Object.is(published[2], parts.hiddenStyle);
|
|
148
|
+
}
|
|
149
|
+
// The fresh ARRAY allocation stays DELIBERATELY: `setNativeProps` bypasses these parts, so a
|
|
150
|
+
// hoisted style constant would be skipped by the identity guard and never restore the declarative
|
|
151
|
+
// style an animation overwrote. The re-push IS the restore path
|
|
152
|
+
export function pushClassStyle(node, parts) {
|
|
153
|
+
// An unchanged class still reaches this write, т.к. Solid has no diff. The guard keys off
|
|
154
|
+
// `published`, which `setNativeProps` clears, so an imperative restore still re-publishes
|
|
155
|
+
if (isAlreadyPublished(parts))
|
|
156
|
+
return;
|
|
157
|
+
// Emits NO_VALUE rather than `[undefined, undefined]`, so the host skips a real array with one
|
|
158
|
+
// pointer check instead of building and diffing a `folly::dynamic`
|
|
159
|
+
if (hasNothingToPublish(parts)) {
|
|
160
|
+
parts.published = PUBLISHED_NOTHING;
|
|
161
|
+
setProp(node, 'style', undefined);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
// Third slot only appended while hidden: a permanent 3-element array would add an allocation to
|
|
165
|
+
// every style write for a state most nodes never enter
|
|
166
|
+
const base = baseStyleOf(parts);
|
|
167
|
+
const explicit = explicitStyleOf(parts);
|
|
168
|
+
const published = parts.hiddenStyle === undefined
|
|
169
|
+
? (sharedStylePair(base, explicit) ?? [base, explicit])
|
|
170
|
+
: [base, explicit, parts.hiddenStyle];
|
|
171
|
+
parts.published = published;
|
|
172
|
+
setProp(node, 'style', published);
|
|
173
|
+
}
|
|
174
|
+
// `display: 'none'` is a real RN style value (Yoga's DisplayNone), so a hidden node keeps its
|
|
175
|
+
// place in the tree, its state and its children, and just stops laying out and painting
|
|
176
|
+
const HIDDEN_STYLE = { display: 'none' };
|
|
177
|
+
// The seam React's Activity/Suspense reach for through `hideInstance` / `unhideInstance`. In the
|
|
178
|
+
// engine rather than an adapter, т.к. restoring the author's style byte belongs to the style merge
|
|
179
|
+
export function setNodeHidden(node, hidden) {
|
|
180
|
+
const parts = stylePartsOf(node);
|
|
181
|
+
parts.hiddenStyle = hidden ? HIDDEN_STYLE : undefined;
|
|
182
|
+
pushClassStyle(node, parts);
|
|
183
|
+
}
|
|
184
|
+
// Put a node into (or out of) its pressed state so `:active` rules apply. Costs nothing when no
|
|
185
|
+
// `:active` rule is registered, т.к. `isAlreadyPublished` turns the re-push away
|
|
186
|
+
export function setNodePressed(node, pressed) {
|
|
187
|
+
const parts = stylePartsOf(node);
|
|
188
|
+
parts.isPressed = pressed;
|
|
189
|
+
pushClassStyle(node, parts);
|
|
190
|
+
}
|
|
191
|
+
// Tell the host a behavior's FEEDBACK is showing, which is TouchableHighlight's underlay and only
|
|
192
|
+
// that. Not the same bit as `setNodePressed`, т.к. `shown` lags `pressed` by a timer
|
|
193
|
+
export function setNodeUnderlayShown(node, shown) {
|
|
194
|
+
recordSetUnderlayShown(node, shown);
|
|
195
|
+
}
|
|
196
|
+
// Forget what was last published so the next `pushClassStyle` cannot be turned away. A no-op for a
|
|
197
|
+
// node nobody has styled, which is why this is not `stylePartsOf(node).published = undefined`
|
|
198
|
+
export function clearPublishedStyle(node) {
|
|
199
|
+
if (node.styleParts !== undefined)
|
|
200
|
+
node.styleParts.published = undefined;
|
|
201
|
+
}
|
|
202
|
+
// The explicit (non-class) style half, which an adapter building style key by key merges onto
|
|
203
|
+
// rather than `node.props.style`, which may hold the pair `pushClassStyle` publishes
|
|
204
|
+
export function getExplicitStyle(node) {
|
|
205
|
+
return node.styleParts?.explicitStyle;
|
|
206
|
+
}
|
|
207
|
+
// The `[classStyle, explicitStyle]` pair the node publishes, in the order `pushClassStyle` writes.
|
|
208
|
+
// For a caller that wants the merged answer without a host, `core/css-parser` reads it here
|
|
209
|
+
export function getPublishedStyle(node) {
|
|
210
|
+
const parts = node.styleParts;
|
|
211
|
+
if (parts === undefined)
|
|
212
|
+
return [];
|
|
213
|
+
return [parts.classStyle, parts.explicitStyle];
|
|
214
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ISymbioteNode } from './node-types';
|
|
2
|
+
import type { ITreeCensus } from './tree-host';
|
|
3
|
+
export declare function appendChild(requestedParent: ISymbioteNode, child: ISymbioteNode): void;
|
|
4
|
+
export declare function insertBefore(requestedParent: ISymbioteNode, child: ISymbioteNode, beforeChild: ISymbioteNode | null | undefined): void;
|
|
5
|
+
export declare function removeChild(requestedParent: ISymbioteNode, child: ISymbioteNode): void;
|
|
6
|
+
export declare function censusRetainedTree(roots: readonly ISymbioteNode[]): ITreeCensus;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// The structural ops. Each is one op and nothing else: the host detaches a child from whatever
|
|
2
|
+
// parent it currently has before linking it, so JS never tracks the old parent
|
|
3
|
+
import { recordAppendChild, recordInsertBefore, recordRemoveChild, } from './mutation-buffer.js';
|
|
4
|
+
import { claimModeFor, hasAttachedBehaviors, hasHostBehaviors, markDetachCandidate, notifyChildInserted, noteCommitHookNodeChanged, reattachHostBehaviors, slotTakesChildren, } from './host-behavior.js';
|
|
5
|
+
import { hasAnimatedBindings, reattachAnimatedProps, } from './animated/host-binding.js';
|
|
6
|
+
import { EMPTY_CENSUS, flushOps, treeHost } from './tree-host.js';
|
|
7
|
+
import { isSymbioteNode } from './node-types.js';
|
|
8
|
+
// The host's raw answer, surface INCLUDED, unlike `parentOf` in `host-access.ts`, which reports a
|
|
9
|
+
// top-level node as parentless by design. The two swaps below have to NAME the holder in an op
|
|
10
|
+
function holderOf(node) {
|
|
11
|
+
flushOps();
|
|
12
|
+
const parent = treeHost()?.parentOf(node);
|
|
13
|
+
return isSymbioteNode(parent) ? parent : undefined;
|
|
14
|
+
}
|
|
15
|
+
// Which node a child actually lands on: the adapter always names the OWNER, and a node whose
|
|
16
|
+
// behavior built an internal subtree redirects the app's children into it unless the behavior
|
|
17
|
+
// CLAIMS this particular child
|
|
18
|
+
function hostFor(parent, child) {
|
|
19
|
+
const slot = parent.childHost;
|
|
20
|
+
if (slot === undefined)
|
|
21
|
+
return parent;
|
|
22
|
+
// A slot that is a built SIBLING rather than a container (ImageBackground's filled image) keeps
|
|
23
|
+
// the app's children on the owner
|
|
24
|
+
if (!slotTakesChildren(parent))
|
|
25
|
+
return parent;
|
|
26
|
+
return claimModeFor(parent, child.component) === undefined ? slot : parent;
|
|
27
|
+
}
|
|
28
|
+
// The node a child must be inserted before, or `undefined` for an ordinary append. A host that
|
|
29
|
+
// still has a slot is an owner taking a claimed child, which goes before the slot whatever the
|
|
30
|
+
// framework asked, т.к. RN renders `{refreshControl}{content}` in that order
|
|
31
|
+
function slotAnchorOf(host) {
|
|
32
|
+
const slot = host.childHost;
|
|
33
|
+
if (slot === undefined || !slotTakesChildren(host))
|
|
34
|
+
return undefined;
|
|
35
|
+
return slot;
|
|
36
|
+
}
|
|
37
|
+
// Arm a parent's recurring post-commit hook for a STRUCTURAL change, not just a prop write: the
|
|
38
|
+
// sticky-header machine drops a wrapper writing no prop at all, and a props-only beat left it stuck
|
|
39
|
+
function armCommitHookForChildChange(parent) {
|
|
40
|
+
if (parent.hasCommitHook)
|
|
41
|
+
noteCommitHookNodeChanged(parent);
|
|
42
|
+
}
|
|
43
|
+
// `mayHaveChildren` is only sound if every op that gives a node a child raises it, so it is raised
|
|
44
|
+
// in these two rather than at each of the five call sites
|
|
45
|
+
function recordAppendInto(parent, child) {
|
|
46
|
+
parent.mayHaveChildren = true;
|
|
47
|
+
armCommitHookForChildChange(parent);
|
|
48
|
+
recordAppendChild(parent, child);
|
|
49
|
+
}
|
|
50
|
+
function recordInsertInto(parent, child, beforeChild) {
|
|
51
|
+
parent.mayHaveChildren = true;
|
|
52
|
+
armCommitHookForChildChange(parent);
|
|
53
|
+
recordInsertBefore(parent, child, beforeChild);
|
|
54
|
+
}
|
|
55
|
+
// What actually occupies this node's place in its parent's child list: a wrapped owner is what the
|
|
56
|
+
// adapter names and the wrapper is what the tree holds
|
|
57
|
+
function placedNode(node) {
|
|
58
|
+
return node.wrapper ?? node;
|
|
59
|
+
}
|
|
60
|
+
// Make `child` the owner's parent, in place, returning false when this is not a wrap claim. The
|
|
61
|
+
// owner being unattached is the normal case, т.к. every adapter fills a node's children before
|
|
62
|
+
// appending it to its own parent
|
|
63
|
+
function wrapsOwner(owner, child) {
|
|
64
|
+
if (owner.childHost === undefined)
|
|
65
|
+
return false;
|
|
66
|
+
if (claimModeFor(owner, child.component) !== 'wrap')
|
|
67
|
+
return false;
|
|
68
|
+
if (hasHostBehaviors())
|
|
69
|
+
reattachHostBehaviors(child);
|
|
70
|
+
if (hasAnimatedBindings())
|
|
71
|
+
reattachAnimatedProps(child);
|
|
72
|
+
const holder = holderOf(owner);
|
|
73
|
+
// Wrapper takes the owner's place first, then the owner moves under it: the host's own detach on
|
|
74
|
+
// link is what unlinks the owner from `holder`, so no removal op is needed
|
|
75
|
+
if (holder !== undefined)
|
|
76
|
+
recordInsertInto(holder, child, owner);
|
|
77
|
+
owner.wrapper = child;
|
|
78
|
+
recordAppendInto(child, owner);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
// Put the owner back where its wrapper stood, the mirror of `wrapsOwner`. It must leave the owner
|
|
82
|
+
// ATTACHED: the framework is removing the RefreshControl, not the ScrollView
|
|
83
|
+
function unwrapsOwner(owner, child) {
|
|
84
|
+
if (owner.wrapper !== child)
|
|
85
|
+
return false;
|
|
86
|
+
owner.wrapper = undefined;
|
|
87
|
+
const holder = holderOf(child);
|
|
88
|
+
if (holder === undefined) {
|
|
89
|
+
// The wrapper never reached a parent, so there is no place to take back
|
|
90
|
+
recordRemoveChild(child, owner);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
recordInsertInto(holder, owner, child);
|
|
94
|
+
recordRemoveChild(holder, child);
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
// The body both inserts share: only the anchor differs, and NO anchor means append. A real case
|
|
99
|
+
// rather than a defensive guard, т.к. `solid-js/universal` spells "insert at end" as
|
|
100
|
+
// `insertNode(parent, node, null)` and Vue passes `anchor` through
|
|
101
|
+
function place(requestedParent, child, beforeChild) {
|
|
102
|
+
if (wrapsOwner(requestedParent, child))
|
|
103
|
+
return;
|
|
104
|
+
const parent = hostFor(requestedParent, child);
|
|
105
|
+
// A node the sweep tore down can be put back, т.к. Svelte parks live subtrees offscreen across
|
|
106
|
+
// commits. A `WeakSet` miss for anything freshly built, so the create path pays nothing
|
|
107
|
+
if (hasHostBehaviors())
|
|
108
|
+
reattachHostBehaviors(child);
|
|
109
|
+
if (hasAnimatedBindings())
|
|
110
|
+
reattachAnimatedProps(child);
|
|
111
|
+
const placed = placedNode(child);
|
|
112
|
+
const anchor = slotAnchorOf(parent) ??
|
|
113
|
+
(beforeChild === null || beforeChild === undefined
|
|
114
|
+
? undefined
|
|
115
|
+
: placedNode(beforeChild));
|
|
116
|
+
if (anchor === undefined)
|
|
117
|
+
recordAppendInto(parent, placed);
|
|
118
|
+
else
|
|
119
|
+
recordInsertInto(parent, placed, anchor);
|
|
120
|
+
if (hasHostBehaviors())
|
|
121
|
+
notifyChildInserted(parent, placed);
|
|
122
|
+
}
|
|
123
|
+
export function appendChild(requestedParent, child) {
|
|
124
|
+
place(requestedParent, child, undefined);
|
|
125
|
+
}
|
|
126
|
+
export function insertBefore(requestedParent, child, beforeChild) {
|
|
127
|
+
place(requestedParent, child, beforeChild);
|
|
128
|
+
}
|
|
129
|
+
// Removal only NOMINATES a behavior for teardown and the commit sweep decides, т.к. a framework may
|
|
130
|
+
// spell a move as remove-then-reinsert and the machine must survive it
|
|
131
|
+
export function removeChild(requestedParent, child) {
|
|
132
|
+
// A wrap claim leaving: the owner takes its own place back and stays in the tree, nominated for
|
|
133
|
+
// teardown like any other removed node т.к. the wrapper IS leaving
|
|
134
|
+
if (unwrapsOwner(requestedParent, child)) {
|
|
135
|
+
if (hasAttachedBehaviors() || hasAnimatedBindings())
|
|
136
|
+
markDetachCandidate(child);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// A slot that IS the child being removed stops being one. Without the clear, `hostFor` below
|
|
140
|
+
// redirects the removal into the node being removed and the next child nests inside the orphan
|
|
141
|
+
if (requestedParent.childHost === child)
|
|
142
|
+
requestedParent.childHost = undefined;
|
|
143
|
+
const parent = hostFor(requestedParent, child);
|
|
144
|
+
// `hasAttachedBehaviors`, not `hasHostBehaviors`: the latter is on from module load in every app
|
|
145
|
+
// just from registering Pressable as a type
|
|
146
|
+
if (hasAttachedBehaviors() || hasAnimatedBindings())
|
|
147
|
+
markDetachCandidate(child);
|
|
148
|
+
// BOTH, and the owner is the one that matters: a composed primitive's behavior lives on the node
|
|
149
|
+
// the adapter named, while `hostFor` redirects the mutation into its internal slot
|
|
150
|
+
armCommitHookForChildChange(requestedParent);
|
|
151
|
+
armCommitHookForChildChange(parent);
|
|
152
|
+
recordRemoveChild(parent, placedNode(child));
|
|
153
|
+
}
|
|
154
|
+
// A structural census of the tree the HOST holds. Walks nothing here, т.к. the walk needs
|
|
155
|
+
// `props.text` and a child list, and JS has neither
|
|
156
|
+
export function censusRetainedTree(roots) {
|
|
157
|
+
flushOps();
|
|
158
|
+
return treeHost()?.census(roots) ?? EMPTY_CENSUS;
|
|
159
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { IMeasureOnSuccess, IMeasureInWindowOnSuccess, IMeasureLayoutOnSuccess } from './fabric';
|
|
2
|
+
import type { IHostBehavior, IPayloadFold } from './host-behavior';
|
|
3
|
+
import type { IClassNameValue } from './style-registry';
|
|
4
|
+
export declare const BRAND: unique symbol;
|
|
5
|
+
export declare const RAW_TEXT_COMPONENT = "RCTRawText";
|
|
6
|
+
export declare const TEXT_COMPONENT = "RCTText";
|
|
7
|
+
export declare const VIRTUAL_TEXT_COMPONENT = "RCTVirtualText";
|
|
8
|
+
export declare const ANCHOR_COMPONENT = "#anchor";
|
|
9
|
+
export declare const VOID_COMPONENT = "#void";
|
|
10
|
+
export declare const SURFACE_COMPONENT = "#surface";
|
|
11
|
+
export interface ISymbioteEvent {
|
|
12
|
+
type: string;
|
|
13
|
+
target: ISymbioteNode;
|
|
14
|
+
currentTarget: ISymbioteNode;
|
|
15
|
+
nativeEvent: Record<string, unknown>;
|
|
16
|
+
stopPropagation: () => void;
|
|
17
|
+
}
|
|
18
|
+
export type IListener = (event: ISymbioteEvent) => unknown;
|
|
19
|
+
export type IEventDispatch = {
|
|
20
|
+
readonly names: ReadonlySet<string>;
|
|
21
|
+
readonly deliver: (node: ISymbioteNode, name: string, event: ISymbioteEvent) => unknown;
|
|
22
|
+
};
|
|
23
|
+
export declare function isSymbioteEvent(value: unknown): value is ISymbioteEvent;
|
|
24
|
+
export interface IClassStyleParts {
|
|
25
|
+
classStyle: unknown;
|
|
26
|
+
explicitStyle: unknown;
|
|
27
|
+
hiddenStyle: unknown;
|
|
28
|
+
className: IClassNameValue | undefined;
|
|
29
|
+
isPressed: boolean;
|
|
30
|
+
activeStyle: unknown;
|
|
31
|
+
activeStyleFromCallback: boolean;
|
|
32
|
+
published: readonly unknown[] | undefined;
|
|
33
|
+
}
|
|
34
|
+
export interface ISymbioteNode {
|
|
35
|
+
readonly [BRAND]: true;
|
|
36
|
+
component: string;
|
|
37
|
+
readonly isText: boolean;
|
|
38
|
+
listeners: Map<string, IListener> | undefined;
|
|
39
|
+
dispatch: IEventDispatch | undefined;
|
|
40
|
+
hasCommitHook: boolean;
|
|
41
|
+
resolvesImageSources: boolean;
|
|
42
|
+
nativeIdWinsOverId: boolean;
|
|
43
|
+
payloadFold: IPayloadFold | undefined;
|
|
44
|
+
hostBehavior: IHostBehavior | undefined;
|
|
45
|
+
styleParts: IClassStyleParts | undefined;
|
|
46
|
+
childHost: ISymbioteNode | undefined;
|
|
47
|
+
wrapper: ISymbioteNode | undefined;
|
|
48
|
+
mayHaveChildren: boolean;
|
|
49
|
+
isTornDown: boolean;
|
|
50
|
+
slot: number;
|
|
51
|
+
slotBatch: number;
|
|
52
|
+
createdBatch: number;
|
|
53
|
+
measure(callback: IMeasureOnSuccess): void;
|
|
54
|
+
measureInWindow(callback: IMeasureInWindowOnSuccess): void;
|
|
55
|
+
measureLayout(relativeToNativeNode: ISymbioteNode | number, onSuccess: IMeasureLayoutOnSuccess, onFail?: () => void): void;
|
|
56
|
+
setNativeProps(nativeProps: Record<string, unknown>): void;
|
|
57
|
+
focus(): void;
|
|
58
|
+
blur(): void;
|
|
59
|
+
scrollTo(options?: {
|
|
60
|
+
x?: number;
|
|
61
|
+
y?: number;
|
|
62
|
+
animated?: boolean;
|
|
63
|
+
}): void;
|
|
64
|
+
scrollToEnd(options?: {
|
|
65
|
+
animated?: boolean;
|
|
66
|
+
}): void;
|
|
67
|
+
flashScrollIndicators(): void;
|
|
68
|
+
}
|
|
69
|
+
export declare function isSymbioteNode(value: unknown): value is ISymbioteNode;
|
|
70
|
+
export declare function isAnchor(node: ISymbioteNode): boolean;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// The node's SHAPE and nothing that acts on it: the interface every sibling of `node.ts` writes
|
|
2
|
+
// through, the Fabric view names, and the two guards
|
|
3
|
+
export const BRAND = Symbol('symbiote.node');
|
|
4
|
+
// A node carries the Fabric view name directly, so adding a primitive is just a new string from
|
|
5
|
+
// the adapter. The one name resolved at commit time is text: `isText` marks a container, and a
|
|
6
|
+
// `<Text>` nested inside another commits as a virtual span
|
|
7
|
+
export const RAW_TEXT_COMPONENT = 'RCTRawText';
|
|
8
|
+
export const TEXT_COMPONENT = 'RCTText';
|
|
9
|
+
export const VIRTUAL_TEXT_COMPONENT = 'RCTVirtualText';
|
|
10
|
+
// Vue's runtime-core needs anchor nodes (fragments, `v-if`, `v-for`) to track sibling order and
|
|
11
|
+
// Fabric has no such concept. A real retained node keeps the ordering correct, and the commit walk
|
|
12
|
+
// SKIPS it so no native view is created
|
|
13
|
+
export const ANCHOR_COMPONENT = '#anchor';
|
|
14
|
+
// The sentinel a primitive resolves to when its ENTIRE subtree must vanish from Fabric on this
|
|
15
|
+
// platform (`input-accessory-view` on Android). An anchor hoists its children up, a void node does
|
|
16
|
+
// not: the walk stops there, recursively
|
|
17
|
+
export const VOID_COMPONENT = '#void';
|
|
18
|
+
// The sentinel a SURFACE's own root node carries, so `parentOf` can stop there and a top-level node
|
|
19
|
+
// answers `undefined` for its parent. JS-side name only, т.к. what goes over the wire is `RCTView`
|
|
20
|
+
export const SURFACE_COMPONENT = '#surface';
|
|
21
|
+
// Guard narrowing `unknown` to `ISymbioteEvent`, beside the interface it tests so every adapter
|
|
22
|
+
// checking for a Symbiote event shares one instead of writing its own
|
|
23
|
+
export function isSymbioteEvent(value) {
|
|
24
|
+
if (typeof value !== 'object' || value === null)
|
|
25
|
+
return false;
|
|
26
|
+
const nativeEvent = Reflect.get(value, 'nativeEvent');
|
|
27
|
+
return typeof nativeEvent === 'object' && nativeEvent !== null;
|
|
28
|
+
}
|
|
29
|
+
// `instanceHandle` round-trips through Fabric unchanged and comes back as the event target, so our
|
|
30
|
+
// nodes are branded and the handler can confirm one is ours
|
|
31
|
+
export function isSymbioteNode(value) {
|
|
32
|
+
return typeof value === 'object' && value !== null && BRAND in value;
|
|
33
|
+
}
|
|
34
|
+
export function isAnchor(node) {
|
|
35
|
+
return node.component === ANCHOR_COMPONENT;
|
|
36
|
+
}
|