@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
@@ -6,8 +6,10 @@ export type IImageSize = {
6
6
  export type IImageCacheStatus = 'memory' | 'disk' | 'disk/memory';
7
7
  type ISizeSuccess = (width: number, height: number) => void;
8
8
  type ISizeFailure = (error: unknown) => void;
9
- declare function getSize(uri: string, success?: ISizeSuccess, failure?: ISizeFailure): Promise<IImageSize>;
10
- declare function getSizeWithHeaders(uri: string, headers: Record<string, string>, success?: ISizeSuccess, failure?: ISizeFailure): Promise<IImageSize>;
9
+ declare function getSize(uri: string): Promise<IImageSize>;
10
+ declare function getSize(uri: string, success: ISizeSuccess, failure?: ISizeFailure): undefined;
11
+ declare function getSizeWithHeaders(uri: string, headers: Record<string, string>): Promise<IImageSize>;
12
+ declare function getSizeWithHeaders(uri: string, headers: Record<string, string>, success: ISizeSuccess, failure?: ISizeFailure): undefined;
11
13
  declare function prefetch(uri: string, callback?: (requestId: number) => void): Promise<boolean>;
12
14
  declare function abortPrefetch(requestId: number): void;
13
15
  declare function queryCache(uris: string[]): Promise<Record<string, IImageCacheStatus>>;
@@ -1,27 +1,16 @@
1
- // Image static methods (RN's Image.getSize / prefetch / queryCache / etc).
2
- //
3
- // These mirror RN's iOS Image statics (Libraries/Image/Image.ios.js), which delegate to the
4
- // `ImageLoader` native (Turbo)Module declared in NativeImageLoaderIOS.js. The Android spec
5
- // (NativeImageLoaderAndroid.js) registers under the SAME module name ('ImageLoader'), so this
6
- // stays a flat, non-platform-split module - only the Android prefetch call signature differs (a
7
- // second `requestId` arg), branched on Platform.OS below, not on module name. NOTE the asymmetry
8
- // in the iOS spec: `getSize` resolves a `[width, height]` ARRAY, while `getSizeWithHeaders`
9
- // resolves a `{width, height}` OBJECT, both are guarded below before reading. The native result
10
- // crosses the I/O boundary as `unknown`; we never cast it, we narrow its shape.
11
- //
12
- // This is a stateful, native-bridge-touching imperative module (module-level ImageLoader cache +
13
- // prefetch requestId counter) with no view of its own - it belongs in @symbiote-native/engine
14
- // alongside Alert/Share, not in a view/render-*.ts file (whose contract is zero state / zero
15
- // native bridge).
1
+ // Image static methods (RN's Image.getSize/prefetch/queryCache/etc). Both iOS and Android specs
2
+ // register under the same module name ('ImageLoader'), so this stays flat, non-platform-split —
3
+ // only the Android prefetch call signature differs (a second requestId arg), branched below.
4
+ // The native result crosses the I/O boundary as unknown; we never cast it, we narrow its shape.
5
+ // This is a stateful, native-bridge-touching imperative module with no view of its own — it
6
+ // belongs here alongside Alert/Share, not in a view/render-*.ts file.
16
7
  import { dlog } from './debug.js';
17
8
  import { resolveImageSource, } from './image-source-resolver.js';
18
9
  import { getNativeModule } from './native-modules/index.js';
19
10
  import { Platform } from './platform';
20
11
  import { isNumber } from './type-guards.js';
21
- // The iOS native module name RN registers this under (NativeImageLoaderIOS.js resolves
22
- // `TurboModuleRegistry.getEnforcing<Spec>('ImageLoader')`). A module name like this is only
23
- // provable on a real host - a headless fake answers to any name - so this iOS name is
24
- // device-verify-pending.
12
+ // The native module name RN registers this under. A module name like this is only provable on a
13
+ // real host — a headless fake answers to any name.
25
14
  const IMAGE_LOADER_MODULE = 'ImageLoader';
26
15
  let imageLoaderModule;
27
16
  function getImageLoader() {
@@ -54,40 +43,29 @@ function requireLoader(method) {
54
43
  }
55
44
  return loader;
56
45
  }
57
- // Resolve image dimensions, optionally via success/failure callbacks. Always returns the Promise
58
- // too (RN returns void when a callback is passed, but a promise-and-callback shape is friendlier
59
- // and a strict superset).
46
+ // Image.ios.js / Image.android.js: the promise when no success callback is given; otherwise the
47
+ // result goes to the callbacks and nothing is returned, a missing `failure` becoming a warning.
48
+ function deliverSize(promise, uri, success, failure) {
49
+ if (typeof success !== 'function')
50
+ return promise;
51
+ promise
52
+ .then(size => success(size.width, size.height))
53
+ .catch(typeof failure === 'function'
54
+ ? failure
55
+ : () => console.warn('Failed to get size for image: ' + uri));
56
+ return undefined;
57
+ }
60
58
  function getSize(uri, success, failure) {
61
59
  const promise = Promise.resolve()
62
60
  .then(() => requireLoader('getSize').getSize(uri))
63
61
  .then(toImageSize);
64
- if (typeof success === 'function') {
65
- promise
66
- .then(size => success(size.width, size.height))
67
- .catch((error) => {
68
- if (typeof failure === 'function')
69
- failure(error);
70
- else
71
- dlog(`Image.getSize failed for ${uri}: ${String(error)}`);
72
- });
73
- }
74
- return promise;
62
+ return deliverSize(promise, uri, success, failure);
75
63
  }
76
64
  function getSizeWithHeaders(uri, headers, success, failure) {
77
65
  const promise = Promise.resolve()
78
66
  .then(() => requireLoader('getSizeWithHeaders').getSizeWithHeaders(uri, headers))
79
67
  .then(toImageSize);
80
- if (typeof success === 'function') {
81
- promise
82
- .then(size => success(size.width, size.height))
83
- .catch((error) => {
84
- if (typeof failure === 'function')
85
- failure(error);
86
- else
87
- dlog(`Image.getSizeWithHeaders failed for ${uri}: ${String(error)}`);
88
- });
89
- }
90
- return promise;
68
+ return deliverSize(promise, uri, success, failure);
91
69
  }
92
70
  // Android keys an in-flight prefetch by a monotonic requestId (so abortRequest can cancel it);
93
71
  // RN's Image.android.js generates the same way. iOS ignores the arg.
@@ -159,10 +137,8 @@ async function queryCache(uris) {
159
137
  throw error;
160
138
  });
161
139
  }
162
- // PURE JS: run the currently-installed source resolver (the same machinery the Image component
163
- // uses via resolveImageSource). RN's resolveAssetSource turns a require() asset id into
164
- // {uri, scale, ...}; the app injects the real one with setImageSourceResolver, and this exposes
165
- // its output to callers directly.
140
+ // Pure JS: run the currently-installed source resolver (the same machinery the Image component
141
+ // uses via resolveImageSource). The app injects the real one with setImageSourceResolver.
166
142
  function resolveAssetSource(source) {
167
143
  return resolveImageSource(source);
168
144
  }
@@ -1,10 +1,6 @@
1
- // Image source resolution seam: require('./x.png') asset ids and {uri} sources are resolved by
2
- // RN's own resolveAssetSource before reaching the shared render fn. The actual resolution is
3
- // RN-platform-specific, so it is injected here rather than imported, mirroring platform-color.ts's
4
- // processColor seam - this keeps @symbiote-native/components free of a react-native dependency (and
5
- // the headless harness working). BOTH the pure renderImage view (@symbiote-native/components) and
6
- // this package's own image-loader statics (resolveAssetSource) call resolveImageSource; neither
7
- // reaches into the mutable resolver directly.
1
+ // require('./x.png') asset ids and {uri} sources resolve via RN's own resolveAssetSource, which is
2
+ // platform-specific and injected here rather than imported (mirrors platform-color.ts's
3
+ // processColor seam) so @symbiote-native/components stays free of a react-native dependency.
8
4
  let sourceResolver = source => source;
9
5
  export function setImageSourceResolver(resolve) {
10
6
  sourceResolver = resolve;
@@ -1,16 +1,5 @@
1
1
  export declare const IMAGE_SOURCE_PROPS: ReadonlySet<string>;
2
- /**
3
- * Resolve a source prop and normalise it to the ARRAY shape native expects.
4
- *
5
- * Always an array, including for the single-object and asset-id cases: a bare object reaching
6
- * Fabric paints nothing and reports nothing, which is worse than an image that is simply absent.
7
- * The rule downstream then has one shape to reason about instead of three.
8
- *
9
- * A value this cannot make sense of comes back UNTOUCHED rather than wrapped. `routeProp` writes
10
- * whatever it is handed, and a tag with no image behavior must keep its props verbatim — the
11
- * control case in `image-payload.itest.ts` is what holds that line.
12
- */
13
- export declare function resolveImageSourceProp(value: unknown): unknown;
2
+ export declare function resolveImageSourceProp(value: unknown, dropsSingleSourceHeaders?: boolean): unknown;
14
3
  export declare const IMAGE_LOAD_EVENT_NAMES: ReadonlySet<string>;
15
4
  /** Whether at least one of the four still has a listener installed on the node. */
16
5
  export declare function anyImageLoadEventListenerWired(listeners: ReadonlyMap<string, unknown> | undefined): boolean;
@@ -1,17 +1,7 @@
1
- // Image sources, resolved on the way IN rather than on the way out.
2
- //
3
- // WHY IT IS HERE AND NOT IN THE PAYLOAD BUILDER, which is where every other part of Image's rule
4
- // now lives. `resolveImageSource` asks METRO'S ASSET REGISTRY — the table `require('./logo.png')`
5
- // indexes into, populated at bundle time, in JavaScript. There is no such table in C++ and there
6
- // should not be: it is the bundler's, not the platform's.
7
- //
8
- // This is the same seam and the same argument as `structured-style.ts`, which resolves
9
- // `boxShadow`/`filter`/`transform` at write time for the identical reason — a value resolved at
10
- // PAYLOAD-BUILD time is resolved HEADLESS ONLY, because the C++ builder has no JS to call, and the
11
- // device then commits the raw input and Fabric drops it in silence. Moving the lookup one step
12
- // earlier costs nothing and leaves the rest of the rule pure, which is what let it move at all.
13
- //
14
- // The three names are Image's: `source` is the real one, `defaultSource` the placeholder, and
1
+ // Image sources, resolved on the way IN rather than in the C++ payload builder like the rest of
2
+ // Image's rule: resolveImageSource asks Metro's asset registry, a JS-only, bundle-time table with
3
+ // no C++ side. Same seam as structured-style.ts's write-time boxShadow/filter/transform fold.
4
+ // The three names are Image's: `source` the real one, `defaultSource` the placeholder,
15
5
  // `loadingIndicatorSource` Android's spinner.
16
6
  import { resolveImageSource } from './image-source-resolver.js';
17
7
  export const IMAGE_SOURCE_PROPS = new Set([
@@ -19,34 +9,36 @@ export const IMAGE_SOURCE_PROPS = new Set([
19
9
  'defaultSource',
20
10
  'loadingIndicatorSource',
21
11
  ]);
22
- /**
23
- * Resolve a source prop and normalise it to the ARRAY shape native expects.
24
- *
25
- * Always an array, including for the single-object and asset-id cases: a bare object reaching
26
- * Fabric paints nothing and reports nothing, which is worse than an image that is simply absent.
27
- * The rule downstream then has one shape to reason about instead of three.
28
- *
29
- * A value this cannot make sense of comes back UNTOUCHED rather than wrapped. `routeProp` writes
30
- * whatever it is handed, and a tag with no image behavior must keep its props verbatim — the
31
- * control case in `image-payload.itest.ts` is what holds that line.
32
- */
33
- export function resolveImageSourceProp(value) {
12
+ // Resolve a source prop and normalize it to the array shape native expects — always an array,
13
+ // even for the single-object and asset-id cases, since a bare object reaching Fabric paints and
14
+ // reports nothing at all.
15
+ // A value this can't make sense of comes back untouched rather than wrapped: a tag with no image
16
+ // behavior must keep its props verbatim (pinned by image-payload.itest.ts).
17
+ export function resolveImageSourceProp(value, dropsSingleSourceHeaders = false) {
34
18
  if (value === undefined || value === null)
35
19
  return value;
36
20
  if (typeof value !== 'number' && typeof value !== 'object')
37
21
  return value;
38
22
  const resolved = resolveImageSource(value);
39
- return Array.isArray(resolved) ? resolved : [resolved];
23
+ if (Array.isArray(resolved))
24
+ return resolved;
25
+ // Android `source` only: `Image.android.js` lifts headers to the native `headers` prop just for an
26
+ // ARRAY source, and ReactImageView ignores per-source headers, so RN sends none for a single
27
+ // object. Wrapping here erases that shape, so its headers go here too.
28
+ if (dropsSingleSourceHeaders &&
29
+ typeof resolved === 'object' &&
30
+ resolved !== null &&
31
+ 'headers' in resolved) {
32
+ const { headers: _dropped, ...rest } = resolved;
33
+ return [rest];
34
+ }
35
+ return [resolved];
40
36
  }
41
- // `ReactImageView.setShouldNotifyLoadEvents` (Android) — `downloadListener` stays `null`, and
42
- // none of these four ever fires, until this prop is `true`. `Image.android.js` sets it whenever
43
- // ANY one of them is authored; iOS's native side has no such gate and never sets it.
44
- //
45
- // These four are real Fabric events (`view-config.ts`'s `COMPONENT_EVENTS.RCTImageView`), so
46
- // `routeProp` diverts them through `setEventListener`/`node.listeners`, never through `writeProp`
47
- // — unlike an ordinary function prop, they never reach the `functionProps` stash. Named here in
48
- // LISTENER form (post `listenerName()`: `onLoad` -> `load`), which is what `node.listeners` keys
49
- // on. Same shape `GATED_EVENT_PROPS` uses for `onLayout`, applied to a name no host behavior owns.
37
+ // Android's ReactImageView.setShouldNotifyLoadEvents gates on this prop: none of these four ever
38
+ // fire until Image.android.js sets it, because any one of them is authored; iOS has no such gate.
39
+ // Real Fabric events (view-config.ts's COMPONENT_EVENTS.RCTImageView), so routeProp diverts them
40
+ // through setEventListener/node.listeners, never writeProp — named in listener form (onLoad ->
41
+ // load), what node.listeners keys on.
50
42
  export const IMAGE_LOAD_EVENT_NAMES = new Set([
51
43
  'loadStart',
52
44
  'load',
@@ -2,47 +2,19 @@ import type { IMeasureInWindowOnSuccess, IMeasureLayoutOnSuccess, IMeasureOnSucc
2
2
  import { type ISymbioteNode } from './node';
3
3
  export declare function registerSurfaceCommit(commit: (rootTag: IRootTag) => void, forget: (rootTag: IRootTag) => void): void;
4
4
  export declare function flushNativeProps(): void;
5
- /**
6
- * Publish a node whose props changed OUTSIDE any renderer mutation.
7
- *
8
- * Recording is not publishing. Every other write reaches Fabric because the framework's own commit
9
- * follows it; a change driven by a NATIVE EVENT has no such follow-up — the press path's
10
- * `setNodePressed` is the first caller that is not `setNativeProps`.
11
- *
12
- * Queued rather than committed on the spot: several writes in one task publish together at the
13
- * microtask boundary, one commit per surface.
14
- */
15
5
  export declare function requestCommitFor(node: ISymbioteNode): void;
16
6
  export declare function disposeRoot(rootTag: IRootTag): void;
17
7
  /** The committed reactTag, stable across clone-on-write — what the native Animated driver binds. */
18
8
  export declare function getNativeTag(node: ISymbioteNode): number | undefined;
19
- /**
20
- * The node's current native handle, in kind identical to React's `stateNode.node`.
21
- *
22
- * OPAQUE. Under the native host it is a `ShadowNode`, under a headless one whatever that host
23
- * committed — see `ICommittedRecord.handle`. It used to be typed `IFabricNode`, a brand with no
24
- * members, so a caller can do exactly as much with it as before.
25
- */
26
9
  export declare function getNativeNode(node: ISymbioteNode): object | undefined;
27
10
  /** Called by the commit path once a batch has published. */
28
11
  export declare function notifyCommitted(): void;
29
- /**
30
- * Run `action` once `node` has a committed Fabric handle — immediately if it already does, else
31
- * after the commit that assigns one. Returns a cancel fn (drop the retry, e.g. on unmount).
32
- */
33
12
  export declare function whenCommitted(node: ISymbioteNode, action: () => void): () => void;
34
13
  export declare function dispatchViewCommand(node: ISymbioteNode, commandName: string, args: readonly unknown[]): void;
35
14
  export declare function sendAccessibilityEvent(node: ISymbioteNode, eventType: string): void;
36
15
  export declare function measure(node: ISymbioteNode, callback: IMeasureOnSuccess): void;
37
16
  export declare function measureInWindow(node: ISymbioteNode, callback: IMeasureInWindowOnSuccess): void;
38
17
  export declare function measureLayout(node: ISymbioteNode, relativeTo: ISymbioteNode, onSuccess: IMeasureLayoutOnSuccess, onFail?: () => void): void;
39
- /**
40
- * Tell native that JS has taken the gesture, or given it up.
41
- *
42
- * Through the HOST like the five above it, never through the Fabric slot: under the native tree
43
- * host the committed handle is our placeholder, and `nativeFabricUIManager.setIsJSResponder` unwraps
44
- * only a `ShadowNode` reference it minted itself.
45
- */
46
18
  export declare function setIsJSResponder(node: ISymbioteNode, isResponder: boolean, blockNativeResponder: boolean): void;
47
19
  /** The node's CURRENT prop value, as the host holds it. The behaviors' one read. */
48
20
  export declare function propOf(node: ISymbioteNode, key: string): unknown;
@@ -1,29 +1,18 @@
1
- // The imperative half of what `commit.ts` used to export — everything an app or an adapter reaches
2
- // for OUTSIDE a render: measure, view commands, accessibility events, the Animated fast path.
3
- //
4
- // It survived the tree's removal unchanged in SHAPE, because every one of these was already written
5
- // against three fields — a Fabric handle, a tag, a root tag — and never against the walk. What
6
- // changed is where those come from: `committedOf(node)` read an `IMirror` on the node, and the tree
7
- // HOST answers now (`tree-host.ts`) — native on device, the TypeScript applier headlessly.
8
- //
9
- // `undefined` is the ORDINARY state here, not an error. An adapter that wires an imperative call at
10
- // lifecycle time runs before `completeRoot` under an async-batched commit (Vue and Svelte schedule
11
- // it on a microtask), so the node has no handle yet. Every function either defers (`whenCommitted`)
12
- // or logs and returns — never throws.
1
+ // Everything an app or adapter reaches for outside a render: measure, view commands, accessibility
2
+ // events, the Animated fast path. Each is written against a Fabric handle/tag/root tag answered by
3
+ // the tree host (tree-host.ts): native on device, a TypeScript applier headlessly.
4
+ // undefined is the ordinary state here, not an error. An adapter wiring an imperative call at
5
+ // lifecycle time can run before completeRoot under an async-batched commit, so the node has no
6
+ // handle yet. Every function either defers (whenCommitted) or logs and returns — never throws.
13
7
  import { dlog, isDebug } from './debug.js';
14
8
  import { clearPublishedStyle, writeProp } from './node.js';
15
9
  import { flattenStyle } from './style/index.js';
16
10
  import { flushOps, treeHost } from './tree-host.js';
17
- /**
18
- * What Fabric currently holds for `node`, or `undefined` before its first commit.
19
- *
20
- * Flushes first: an imperative call can land between a mutation and its commit, and the host cannot
21
- * answer about ops it has not been handed.
22
- *
23
- * `undefined` is the ORDINARY state, not an error — and it is also what a WRAPPED node gets. The host
24
- * keys on the handle OBJECT, so a Vue `reactive()` / deep-`ref()` Proxy around a host element misses
25
- * and every function below degrades to its log. Hold host nodes with `shallowRef`.
26
- */
11
+ // What Fabric currently holds for `node`, or undefined before its first commit. Flushes first: an
12
+ // imperative call can land between a mutation and its commit, and the host can't answer about ops
13
+ // it hasn't been handed.
14
+ // undefined is also what a wrapped node gets: the host keys on the handle object, so a Vue
15
+ // reactive()/deep-ref() Proxy around a host element misses. Hold host nodes with shallowRef.
27
16
  function committedRecordOf(node) {
28
17
  flushOps();
29
18
  return treeHost()?.committedRecordOf(node);
@@ -48,16 +37,11 @@ export function flushNativeProps() {
48
37
  for (const rootTag of roots)
49
38
  commitSurface?.(rootTag);
50
39
  }
51
- /**
52
- * Publish a node whose props changed OUTSIDE any renderer mutation.
53
- *
54
- * Recording is not publishing. Every other write reaches Fabric because the framework's own commit
55
- * follows it; a change driven by a NATIVE EVENT has no such follow-up — the press path's
56
- * `setNodePressed` is the first caller that is not `setNativeProps`.
57
- *
58
- * Queued rather than committed on the spot: several writes in one task publish together at the
59
- * microtask boundary, one commit per surface.
60
- */
40
+ // Publish a node whose props changed outside any renderer mutation. Recording is not publishing:
41
+ // every other write reaches Fabric because the framework's own commit follows it, but a change
42
+ // driven by a native event has no such follow-up (setNodePressed is the first such caller).
43
+ // Queued rather than committed on the spot: several writes in one task publish together at the
44
+ // microtask boundary, one commit per surface.
61
45
  export function requestCommitFor(node) {
62
46
  const record = committedRecordOf(node);
63
47
  if (record === undefined) {
@@ -66,14 +50,9 @@ export function requestCommitFor(node) {
66
50
  }
67
51
  requestCommitForRoot(record.rootTag);
68
52
  }
69
- /**
70
- * The same request, for a caller that already holds the node's record.
71
- *
72
- * `committedRecordOf` crosses the host boundary, and `setNativeProps` fetches the record two lines
73
- * before it calls this — so asking again was two crossings for one fact, on the path an
74
- * `AnimatedProps` leaf runs once per frame per animated node. Measured at 2 of the 3 crossings a
75
- * frame made (`__tests__/animated-frame-cost.test.ts`).
76
- */
53
+ // The same request, for a caller that already holds the node's record. committedRecordOf crosses
54
+ // the host boundary, and setNativeProps fetches the record two lines before it calls this — asking
55
+ // again would be a second crossing for one fact, on a path an AnimatedProps leaf runs per frame.
77
56
  function requestCommitForRoot(rootTag) {
78
57
  pendingRoots.add(rootTag);
79
58
  if (!flushScheduled) {
@@ -92,20 +71,15 @@ export function disposeRoot(rootTag) {
92
71
  export function getNativeTag(node) {
93
72
  return committedRecordOf(node)?.tag;
94
73
  }
95
- /**
96
- * The node's current native handle, in kind identical to React's `stateNode.node`.
97
- *
98
- * OPAQUE. Under the native host it is a `ShadowNode`, under a headless one whatever that host
99
- * committed — see `ICommittedRecord.handle`. It used to be typed `IFabricNode`, a brand with no
100
- * members, so a caller can do exactly as much with it as before.
101
- */
74
+ // The node's current native handle, in kind identical to React's stateNode.node. Opaque: under the
75
+ // native host it's a ShadowNode, under a headless one whatever that host committed (see
76
+ // ICommittedRecord.handle).
102
77
  export function getNativeNode(node) {
103
78
  return committedRecordOf(node)?.handle;
104
79
  }
105
- // Actions waiting for their node's first commit. An adapter that wires an imperative call at
106
- // lifecycle time can run BEFORE completeRoot under an async-batched commit, so the node has no
107
- // handle yet and the call would silently no-op. Each waiter retries after a commit and is dropped
108
- // once it runs. React commits synchronously, so its actions run inline and never land here.
80
+ // Actions waiting for their node's first commit — an adapter wiring a call at lifecycle time can
81
+ // run before completeRoot under an async-batched commit, so the node has no handle yet. Each
82
+ // waiter retries after a commit and is dropped once it runs; React runs its actions inline.
109
83
  const pendingCommitWaiters = new Set();
110
84
  /** Called by the commit path once a batch has published. */
111
85
  export function notifyCommitted() {
@@ -114,10 +88,8 @@ export function notifyCommitted() {
114
88
  pendingCommitWaiters.delete(waiter);
115
89
  }
116
90
  }
117
- /**
118
- * Run `action` once `node` has a committed Fabric handle — immediately if it already does, else
119
- * after the commit that assigns one. Returns a cancel fn (drop the retry, e.g. on unmount).
120
- */
91
+ // Run `action` once `node` has a committed Fabric handle — immediately if it already does, else
92
+ // after the commit that assigns one. Returns a cancel fn to drop the retry (e.g. on unmount).
121
93
  export function whenCommitted(node, action) {
122
94
  const attempt = () => {
123
95
  if (committedRecordOf(node) === undefined)
@@ -175,13 +147,9 @@ export function measureLayout(node, relativeTo, onSuccess, onFail = () => { }) {
175
147
  }
176
148
  treeHost()?.measureLayout(node, relativeTo, onFail, onSuccess);
177
149
  }
178
- /**
179
- * Tell native that JS has taken the gesture, or given it up.
180
- *
181
- * Through the HOST like the five above it, never through the Fabric slot: under the native tree
182
- * host the committed handle is our placeholder, and `nativeFabricUIManager.setIsJSResponder` unwraps
183
- * only a `ShadowNode` reference it minted itself.
184
- */
150
+ // Tell native that JS has taken the gesture, or given it up. Through the host like the five above
151
+ // it, never through the Fabric slot: under the native tree host the committed handle is our
152
+ // placeholder, and nativeFabricUIManager's own version unwraps only a ShadowNode it minted itself.
185
153
  export function setIsJSResponder(node, isResponder, blockNativeResponder) {
186
154
  if (committedRecordOf(node) === undefined) {
187
155
  dlog('setIsJSResponder skipped: node not committed');
@@ -194,22 +162,12 @@ export function propOf(node, key) {
194
162
  flushOps();
195
163
  return treeHost()?.propOf(node, key);
196
164
  }
197
- // Per-frame prop write for the JS-driven Animated path, and the one write that bypasses the
198
- // declarative style. RN flushes an animation frame with an in-place `instance.setNativeProps(...)`;
199
- // Fabric is persistent, so a frame is one scoped commit instead.
200
- //
201
- // IT NO LONGER BYPASSES ANYTHING, and the correction it used to owe the host is gone with the
202
- // bypass. This wrote to Fabric directly once, so the host had to be told what had been sent behind
203
- // its back or its next diff would be computed against a stale base. The write is an ordinary op now
204
- // — the commit that carries it updates that record itself. Telling the host EARLY was strictly
205
- // worse than not telling it: `noteNativePropsBypass` folded the values into `committedProps` before
206
- // the commit ran, so the diff found nothing to send and the write never reached Fabric at all.
207
- //
208
- // One correction survives, because it is about the ENGINE's own state rather than the host's:
209
- // `parts.published` is cleared, so `pushClassStyle`'s guard cannot turn away the re-push that
210
- // RESTORES the declarative style this frame just overwrote. The guard is exact, and an app handing
211
- // over a hoisted style constant (StyleSheet.create, a module-level object) would otherwise never
212
- // get it back.
165
+ // Per-frame prop write for the JS-driven Animated path — RN flushes an animation frame with an
166
+ // in-place instance.setNativeProps(...); Fabric is persistent, so a frame is one scoped commit
167
+ // instead, an ordinary op the commit that carries it accounts for on its own.
168
+ // One correction survives, about the engine's own state: parts.published is cleared, so
169
+ // pushClassStyle's guard can't turn away the re-push that restores the declarative style this
170
+ // frame just overwrote — otherwise a hoisted style constant would never come back.
213
171
  export function setNativeProps(node, partial) {
214
172
  const record = committedRecordOf(node);
215
173
  if (record === undefined) {
@@ -229,30 +187,22 @@ export function setNativeProps(node, partial) {
229
187
  }
230
188
  else {
231
189
  merged[key] = value;
232
- // This path used to owe the aria gate that `setProp` carried, since it bypasses it. There is
233
- // no gate to owe any more: the fold is the device's rule and it recomputes presence from the
234
- // bag. See `node.ts`'s note at the old choke point.
235
190
  }
236
191
  }
237
192
  for (const [key, value] of Object.entries(merged)) {
238
- // `writeProp`, not `recordSetProp`: this is the path with no `routeProp` in front of it, so a
239
- // function reaches the wire from here and nowhere else. `AnimatedProps.__getValue()` copies
240
- // every key its leaf holds, `panHandlers` included.
193
+ // writeProp, not recordSetProp: this is the path with no routeProp in front of it, so a
194
+ // function reaches the wire from here and nowhere else (AnimatedProps.__getValue() copies
195
+ // every key its leaf holds, panHandlers included).
241
196
  writeProp(node, key, value);
242
197
  }
243
198
  if (merged.style !== undefined)
244
199
  clearPublishedStyle(node);
245
- // GATED, and this is the one place in the engine where "per frame" is the right unit to worry
246
- // about. An `AnimatedProps` leaf calls this once per frame per animated node, so the template
247
- // string and the `Object.keys` array it builds are 60 allocations a second per node — times the
248
- // list, for a list that animates its rows. F-61 dismissed 347 ungated arguments because none of
249
- // them was on a path that repeats; this one is, and F-61 had it in its own in-loop list and
250
- // priced it as "runs once".
200
+ // Gated: an AnimatedProps leaf calls this once per frame per animated node, so the template
201
+ // string and Object.keys array below would otherwise allocate 60 times a second per node.
251
202
  if (isDebug()) {
252
203
  dlog(`setNativeProps root=${record.rootTag} tag=${record.tag} keys=${Object.keys(partial)}`);
253
204
  }
254
205
  // Queued, not committed: every write made in this task publishes together at the microtask
255
- // boundary. With the record already in hand — asking for it again is a second crossing for a
256
- // fact this function fetched four lines up.
206
+ // boundary. The record is already in hand, so this avoids a second crossing for the same fact.
257
207
  requestCommitForRoot(record.rootTag);
258
208
  }
package/build/index.d.ts CHANGED
@@ -65,6 +65,7 @@ export { imageStatics } from './image-loader';
65
65
  export type { IImageStatics, IImageSize, IImageCacheStatus, } from './image-loader';
66
66
  export { setImageSourceResolver, resolveImageSource, } from './image-source-resolver';
67
67
  export type { IImageSource, IImageSourceProp } from './image-source-resolver';
68
+ export { setAssetSourceResolver, resolveAssetSource, } from './asset-source-resolver';
68
69
  export { ActionSheetIOS } from './action-sheet-ios';
69
70
  export type { IActionSheetIOSOptions, IShareActionSheetIOSOptions, IShareActionSheetError, } from './action-sheet-ios';
70
71
  export { Linking } from './linking';
@@ -90,8 +91,8 @@ export type { IKeyboardEventName, IKeyboardEvent, IKeyboardMetrics, } from './ke
90
91
  export { currentlyFocusedInput, setInputFocused, setInputBlurred, blurTextInput, focusTextInput, } from './text-input-state';
91
92
  export { LayoutAnimation } from './layout-animation';
92
93
  export type { ILayoutAnimationType, ILayoutAnimationProperty, ILayoutAnimationConfig, ILayoutAnimationAnim, ILayoutAnimationTypes, ILayoutAnimationProperties, } from './layout-animation';
93
- export { BackHandler } from './back-handler';
94
- export type { IBackPressEventName, IBackPressHandler } from './back-handler';
94
+ export { BackHandler, installBackHandler } from './back-handler';
95
+ export type { IBackPressEventName, IBackPressHandler, IHardwareBackPressEvent, } from './back-handler';
95
96
  export { PermissionsAndroid, PERMISSIONS, RESULTS, } from './permissions-android';
96
97
  export type { IPermission, IPermissionStatus, IRationale, } from './permissions-android';
97
98
  export { AccessibilityInfo } from './accessibility-info';