@symbiote-native/engine 0.3.0 → 0.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/README.md +7 -1
- package/build/accessibility-props.d.ts +19 -0
- package/build/accessibility-props.js +209 -0
- package/build/app-registry/index.d.ts +1 -0
- package/build/app-registry/index.js +15 -5
- package/build/commit.d.ts +22 -0
- package/build/commit.js +479 -76
- package/build/debug.js +10 -4
- package/build/events/index.js +124 -81
- package/build/fabric-props.js +163 -9
- package/build/fabric.d.ts +10 -3
- package/build/fabric.js +11 -2
- package/build/host-behavior.d.ts +47 -0
- package/build/host-behavior.js +262 -0
- package/build/host-instance/index.d.ts +2 -10
- package/build/host-instance/index.js +13 -46
- package/build/index.d.ts +6 -1
- package/build/index.js +11 -4
- package/build/node.d.ts +96 -2
- package/build/node.js +474 -58
- package/build/style-registry/index.d.ts +2 -0
- package/build/style-registry/index.js +79 -0
- package/build/surface.js +7 -4
- package/build/view-config.js +6 -0
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -97,8 +97,14 @@ before a tag is guaranteed to exist.
|
|
|
97
97
|
- **Runtime modules**, framework-agnostic, re-exported by every adapter: `Platform`,
|
|
98
98
|
`StyleSheet` (+ `computeHairlineWidth`), `Dimensions`, `PixelRatio`, `Appearance`, `AppState`,
|
|
99
99
|
`Keyboard`, `AccessibilityInfo`, `BackHandler`, `PermissionsAndroid`, `LayoutAnimation`,
|
|
100
|
-
`InteractionManager`, `PanResponder`, and the imperative modules `Alert`, `Share`,
|
|
100
|
+
`InteractionManager`, `PanResponder`, `StatusBar`, and the imperative modules `Alert`, `Share`,
|
|
101
101
|
`ActionSheetIOS`, `Linking`, `Vibration`, `ToastAndroid`, `Settings`, `I18nManager`.
|
|
102
|
+
- **Host behaviors** (`registerHostBehavior` / `IHostBehavior` / `hasHostBehaviors` /
|
|
103
|
+
`clearHostBehaviors` / `appListenerFor` / `setBehaviorListener` / `requestCommitFor`) — the
|
|
104
|
+
registry that lets a primitive's state machine live directly on the engine node instead of
|
|
105
|
+
inside a framework component, so a `Pressable`/`Switch`/`TextInput`/`Image` can compile to a
|
|
106
|
+
bare intrinsic tag ("host-primitive lowering"). `@symbiote-native/components` registers its
|
|
107
|
+
behaviors against this seam; the engine never imports them back.
|
|
102
108
|
- **`Animated`** — both the JS and native driver (`timing` / `spring` / `decay` / `loop` /
|
|
103
109
|
`ValueXY` / tracking / `diffClamp` / `Easing`), including the native-event attachment path
|
|
104
110
|
(`attachNativeEvent`, `AnimatedEvent`).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether one key is an alias this fold consumes. `startsWith` rather than a Set lookup: this runs
|
|
3
|
+
* on `setProp`, the hottest write path in the engine (32 001 writes on one benchmark create), and
|
|
4
|
+
* it is guarded by the node's sticky flag so it is reached at most once per node per key. The
|
|
5
|
+
* `role` comparison comes first because it is the one alias with no prefix.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isAriaAliasKey(key: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Fold the web-alias `aria-*` / `role` props into RN's canonical `accessibility*` props.
|
|
10
|
+
*
|
|
11
|
+
* Returns the input BY IDENTITY when no alias is present — the fast path that keeps this off the
|
|
12
|
+
* hot path for the ~99% of nodes carrying none, and the property idempotence rests on: pass 1
|
|
13
|
+
* blanks every alias, so a second pass finds nothing and returns by identity again.
|
|
14
|
+
*
|
|
15
|
+
* The alias keys are blanked to `undefined` rather than deleted. That is not laziness: `setProp`
|
|
16
|
+
* treats an `undefined` write as a delete and `fabricProps` skips undefined, so a blanked alias
|
|
17
|
+
* cannot reach Fabric, while a `delete` would deoptimise the object's shape on every folded node.
|
|
18
|
+
*/
|
|
19
|
+
export declare function foldAriaProps(props: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// RN's `aria-*` / `role` -> `accessibility*` fold, at the layer every path goes through.
|
|
2
|
+
//
|
|
3
|
+
// WHY IT IS HERE AND NOT IN A WRAPPER. It used to run inside each primitive's COMPONENT, which is
|
|
4
|
+
// exactly the layer host-primitive lowering removes. A per-attribute element path cannot do it —
|
|
5
|
+
// `aria-checked` has to be folded against a sibling `accessibilityState` — so the four lowering
|
|
6
|
+
// transforms REFUSED any element carrying `role` or an `aria-*` attribute
|
|
7
|
+
// (`REFUSAL_CATEGORIES.bagFold`). Accessibility props are ordinary in real code, so that refusal
|
|
8
|
+
// cost lowering coverage on every primitive, including the three already lowered. Moving the fold
|
|
9
|
+
// down deletes the refusal instead of teaching four transforms a bag operation they cannot express.
|
|
10
|
+
//
|
|
11
|
+
// IT IS A MOVE, NOT A REWRITE, AND THAT IS DELIBERATE. The function carries TWO CONTRADICTORY
|
|
12
|
+
// PRECEDENCE RULES: for the scalars an explicit `accessibility*` WINS and the alias only fills a
|
|
13
|
+
// hole (`if (next.X === undefined)`), while INSIDE the `accessibilityState` / `accessibilityValue`
|
|
14
|
+
// composites the ALIAS wins per field (`ariaBusy ?? existing?.busy`). Both mirror RN's View.js.
|
|
15
|
+
// Anyone "cleaning this up" collapses them into one rule, and every component test stays green
|
|
16
|
+
// while real accessibility silently changes. `core/components/src/accessibility-props.test.ts`
|
|
17
|
+
// pins both directions; read it before touching the branches below.
|
|
18
|
+
//
|
|
19
|
+
// Record-level rather than typed, because the engine's caller has a raw `node.props` bag and an
|
|
20
|
+
// interface is not assignable to `Record<string, unknown>` (no index signature). The typed
|
|
21
|
+
// `resolveAccessibilityProps<T>` in `core/components` stays where adapters already import it and
|
|
22
|
+
// delegates here, keeping its own typed gate so the fast path allocates nothing.
|
|
23
|
+
import { dlog } from './debug.js';
|
|
24
|
+
// Copied line for line from the wrapper this replaces. The first copy silently dropped five
|
|
25
|
+
// entries (`button`, `grid`, `link`, `list`, `listitem`) — a role that falls through simply passes
|
|
26
|
+
// unmapped, so `role="listitem"` would have reached Fabric as `listitem` instead of `list` with
|
|
27
|
+
// nothing red anywhere. Diff this against RN's View.js rather than reading it for plausibility.
|
|
28
|
+
const ROLE_TO_ACCESSIBILITY_ROLE = {
|
|
29
|
+
alert: 'alert',
|
|
30
|
+
button: 'button',
|
|
31
|
+
checkbox: 'checkbox',
|
|
32
|
+
combobox: 'combobox',
|
|
33
|
+
grid: 'grid',
|
|
34
|
+
heading: 'header',
|
|
35
|
+
img: 'image',
|
|
36
|
+
link: 'link',
|
|
37
|
+
list: 'list',
|
|
38
|
+
listitem: 'list',
|
|
39
|
+
menu: 'menu',
|
|
40
|
+
menubar: 'menubar',
|
|
41
|
+
menuitem: 'menuitem',
|
|
42
|
+
none: 'none',
|
|
43
|
+
presentation: 'none',
|
|
44
|
+
progressbar: 'progressbar',
|
|
45
|
+
radio: 'radio',
|
|
46
|
+
radiogroup: 'radiogroup',
|
|
47
|
+
scrollbar: 'scrollbar',
|
|
48
|
+
searchbox: 'search',
|
|
49
|
+
slider: 'adjustable',
|
|
50
|
+
spinbutton: 'spinbutton',
|
|
51
|
+
summary: 'summary',
|
|
52
|
+
switch: 'switch',
|
|
53
|
+
tab: 'tab',
|
|
54
|
+
tablist: 'tablist',
|
|
55
|
+
timer: 'timer',
|
|
56
|
+
toolbar: 'toolbar',
|
|
57
|
+
};
|
|
58
|
+
const ARIA_KEYS = [
|
|
59
|
+
'role',
|
|
60
|
+
'aria-label',
|
|
61
|
+
'aria-labelledby',
|
|
62
|
+
'aria-live',
|
|
63
|
+
'aria-hidden',
|
|
64
|
+
'aria-busy',
|
|
65
|
+
'aria-checked',
|
|
66
|
+
'aria-disabled',
|
|
67
|
+
'aria-expanded',
|
|
68
|
+
'aria-selected',
|
|
69
|
+
'aria-modal',
|
|
70
|
+
'aria-valuemax',
|
|
71
|
+
'aria-valuemin',
|
|
72
|
+
'aria-valuenow',
|
|
73
|
+
'aria-valuetext',
|
|
74
|
+
];
|
|
75
|
+
// An indexed loop rather than `.some(key => …)`: the callback captures `props`, so a closure is
|
|
76
|
+
// allocated per call, and this is the gate on a path that runs once per node.
|
|
77
|
+
function hasAnyAriaKey(props) {
|
|
78
|
+
for (let index = 0; index < ARIA_KEYS.length; index += 1) {
|
|
79
|
+
if (props[ARIA_KEYS[index]] !== undefined)
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Whether one key is an alias this fold consumes. `startsWith` rather than a Set lookup: this runs
|
|
86
|
+
* on `setProp`, the hottest write path in the engine (32 001 writes on one benchmark create), and
|
|
87
|
+
* it is guarded by the node's sticky flag so it is reached at most once per node per key. The
|
|
88
|
+
* `role` comparison comes first because it is the one alias with no prefix.
|
|
89
|
+
*/
|
|
90
|
+
export function isAriaAliasKey(key) {
|
|
91
|
+
return key === 'role' || key.startsWith('aria-');
|
|
92
|
+
}
|
|
93
|
+
function isRecord(value) {
|
|
94
|
+
return typeof value === 'object' && value !== null;
|
|
95
|
+
}
|
|
96
|
+
function fieldOf(source, field) {
|
|
97
|
+
return isRecord(source) ? source[field] : undefined;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Fold the web-alias `aria-*` / `role` props into RN's canonical `accessibility*` props.
|
|
101
|
+
*
|
|
102
|
+
* Returns the input BY IDENTITY when no alias is present — the fast path that keeps this off the
|
|
103
|
+
* hot path for the ~99% of nodes carrying none, and the property idempotence rests on: pass 1
|
|
104
|
+
* blanks every alias, so a second pass finds nothing and returns by identity again.
|
|
105
|
+
*
|
|
106
|
+
* The alias keys are blanked to `undefined` rather than deleted. That is not laziness: `setProp`
|
|
107
|
+
* treats an `undefined` write as a delete and `fabricProps` skips undefined, so a blanked alias
|
|
108
|
+
* cannot reach Fabric, while a `delete` would deoptimise the object's shape on every folded node.
|
|
109
|
+
*/
|
|
110
|
+
export function foldAriaProps(props) {
|
|
111
|
+
if (!hasAnyAriaKey(props))
|
|
112
|
+
return props;
|
|
113
|
+
const bag = { ...props };
|
|
114
|
+
dlog('foldAriaProps: folding aria/role aliases into accessibility* props');
|
|
115
|
+
const role = bag.role;
|
|
116
|
+
const ariaLabel = bag['aria-label'];
|
|
117
|
+
const ariaLabelledBy = bag['aria-labelledby'];
|
|
118
|
+
const ariaLive = bag['aria-live'];
|
|
119
|
+
const ariaHidden = bag['aria-hidden'];
|
|
120
|
+
const ariaBusy = bag['aria-busy'];
|
|
121
|
+
const ariaChecked = bag['aria-checked'];
|
|
122
|
+
const ariaDisabled = bag['aria-disabled'];
|
|
123
|
+
const ariaExpanded = bag['aria-expanded'];
|
|
124
|
+
const ariaSelected = bag['aria-selected'];
|
|
125
|
+
const ariaModal = bag['aria-modal'];
|
|
126
|
+
const ariaValueMax = bag['aria-valuemax'];
|
|
127
|
+
const ariaValueMin = bag['aria-valuemin'];
|
|
128
|
+
const ariaValueNow = bag['aria-valuenow'];
|
|
129
|
+
const ariaValueText = bag['aria-valuetext'];
|
|
130
|
+
for (let index = 0; index < ARIA_KEYS.length; index += 1) {
|
|
131
|
+
bag[ARIA_KEYS[index]] = undefined;
|
|
132
|
+
}
|
|
133
|
+
// RULE ONE, for every scalar: the explicit prop WINS, the alias only fills a hole.
|
|
134
|
+
if (typeof ariaLabelledBy === 'string' &&
|
|
135
|
+
bag.accessibilityLabelledBy === undefined) {
|
|
136
|
+
bag.accessibilityLabelledBy = ariaLabelledBy.split(/\s*,\s*/g);
|
|
137
|
+
}
|
|
138
|
+
if (ariaLabel !== undefined && bag.accessibilityLabel === undefined) {
|
|
139
|
+
bag.accessibilityLabel = ariaLabel;
|
|
140
|
+
}
|
|
141
|
+
if (ariaLive !== undefined && bag.accessibilityLiveRegion === undefined) {
|
|
142
|
+
bag.accessibilityLiveRegion = ariaLive === 'off' ? 'none' : ariaLive;
|
|
143
|
+
}
|
|
144
|
+
// One input, TWO outputs, and the second is conditional on the VALUE rather than on presence.
|
|
145
|
+
if (ariaHidden !== undefined) {
|
|
146
|
+
if (bag.accessibilityElementsHidden === undefined) {
|
|
147
|
+
bag.accessibilityElementsHidden = ariaHidden;
|
|
148
|
+
}
|
|
149
|
+
if (ariaHidden === true && bag.importantForAccessibility === undefined) {
|
|
150
|
+
bag.importantForAccessibility = 'no-hide-descendants';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (ariaModal !== undefined && bag.accessibilityViewIsModal === undefined) {
|
|
154
|
+
bag.accessibilityViewIsModal = ariaModal;
|
|
155
|
+
}
|
|
156
|
+
if (typeof role === 'string' && bag.accessibilityRole === undefined) {
|
|
157
|
+
bag.accessibilityRole = ROLE_TO_ACCESSIBILITY_ROLE[role] ?? role;
|
|
158
|
+
}
|
|
159
|
+
// RULE TWO, INSIDE the composites: the polarity INVERTS and the ALIAS wins per field. Read from
|
|
160
|
+
// the ORIGINAL props, not from `bag` — the loop above has already blanked the aliases there.
|
|
161
|
+
//
|
|
162
|
+
// UPSTREAM-BUG(react-native): View.js:96 is `checked: ariaChecked ?? accessibilityState?.checked`
|
|
163
|
+
// — NO type coercion. A template writes `aria-checked="true"` as a STRING in every framework we
|
|
164
|
+
// support, so `accessibilityState.checked` reaches native as `'true'` where the native side
|
|
165
|
+
// declares `boolean | 'mixed'`. Ported verbatim for parity; do NOT add a cast without recording
|
|
166
|
+
// a deliberate divergence. Found by the Svelte session 2026-08-31 and pinned as an ASSERTION in
|
|
167
|
+
// `adapters/svelte/src/aria-fold-parity.test.ts`, so a future decision to coerce shows up as a
|
|
168
|
+
// failing test at the point the decision was made rather than as a silent behaviour change.
|
|
169
|
+
//
|
|
170
|
+
// A SECOND, SMALLER DIVERGENCE, and this one is ours rather than upstream's — it predates the
|
|
171
|
+
// move down and is kept only because changing it here would be an undeclared behaviour change:
|
|
172
|
+
// upstream gates the composite on `!= null` (View.js:87-92) and this gates on `!== undefined`.
|
|
173
|
+
// So an explicit `aria-busy={null}` builds an all-undefined `accessibilityState` here and builds
|
|
174
|
+
// nothing upstream. The VALUES agree either way — `??` treats null and undefined alike — so only
|
|
175
|
+
// the composite's existence differs.
|
|
176
|
+
//
|
|
177
|
+
// The composite is REPLACED by a fresh literal listing exactly the known fields, so an unknown
|
|
178
|
+
// field riding on the incoming object is dropped. Faithful to RN and pinned by a test; it is the
|
|
179
|
+
// shape of bug that only shows for whoever passes a field RN adds later.
|
|
180
|
+
const existingState = fieldOf(props, 'accessibilityState');
|
|
181
|
+
if (existingState !== undefined ||
|
|
182
|
+
ariaBusy !== undefined ||
|
|
183
|
+
ariaChecked !== undefined ||
|
|
184
|
+
ariaDisabled !== undefined ||
|
|
185
|
+
ariaExpanded !== undefined ||
|
|
186
|
+
ariaSelected !== undefined) {
|
|
187
|
+
bag.accessibilityState = {
|
|
188
|
+
busy: ariaBusy ?? fieldOf(existingState, 'busy'),
|
|
189
|
+
checked: ariaChecked ?? fieldOf(existingState, 'checked'),
|
|
190
|
+
disabled: ariaDisabled ?? fieldOf(existingState, 'disabled'),
|
|
191
|
+
expanded: ariaExpanded ?? fieldOf(existingState, 'expanded'),
|
|
192
|
+
selected: ariaSelected ?? fieldOf(existingState, 'selected'),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const existingValue = fieldOf(props, 'accessibilityValue');
|
|
196
|
+
if (existingValue !== undefined ||
|
|
197
|
+
ariaValueMax !== undefined ||
|
|
198
|
+
ariaValueMin !== undefined ||
|
|
199
|
+
ariaValueNow !== undefined ||
|
|
200
|
+
ariaValueText !== undefined) {
|
|
201
|
+
bag.accessibilityValue = {
|
|
202
|
+
max: ariaValueMax ?? fieldOf(existingValue, 'max'),
|
|
203
|
+
min: ariaValueMin ?? fieldOf(existingValue, 'min'),
|
|
204
|
+
now: ariaValueNow ?? fieldOf(existingValue, 'now'),
|
|
205
|
+
text: ariaValueText ?? fieldOf(existingValue, 'text'),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return bag;
|
|
209
|
+
}
|
|
@@ -14,6 +14,7 @@ export type ITaskCanceller = () => void;
|
|
|
14
14
|
export type ITaskCancelProvider = () => ITaskCanceller;
|
|
15
15
|
export interface IHostRegistrar {
|
|
16
16
|
registerRunnable(appKey: string, run: IRunnable): string;
|
|
17
|
+
registerCancellableHeadlessTask?(taskKey: string, taskProvider: ITaskProvider, taskCancelProvider: ITaskCancelProvider): void;
|
|
17
18
|
unmountAtRootTag?(rootTag: IRootTag): void;
|
|
18
19
|
}
|
|
19
20
|
export interface IAppRegistry<TComponentProvider, TWrapperComponentProvider> {
|
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
// the one thing each adapter supplies; everything else (registry bookkeeping, sections, the
|
|
7
7
|
// host-registrar bridge, headless tasks) is byte-identical across adapters and lives here once.
|
|
8
8
|
//
|
|
9
|
-
// The catch:
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// (setHostRegistrar), so the core stays react-native-free and the app glue wires the host once
|
|
13
|
-
// at startup.
|
|
9
|
+
// The catch: native invokes RN's registered callable AppRegistry for both surface runnables and
|
|
10
|
+
// headless tasks; it cannot see this local registry. `setHostRegistrar` mirrors those native-facing
|
|
11
|
+
// registrations there while the core remains react-native-free.
|
|
14
12
|
import { getNativeModule } from '../native-modules/index.js';
|
|
15
13
|
import { dlog } from '../debug.js';
|
|
16
14
|
const HEADLESS_TASK_MODULE = 'HeadlessJsTaskSupport';
|
|
@@ -121,6 +119,7 @@ export function createAppRegistry(runnableFor) {
|
|
|
121
119
|
}
|
|
122
120
|
taskProviders.set(taskKey, taskProvider);
|
|
123
121
|
taskCancelProviders.set(taskKey, taskCancelProvider);
|
|
122
|
+
hostRegistrar?.registerCancellableHeadlessTask?.(taskKey, taskProvider, taskCancelProvider);
|
|
124
123
|
},
|
|
125
124
|
startHeadlessTask(taskId, taskKey, data) {
|
|
126
125
|
runHeadlessTask(taskId, taskKey, data);
|
|
@@ -138,7 +137,18 @@ export function createAppRegistry(runnableFor) {
|
|
|
138
137
|
return {
|
|
139
138
|
AppRegistry,
|
|
140
139
|
setHostRegistrar(registrar) {
|
|
140
|
+
if (hostRegistrar === registrar)
|
|
141
|
+
return;
|
|
141
142
|
hostRegistrar = registrar;
|
|
143
|
+
// Headless tasks are commonly registered before adapter bootstrap attaches RN's callable
|
|
144
|
+
// AppRegistry. Replay only this missing native-facing registry; app runnables retain their
|
|
145
|
+
// existing register-after-bootstrap contract.
|
|
146
|
+
for (const [taskKey, taskProvider] of taskProviders) {
|
|
147
|
+
const taskCancelProvider = taskCancelProviders.get(taskKey);
|
|
148
|
+
if (taskCancelProvider !== undefined) {
|
|
149
|
+
registrar.registerCancellableHeadlessTask?.(taskKey, taskProvider, taskCancelProvider);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
142
152
|
},
|
|
143
153
|
};
|
|
144
154
|
}
|
package/build/commit.d.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { type IFabricNode, type IRootTag, type IMeasureOnSuccess, type IMeasureInWindowOnSuccess, type IMeasureLayoutOnSuccess } from './fabric';
|
|
2
2
|
import { type ISymbioteNode } from './node';
|
|
3
3
|
export { processColor, setColorProcessor } from './platform-color';
|
|
4
|
+
declare global {
|
|
5
|
+
var __SYMBIOTE_BATCH_CREATE__: boolean | undefined;
|
|
6
|
+
}
|
|
4
7
|
export declare function disposeRoot(rootTag: IRootTag): void;
|
|
5
8
|
export declare function commitChildren(rootTag: IRootTag, children: readonly ISymbioteNode[]): void;
|
|
6
9
|
export interface ICommitProfile {
|
|
7
10
|
commits: number;
|
|
8
11
|
walkMs: number;
|
|
9
12
|
nodesVisited: number;
|
|
13
|
+
/** Update-path nodes that rebuilt their Fabric payload and deep-compared it. */
|
|
14
|
+
propsBuilt: number;
|
|
15
|
+
/** Update-path nodes that re-cloned for a child but reused the committed payload by reference. */
|
|
16
|
+
propsReused: number;
|
|
10
17
|
propWrites: number;
|
|
11
18
|
propNoops: number;
|
|
12
19
|
childScans: number;
|
|
@@ -16,7 +23,22 @@ export interface ICommitProfile {
|
|
|
16
23
|
childFlattenWidest: number;
|
|
17
24
|
}
|
|
18
25
|
export declare function readCommitProfile(): ICommitProfile;
|
|
26
|
+
export declare function flushNativeProps(): void;
|
|
19
27
|
export declare function setNativeProps(node: ISymbioteNode, partial: Record<string, unknown>): void;
|
|
28
|
+
/**
|
|
29
|
+
* Publish a node whose props were changed OUTSIDE any renderer mutation.
|
|
30
|
+
*
|
|
31
|
+
* Dirtying is not publishing. Every other write reaches Fabric because the framework's own commit
|
|
32
|
+
* follows it; a change driven by a NATIVE EVENT has no such follow-up — `native-events.ts`
|
|
33
|
+
* requests no commit and no adapter does either, so a node marked dirty from an event handler
|
|
34
|
+
* simply stays dirty. The host-behavior press path (`setNodePressed` for `:active`) is the first
|
|
35
|
+
* caller that is not `setNativeProps`, and `setNodeHidden`'s React twin never needed it because
|
|
36
|
+
* the reconciler is already in its commit phase when it calls.
|
|
37
|
+
*
|
|
38
|
+
* Queued rather than committed on the spot, sharing `setNativeProps`' batch: several writes in one
|
|
39
|
+
* task publish together at the microtask boundary, one commit per surface.
|
|
40
|
+
*/
|
|
41
|
+
export declare function requestCommitFor(node: ISymbioteNode): void;
|
|
20
42
|
export declare function getNativeTag(node: ISymbioteNode): number | undefined;
|
|
21
43
|
export declare function whenCommitted(node: ISymbioteNode, action: () => void): () => void;
|
|
22
44
|
export declare function getNativeNode(node: ISymbioteNode): IFabricNode | undefined;
|