@symbiote-native/engine 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/index.js +2 -2
- 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 +0 -28
- package/build/imperative.js +42 -92
- package/build/index.d.ts +1 -0
- package/build/index.js +24 -37
- package/build/mutation-buffer.d.ts +0 -177
- package/build/mutation-buffer.js +137 -308
- package/build/native-engine.d.ts +0 -102
- package/build/native-engine.js +38 -98
- package/build/native-events.js +9 -18
- package/build/native-tree-host.d.ts +0 -21
- package/build/native-tree-host.js +14 -31
- package/build/node.d.ts +0 -212
- package/build/node.js +338 -774
- 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 +0 -270
- package/build/tree-host.js +63 -153
- package/build/view-config.js +17 -37
- package/package.json +2 -2
|
@@ -1,13 +1,2 @@
|
|
|
1
1
|
export declare const ARIA_ALIAS_KEYS: readonly ["role", "aria-label", "aria-labelledby", "aria-live", "aria-hidden", "aria-busy", "aria-checked", "aria-disabled", "aria-expanded", "aria-selected", "aria-modal", "aria-valuemax", "aria-valuemin", "aria-valuenow", "aria-valuetext"];
|
|
2
|
-
/**
|
|
3
|
-
* Fold the web-alias `aria-*` / `role` props into RN's canonical `accessibility*` props.
|
|
4
|
-
*
|
|
5
|
-
* Returns the input BY IDENTITY when no alias is present — the fast path that keeps this off the
|
|
6
|
-
* hot path for the ~99% of nodes carrying none, and the property idempotence rests on: pass 1
|
|
7
|
-
* blanks every alias, so a second pass finds nothing and returns by identity again.
|
|
8
|
-
*
|
|
9
|
-
* The alias keys are blanked to `undefined` rather than deleted. That is not laziness: `setProp`
|
|
10
|
-
* treats an `undefined` write as a delete and `fabricProps` skips undefined, so a blanked alias
|
|
11
|
-
* cannot reach Fabric, while a `delete` would deoptimise the object's shape on every folded node.
|
|
12
|
-
*/
|
|
13
2
|
export declare function foldAriaProps(props: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -1,27 +1,14 @@
|
|
|
1
|
-
// RN's
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// PRECEDENCE RULES: for the scalars an explicit `accessibility*` WINS and the alias only fills a
|
|
10
|
-
// hole (`if (next.X === undefined)`), while INSIDE the `accessibilityState` / `accessibilityValue`
|
|
11
|
-
// composites the ALIAS wins per field (`ariaBusy ?? existing?.busy`). Both mirror RN's View.js.
|
|
12
|
-
// Anyone "cleaning this up" collapses them into one rule, and every component test stays green
|
|
13
|
-
// while real accessibility silently changes. `core/components/src/accessibility-props.test.ts`
|
|
14
|
-
// pins both directions; read it before touching the branches below.
|
|
15
|
-
//
|
|
16
|
-
// Record-level rather than typed, because the engine's caller has a raw `node.props` bag and an
|
|
17
|
-
// interface is not assignable to `Record<string, unknown>` (no index signature). The typed
|
|
18
|
-
// `resolveAccessibilityProps<T>` in `core/components` stays where adapters already import it and
|
|
19
|
-
// delegates here, keeping its own typed gate so the fast path allocates nothing.
|
|
1
|
+
// RN's aria-* / role -> accessibility* fold, at the layer every path goes through: it can't run
|
|
2
|
+
// per attribute (aria-checked folds against a sibling accessibilityState), so it belongs at the
|
|
3
|
+
// one point where the whole bag is known, the payload build.
|
|
4
|
+
// A move, not a rewrite: the function carries two contradictory precedence rules — scalars let an
|
|
5
|
+
// explicit accessibility* win, alias only fills a hole; the composites let the alias win per field
|
|
6
|
+
// instead. Both mirror RN's View.js. accessibility-props.test.ts pins both directions.
|
|
7
|
+
// Record-level rather than typed: the engine's caller has a raw node.props bag with no index
|
|
8
|
+
// signature. The typed resolveAccessibilityProps<T> in core/components delegates here.
|
|
20
9
|
import { dlog } from './debug.js';
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
// unmapped, so `role="listitem"` would have reached Fabric as `listitem` instead of `list` with
|
|
24
|
-
// nothing red anywhere. Diff this against RN's View.js rather than reading it for plausibility.
|
|
10
|
+
// A role that falls through here passes unmapped, reaching Fabric under the wrong name with
|
|
11
|
+
// nothing red anywhere. Diff against RN's View.js, don't just read for plausibility.
|
|
25
12
|
const ROLE_TO_ACCESSIBILITY_ROLE = {
|
|
26
13
|
alert: 'alert',
|
|
27
14
|
button: 'button',
|
|
@@ -52,14 +39,11 @@ const ROLE_TO_ACCESSIBILITY_ROLE = {
|
|
|
52
39
|
timer: 'timer',
|
|
53
40
|
toolbar: 'toolbar',
|
|
54
41
|
};
|
|
55
|
-
// Exported so a behavior
|
|
56
|
-
// list
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
// consumer index a prop type with them — `pickAccessibilityProps` in the Svelte adapter forwards the
|
|
61
|
-
// aria half by looping this list — and it makes the two sides check each other: a name here that is
|
|
62
|
-
// not a key of `IAriaProps` stops compiling at the use site rather than going quietly unforwarded.
|
|
42
|
+
// Exported so a behavior folding a different node's bag can name these without restating the
|
|
43
|
+
// list — slotDerived (host-behavior.ts) takes prop names, so a derived primitive enumerates them.
|
|
44
|
+
// as const rather than readonly string[], so the members are literals: pickAccessibilityProps
|
|
45
|
+
// (Svelte adapter) indexes IAriaProps with them, and a name here not a key of IAriaProps then
|
|
46
|
+
// fails to compile at the use site instead of going quietly unforwarded.
|
|
63
47
|
export const ARIA_ALIAS_KEYS = [
|
|
64
48
|
'role',
|
|
65
49
|
'aria-label',
|
|
@@ -86,28 +70,17 @@ function hasAnyAriaKey(props) {
|
|
|
86
70
|
}
|
|
87
71
|
return false;
|
|
88
72
|
}
|
|
89
|
-
// `isAriaAliasKey` WAS HERE AND IS GONE (2026-09-18). Its whole reason was to maintain
|
|
90
|
-
// `node.hasAriaAlias` from `setProp` — the hottest write path in the engine, 32 001 writes on one
|
|
91
|
-
// benchmark create — and that flag existed only to gate the aria fold inside the headless payload
|
|
92
|
-
// builder. The fold left for `SymbioteFabricProps.cpp`, which recomputes presence from the bag it
|
|
93
|
-
// holds, so the flag became write-only and this function became its only maintainer. Both went.
|
|
94
73
|
function isRecord(value) {
|
|
95
74
|
return typeof value === 'object' && value !== null;
|
|
96
75
|
}
|
|
97
76
|
function fieldOf(source, field) {
|
|
98
77
|
return isRecord(source) ? source[field] : undefined;
|
|
99
78
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
* blanks every alias, so a second pass finds nothing and returns by identity again.
|
|
106
|
-
*
|
|
107
|
-
* The alias keys are blanked to `undefined` rather than deleted. That is not laziness: `setProp`
|
|
108
|
-
* treats an `undefined` write as a delete and `fabricProps` skips undefined, so a blanked alias
|
|
109
|
-
* cannot reach Fabric, while a `delete` would deoptimise the object's shape on every folded node.
|
|
110
|
-
*/
|
|
79
|
+
// Fold the web-alias aria-*/role props into RN's canonical accessibility* props. Returns the input
|
|
80
|
+
// by identity when no alias is present, keeping this off the hot path for nodes carrying none —
|
|
81
|
+
// and idempotent, since pass 1 blanks every alias so a second pass finds nothing.
|
|
82
|
+
// Alias keys are blanked to undefined rather than deleted: setProp treats undefined as a delete
|
|
83
|
+
// and fabricProps skips it, while a real delete would deoptimise the object's shape.
|
|
111
84
|
export function foldAriaProps(props) {
|
|
112
85
|
if (!hasAnyAriaKey(props))
|
|
113
86
|
return props;
|
|
@@ -157,27 +130,16 @@ export function foldAriaProps(props) {
|
|
|
157
130
|
if (typeof role === 'string' && bag.accessibilityRole === undefined) {
|
|
158
131
|
bag.accessibilityRole = ROLE_TO_ACCESSIBILITY_ROLE[role] ?? role;
|
|
159
132
|
}
|
|
160
|
-
//
|
|
161
|
-
// the
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
// A SECOND, SMALLER DIVERGENCE, and this one is ours rather than upstream's — it predates the
|
|
172
|
-
// move down and is kept only because changing it here would be an undeclared behaviour change:
|
|
173
|
-
// upstream gates the composite on `!= null` (View.js:87-92) and this gates on `!== undefined`.
|
|
174
|
-
// So an explicit `aria-busy={null}` builds an all-undefined `accessibilityState` here and builds
|
|
175
|
-
// nothing upstream. The VALUES agree either way — `??` treats null and undefined alike — so only
|
|
176
|
-
// the composite's existence differs.
|
|
177
|
-
//
|
|
178
|
-
// The composite is REPLACED by a fresh literal listing exactly the known fields, so an unknown
|
|
179
|
-
// field riding on the incoming object is dropped. Faithful to RN and pinned by a test; it is the
|
|
180
|
-
// shape of bug that only shows for whoever passes a field RN adds later.
|
|
133
|
+
// Rule two, inside the composites: the polarity inverts and the alias wins per field. Read from
|
|
134
|
+
// the original props, not bag — the loop above has already blanked the aliases there.
|
|
135
|
+
// Upstream bug, ported verbatim: RN's View.js does `checked: ariaChecked ?? accessibilityState
|
|
136
|
+
// ?.checked` with no coercion, so a string "true" reaches native where it declares boolean |
|
|
137
|
+
// 'mixed'. No cast without recording a divergence (aria-fold-parity.test.ts pins it).
|
|
138
|
+
// A second, smaller divergence, ours rather than upstream's: upstream gates the composite on
|
|
139
|
+
// `!= null`, this on `!== undefined`, so `aria-busy={null}` builds an all-undefined
|
|
140
|
+
// accessibilityState here but nothing upstream — the values agree either way.
|
|
141
|
+
// The composite is replaced by a fresh literal listing exactly the known fields, so an unknown
|
|
142
|
+
// field riding on the incoming object is dropped, faithful to RN.
|
|
181
143
|
const existingState = fieldOf(props, 'accessibilityState');
|
|
182
144
|
if (existingState !== undefined ||
|
|
183
145
|
ariaBusy !== undefined ||
|
package/build/animated/graph.js
CHANGED
|
@@ -278,7 +278,7 @@ export function flushValue(rootNode) {
|
|
|
278
278
|
// in RELEASE builds only (dev was fine, every headless test was fine, and the code was even
|
|
279
279
|
// present in the bundle — it just never ran). Adding a bare `import './interpolation-node'` to
|
|
280
280
|
// the barrel does NOT fix it either: Babel merges that import with the adjacent re-export of the
|
|
281
|
-
// same specifier, and the merged dependency stays lazy. Both were tried on device
|
|
281
|
+
// same specifier, and the merged dependency stays lazy. Both were tried on device.
|
|
282
282
|
//
|
|
283
283
|
// Keeping the class next to its base class removes the cycle, the factory, and the entire class
|
|
284
284
|
// of load-order bug with it: `interpolate()` constructs it directly and there is nothing left to
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// create-animated-component.tsx, Vue's create-animated-component.ts, Svelte's
|
|
8
8
|
// animated-props-runtime.ts, Angular's animated-leaf-binder.ts). Each re-derived the same policy,
|
|
9
9
|
// and they drifted: only Svelte ever grew the rebuild guard below, after a day of device
|
|
10
|
-
// debugging
|
|
10
|
+
// debugging. React's `useMemo(..., [rest])` LOOKS like the same guard but is not - its
|
|
11
11
|
// dependency is a fresh rest-destructured object every render - and Vue's `pendingLeaf === null`
|
|
12
12
|
// answers "has render run yet", not "did anything change". A fix landing in one copy is exactly
|
|
13
13
|
// what <adapters_reach_full_feature_parity> forbids: parity has to be structural.
|
|
@@ -86,7 +86,7 @@ export function createAnimatedLeafLifecycle(label) {
|
|
|
86
86
|
lastWantsNative = wantsNative;
|
|
87
87
|
// A SNAPSHOT, never the reference. A caller can legitimately hand back the SAME object on
|
|
88
88
|
// every update, mutating what its keys resolve to rather than allocating (Svelte's rest
|
|
89
|
-
// proxy does exactly that, device-confirmed
|
|
89
|
+
// proxy does exactly that, device-confirmed); storing it would make the next
|
|
90
90
|
// call run shallowEqualProps on that object against ITSELF - reading the same current values
|
|
91
91
|
// through both sides - which is unconditionally true. `propsChanged` could then never be
|
|
92
92
|
// true again and reconcile would be skipped forever, so every rebuilt AnimatedInterpolation
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Generic asset-id resolution seam, the non-Image sibling of image-source-resolver.ts. Kept as a
|
|
2
|
+
// separate file so a non-image consumer (audio's `number` source, sqlite's `assetSource`) never
|
|
3
|
+
// has to import something named "image" to resolve its own assets.
|
|
4
|
+
let sourceResolver = source => source;
|
|
5
|
+
export function setAssetSourceResolver(resolve) {
|
|
6
|
+
sourceResolver = resolve;
|
|
7
|
+
}
|
|
8
|
+
// Public mirror of image-source-resolver.ts's resolveImageSource, for non-Image consumers of RN's
|
|
9
|
+
// require()'d-asset resolution (audio's `number` source form, sqlite's `assetSource` option).
|
|
10
|
+
// Headless (no resolver wired) it is the identity, so smokes see the input unchanged.
|
|
11
|
+
export function resolveAssetSource(source) {
|
|
12
|
+
return sourceResolver(source);
|
|
13
|
+
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { type IEventSubscription } from '../native-events';
|
|
2
2
|
declare const DEVICE_BACK_EVENT = "hardwareBackPress";
|
|
3
|
-
|
|
4
|
-
readonly backPress: "backPress";
|
|
5
|
-
readonly hardwareBackPress: "hardwareBackPress";
|
|
6
|
-
};
|
|
7
|
-
export type IBackPressEventName = (typeof BACK_PRESS_EVENT)[keyof typeof BACK_PRESS_EVENT];
|
|
3
|
+
export type IBackPressEventName = 'backPress' | 'hardwareBackPress';
|
|
8
4
|
export type IHardwareBackPressEvent = {
|
|
9
5
|
readonly type: typeof DEVICE_BACK_EVENT;
|
|
10
6
|
readonly timeStamp: number;
|
|
@@ -17,12 +17,6 @@ import { dlog } from '../debug.js';
|
|
|
17
17
|
const DEVICE_EVENT_MANAGER_MODULE = 'DeviceEventManager';
|
|
18
18
|
// The device event native emits when the hardware back button is pressed.
|
|
19
19
|
const DEVICE_BACK_EVENT = 'hardwareBackPress';
|
|
20
|
-
// The public event names callers subscribe to. `backPress` is RN's legacy alias
|
|
21
|
-
// for `hardwareBackPress`; both map to the same chain.
|
|
22
|
-
const BACK_PRESS_EVENT = {
|
|
23
|
-
backPress: 'backPress',
|
|
24
|
-
hardwareBackPress: 'hardwareBackPress',
|
|
25
|
-
};
|
|
26
20
|
function nativeTimeStamp(nativeEvent) {
|
|
27
21
|
if (typeof nativeEvent !== 'object' || nativeEvent === null)
|
|
28
22
|
return undefined;
|
package/build/debug.js
CHANGED
|
@@ -1,25 +1,11 @@
|
|
|
1
|
-
// Opt-in diagnostic logging, off by default. Two switches
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// but ONLY if the caller does not build the message itself first. A template
|
|
10
|
-
// literal is evaluated at the CALL SITE, before dlog can decide anything, so a
|
|
11
|
-
// `dlog(\`… ${JSON.stringify(x)}\`)` on a per-frame path costs its full price with
|
|
12
|
-
// logging off. On a hot path (a getter Angular re-reads every change-detection
|
|
13
|
-
// pass, an Animated reconcile, a scroll-driven apply) pass a THUNK instead:
|
|
14
|
-
// `dlog(() => \`…\`)` - it is only called once the switch is on.
|
|
15
|
-
// Read ONCE, at module load. `process.env` is not a plain object in Node - each property read
|
|
16
|
-
// crosses into the host environment - and isDebug() is called on the per-node commit path, so the
|
|
17
|
-
// per-call read showed up as 17% of self time in a create-path CPU profile (headless; on a native
|
|
18
|
-
// host `process` is a shim and the read is cheap, so treat that figure as a Node one).
|
|
19
|
-
//
|
|
20
|
-
// Safe to freeze because nothing toggles the ENV switch mid-process: every runtime toggle in the
|
|
21
|
-
// repo goes through __SYMBIOTE_DEBUG__ below, which stays dynamic, and bootstrap mirrors the env
|
|
22
|
-
// onto it at start anyway.
|
|
1
|
+
// Opt-in diagnostic logging, off by default. Two switches: env DEBUG=1 (Node, headless smokes;
|
|
2
|
+
// mirrored onto globalThis.__SYMBIOTE_DEBUG__ at bootstrap, so toggling it needs a Metro
|
|
3
|
+
// --reset-cache, not a rebuild), or setting globalThis.__SYMBIOTE_DEBUG__ directly.
|
|
4
|
+
// Read ONCE, at module load: `process.env` is not a plain object in Node, each property read
|
|
5
|
+
// crosses into the host environment, and isDebug() runs on the per-node commit path — costly in
|
|
6
|
+
// Node, cheap on a native host where `process` is just a shim.
|
|
7
|
+
// Safe to freeze: nothing toggles the env switch mid-process, every runtime toggle goes through
|
|
8
|
+
// the dynamic __SYMBIOTE_DEBUG__ instead.
|
|
23
9
|
const envDebug = typeof process !== 'undefined' && process.env.DEBUG === '1';
|
|
24
10
|
export function isDebug() {
|
|
25
11
|
return envDebug || globalThis.__SYMBIOTE_DEBUG__ === true;
|
package/build/dispatch.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
// The one place a native event re-enters a framework's update loop
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// adapter injects a wrapper that runs the listener at the right priority and
|
|
5
|
-
// flushes synchronously so the result paints (React: discrete lane +
|
|
6
|
-
// flushSyncWork). Both event rails, Fabric events (events.ts) and device-module
|
|
7
|
-
// events (native-events.ts), route through this single seam, so the adapter
|
|
8
|
-
// wires it once and both are covered. Default is a pass-through for adapters (and
|
|
9
|
-
// the headless harness) that need no wrapping.
|
|
1
|
+
// The one place a native event re-enters a framework's update loop: a native event runs its
|
|
2
|
+
// listener outside that loop, so the adapter injects a wrapper that runs it at the right priority
|
|
3
|
+
// and flushes synchronously. Both Fabric events and device-module events share this seam.
|
|
10
4
|
let wrapDispatch = run => {
|
|
11
5
|
run();
|
|
12
6
|
};
|
package/build/events/index.js
CHANGED
|
@@ -260,8 +260,8 @@ function findWantsResponder(path, captureName, bubbleName, nativeEvent, skip) {
|
|
|
260
260
|
// WITHOUT THIS A JS RESPONDER LOSES TO ANY SCROLL VIEW ABOVE IT, and it is invisible from JS:
|
|
261
261
|
// `onStartShouldSetResponder` returns true, the native UIScrollView never learns the gesture was
|
|
262
262
|
// claimed, and every subsequent move arrives as `topScroll` instead of `topTouchMove` — so the
|
|
263
|
-
// negotiation never even gets a move to grant on. Device-diagnosed
|
|
264
|
-
//
|
|
263
|
+
// negotiation never even gets a move to grant on. Device-diagnosed on a PanResponder drag box
|
|
264
|
+
// inside the canary's ScrollView: `startShouldSet -> true` followed by
|
|
265
265
|
// `topScrollBeginDrag` and twenty `topScroll`, with no grant and no move.
|
|
266
266
|
//
|
|
267
267
|
// `blockNativeResponder` is the taker's own `onResponderGrant` return, exactly as RN reads it.
|