@symbiote-native/engine 0.1.6 → 0.2.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.
Files changed (67) hide show
  1. package/build/accessibility-info/index.android.js +1 -1
  2. package/build/accessibility-info/index.ios.js +1 -1
  3. package/build/action-sheet-ios/index.js +1 -1
  4. package/build/alert/index.android.js +1 -1
  5. package/build/alert/index.ios.js +11 -1
  6. package/build/animated/color.d.ts +2 -8
  7. package/build/animated/color.js +4 -45
  8. package/build/animated/easing.js +0 -8
  9. package/build/animated/graph.d.ts +13 -5
  10. package/build/animated/graph.js +85 -11
  11. package/build/animated/index.d.ts +2 -2
  12. package/build/animated/index.js +2 -2
  13. package/build/animated/interpolation.js +1 -1
  14. package/build/animated/leaf-lifecycle.d.ts +7 -0
  15. package/build/animated/leaf-lifecycle.js +185 -0
  16. package/build/animated/native/native-animated.js +2 -1
  17. package/build/animated/rgba.d.ts +8 -0
  18. package/build/animated/rgba.js +55 -0
  19. package/build/animated/value.js +12 -7
  20. package/build/app-registry/index.js +1 -1
  21. package/build/app-state/index.js +1 -1
  22. package/build/appearance/index.js +1 -1
  23. package/build/back-handler/index.js +1 -1
  24. package/build/commit.js +1 -5
  25. package/build/debug.d.ts +1 -1
  26. package/build/debug.js +10 -3
  27. package/build/dimensions/index.js +1 -1
  28. package/build/events/index.js +0 -2
  29. package/build/i18n-manager/index.js +1 -1
  30. package/build/image-loader.js +1 -1
  31. package/build/index.d.ts +4 -2
  32. package/build/index.js +3 -2
  33. package/build/keyboard/index.js +1 -1
  34. package/build/layout-animation/index.js +1 -2
  35. package/build/linking/shared.js +1 -1
  36. package/build/permissions-android/index.js +1 -1
  37. package/build/platform/shared.js +1 -1
  38. package/build/process-font-variant.js +0 -1
  39. package/build/process-transform/index.js +13 -19
  40. package/build/report-error.d.ts +5 -0
  41. package/build/report-error.js +60 -0
  42. package/build/settings/index.js +1 -1
  43. package/build/share/index.android.js +1 -1
  44. package/build/share/index.ios.js +1 -1
  45. package/build/status-bar/index.android.js +6 -7
  46. package/build/status-bar/index.ios.js +1 -1
  47. package/build/style-registry/index.js +95 -16
  48. package/build/style-sheet/index.js +5 -3
  49. package/build/surface.js +2 -1
  50. package/build/toast-android/index.js +1 -1
  51. package/build/vibration/index.android.js +2 -3
  52. package/build/vibration/shared.js +1 -1
  53. package/package.json +3 -2
  54. package/build/animated/interpolation-node.d.ts +0 -15
  55. package/build/animated/interpolation-node.js +0 -58
  56. package/build/image-loader/index.d.ts +0 -24
  57. package/build/image-loader/index.js +0 -175
  58. package/build/image-source-resolver/index.d.ts +0 -9
  59. package/build/image-source-resolver/index.js +0 -16
  60. package/build/native-modules.d.ts +0 -18
  61. package/build/native-modules.js +0 -120
  62. package/build/style-registry/scope/index.d.ts +0 -3
  63. package/build/style-registry/scope/index.js +0 -41
  64. package/build/touch-history/index.d.ts +0 -23
  65. package/build/touch-history/index.js +0 -150
  66. package/build/type-guards/index.d.ts +0 -4
  67. package/build/type-guards/index.js +0 -15
@@ -0,0 +1,55 @@
1
+ // Pure RGBA parsing — no graph node, no AnimatedValue, no imports from this package at all.
2
+ //
3
+ // It is split out of color.ts DELIBERATELY, and must stay dependency-free. `interpolation.ts`
4
+ // needs `normalizeColor` for its color output ranges, and `graph.ts` needs `interpolation.ts`
5
+ // (AnimatedInterpolation lives there). color.ts also defines `AnimatedColor extends
6
+ // AnimatedWithChildren`, so leaving the parser there closes a cycle
7
+ // graph -> interpolation -> color -> graph
8
+ // which surfaces at load time as `TypeError: Class extends value undefined is not a
9
+ // constructor` — AnimatedWithChildren is still uninitialized when color.ts evaluates. Keeping
10
+ // the pure half in its own leaf module breaks it structurally rather than by import ordering.
11
+ export const DEFAULT_COLOR = { r: 0, g: 0, b: 0, a: 1 };
12
+ // Decompose a #hex (3/4/6/8), rgb()/rgba(), or 0xRRGGBBAA number into channels.
13
+ // undefined when unparseable (a named/platform color), so the caller falls back to
14
+ // the default rather than throwing inside a render. Exported so interpolation's
15
+ // color path parses through the same RGBA decoder (DRY) rather than duplicating it.
16
+ export function normalizeColor(color) {
17
+ if (typeof color === 'number') {
18
+ const c = color >>> 0;
19
+ return { r: (c >>> 24) & 255, g: (c >>> 16) & 255, b: (c >>> 8) & 255, a: (c & 255) / 255 };
20
+ }
21
+ const trimmed = color.trim();
22
+ if (trimmed.startsWith('#'))
23
+ return parseHex(trimmed);
24
+ if (/^rgba?\(/i.test(trimmed))
25
+ return parseRgb(trimmed);
26
+ return undefined;
27
+ }
28
+ function parseHex(hex) {
29
+ let body = hex.slice(1);
30
+ if (body.length === 3 || body.length === 4) {
31
+ body = body
32
+ .split('')
33
+ .map(c => c + c)
34
+ .join('');
35
+ }
36
+ if (body.length !== 6 && body.length !== 8)
37
+ return undefined;
38
+ const int = Number.parseInt(body, 16);
39
+ if (Number.isNaN(int))
40
+ return undefined;
41
+ if (body.length === 6) {
42
+ return { r: (int >>> 16) & 255, g: (int >>> 8) & 255, b: int & 255, a: 1 };
43
+ }
44
+ const c = int >>> 0;
45
+ return { r: (c >>> 24) & 255, g: (c >>> 16) & 255, b: (c >>> 8) & 255, a: (c & 255) / 255 };
46
+ }
47
+ function parseRgb(str) {
48
+ const match = /^rgba?\(([^)]+)\)$/i.exec(str);
49
+ if (match === null)
50
+ return undefined;
51
+ const parts = match[1].split(',').map(part => Number.parseFloat(part.trim()));
52
+ if (parts.length < 3 || parts.some(n => Number.isNaN(n)))
53
+ return undefined;
54
+ return { r: parts[0], g: parts[1], b: parts[2], a: parts.length >= 4 ? parts[3] : 1 };
55
+ }
@@ -119,13 +119,12 @@ export class AnimatedValue extends AnimatedWithChildren {
119
119
  this.offset = offset;
120
120
  }
121
121
  }
122
- // Own the native-driver "start" handshake end-to-end: make this value native, mint
122
+ // Owns the native-driver "start" handshake end-to-end: make this value native, mint
123
123
  // its tag, hand the curve to the native module, and on completion sync the JS value
124
- // (no re-flush to bound leaves' native side; native already moved the view). A
125
- // driver (BaseAnimation.startNativeIfNeeded) supplies only what IT owns: the curve
126
- // config, its own animation id, its platform bag, and its own end callback, never
127
- // touching __makeNative / __getNativeTag / __onNativeUpdate / flushValue directly.
128
- // Information Expert: this value is the one object that actually holds those internals.
124
+ // (no re-flush; native already moved the view). The driver (BaseAnimation.
125
+ // startNativeIfNeeded) supplies only what it owns - curve config, animation id,
126
+ // platform bag, end callback - never touching __makeNative / __getNativeTag /
127
+ // __onNativeUpdate / flushValue directly; those internals live here.
129
128
  __startNativeAnimation(config, nativeId, onEnd, platformConfig) {
130
129
  this.__makeNative(platformConfig);
131
130
  nativeAnimated.startAnimatingNode(nativeId, this.__getNativeTag(), config, result => {
@@ -164,10 +163,16 @@ export class AnimatedValue extends AnimatedWithChildren {
164
163
  this.tracking = null;
165
164
  }
166
165
  }
167
- // Stop any animation and reset to the original value.
166
+ // Stop any animation and reset to the original value. The native graph keeps its OWN copy of
167
+ // the value, so a native-driven node has to be told as well - otherwise the reset lands in JS
168
+ // only and the view stays wherever the animation left it. setValue above pushes for the same
169
+ // reason; RN pushes here too (Libraries/Animated/nodes/AnimatedValue.js:285).
168
170
  resetAnimation(callback) {
169
171
  this.stopAnimation(callback);
170
172
  this.value = this.startingValue;
173
+ if (this.isNative) {
174
+ nativeAnimated.setAnimatedNodeValue(this.__getNativeTag(), this.startingValue);
175
+ }
171
176
  }
172
177
  // Drive this value with an animation. Typically called by Animated.timing /
173
178
  // spring / decay rather than directly.
@@ -11,7 +11,7 @@
11
11
  // registrar. Reached the same way shared reaches processColor: a dependency-injected seam
12
12
  // (setHostRegistrar), so the core stays react-native-free and the app glue wires the host once
13
13
  // at startup.
14
- import { getNativeModule } from '../native-modules.js';
14
+ import { getNativeModule } from '../native-modules/index.js';
15
15
  import { dlog } from '../debug.js';
16
16
  const HEADLESS_TASK_MODULE = 'HeadlessJsTaskSupport';
17
17
  // Builds one adapter's AppRegistry. `runnableFor` is the sole framework-specific seam: given a
@@ -5,7 +5,7 @@
5
5
  // NativeEventEmitter bound to the AppState native module and maps them onto the
6
6
  // public 'change'/'memoryWarning'/'focus'/'blur' listeners, mirroring RN's
7
7
  // Libraries/AppState/AppState.js.
8
- import { createDeviceEventModule } from '../native-modules.js';
8
+ import { createDeviceEventModule } from '../native-modules/index.js';
9
9
  import {} from '../native-events.js';
10
10
  import { dlog } from '../debug.js';
11
11
  // The native module name RN registers AppState under, confirmed from its spec
@@ -4,7 +4,7 @@
4
4
  // native module (which RN keys its appearance events off of) and re-broadcasts to
5
5
  // JS listeners as a plain `{ colorScheme }` payload. Mirrors RN's
6
6
  // Libraries/Utilities/Appearance.js, slimmed to the parts we need.
7
- import { createDeviceEventModule } from '../native-modules.js';
7
+ import { createDeviceEventModule } from '../native-modules/index.js';
8
8
  import {} from '../native-events.js';
9
9
  import { dlog } from '../debug.js';
10
10
  // The native module name RN registers the appearance module under, confirmed from
@@ -6,7 +6,7 @@
6
6
  // Android-only by intent: iOS has no hardware back button, so the whole module
7
7
  // degrades to a no-op when the native module is absent, matching RN's
8
8
  // BackHandler.ios.js stub. Mirrors RN's Libraries/Utilities/BackHandler.android.js.
9
- import { createDeviceEventModule } from '../native-modules.js';
9
+ import { createDeviceEventModule } from '../native-modules/index.js';
10
10
  import {} from '../native-events.js';
11
11
  import { dlog } from '../debug.js';
12
12
  // The native module name RN registers this under. NOTE: this is the name the
package/build/commit.js CHANGED
@@ -20,9 +20,7 @@ import { nextTag } from './tags.js';
20
20
  import { registerPostCommit, runPostCommitHooks } from './post-commit.js';
21
21
  import { fabricProps } from './fabric-props.js';
22
22
  import { isRecord } from './type-guards.js';
23
- // processColor/setColorProcessor now live in ./platform-color (the stable color-processing
24
- // leaf every color-touching module imports from); re-exported here so nothing outside this
25
- // module needs to change its import path.
23
+ // Re-exported from ./platform-color so callers don't need to change their import path.
26
24
  export { processColor, setColorProcessor } from './platform-color/index.js';
27
25
  // Per-commit work counters, surfaced via dlog so a device run can prove the
28
26
  // engine is incremental (created=0 with clones after the first mount).
@@ -173,8 +171,6 @@ function reconcile(slot, node, rootTag, hasTextAncestor, renderableParent, force
173
171
  const props = fabricProps(node);
174
172
  const childInText = node.isText || hasTextAncestor;
175
173
  const committed = mirror.get(node);
176
- // The children that actually reach Fabric. Anchors are filtered out here so the
177
- // whole walk (child-set emission, identity diff, mirror) is anchor-blind.
178
174
  const kids = renderableChildren(node);
179
175
  // First mount, or the view kind flipped (RCTText <-> RCTVirtualText when a
180
176
  // <Text> moves in or out of another <Text>): a different native component
package/build/debug.d.ts CHANGED
@@ -2,4 +2,4 @@ declare global {
2
2
  var __SYMBIOTE_DEBUG__: boolean | undefined;
3
3
  }
4
4
  export declare function isDebug(): boolean;
5
- export declare function dlog(message: string): void;
5
+ export declare function dlog(message: string | (() => string)): void;
package/build/debug.js CHANGED
@@ -5,7 +5,13 @@
5
5
  // Metro start (--reset-cache), not a rebuild.
6
6
  // - runtime: globalThis.__SYMBIOTE_DEBUG__ = true, an escape hatch for hosts
7
7
  // where the env isn't reachable.
8
- // Production with neither set pays one property read per call and nothing else.
8
+ // Production with neither set pays one property read per call and nothing else -
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.
9
15
  function envEnabled() {
10
16
  return typeof process !== 'undefined' && process.env.DEBUG === '1';
11
17
  }
@@ -13,6 +19,7 @@ export function isDebug() {
13
19
  return globalThis.__SYMBIOTE_DEBUG__ === true || envEnabled();
14
20
  }
15
21
  export function dlog(message) {
16
- if (isDebug())
17
- console.log(`[symbiote] ${message}`);
22
+ if (!isDebug())
23
+ return;
24
+ console.log(`[symbiote] ${typeof message === 'function' ? message() : message}`);
18
25
  }
@@ -11,7 +11,7 @@
11
11
  // We resolve it through the same generic native-module bridge as Platform
12
12
  // (getNativeModule), so this module stays importable headless before a fake proxy
13
13
  // is installed.
14
- import { createDeviceEventModule } from '../native-modules.js';
14
+ import { createDeviceEventModule } from '../native-modules/index.js';
15
15
  import {} from '../native-events.js';
16
16
  import { dlog } from '../debug.js';
17
17
  import { isRecord } from '../type-guards.js';
@@ -515,7 +515,6 @@ function bubble(target, listenerName, nativeEvent) {
515
515
  while (node) {
516
516
  const listener = isAnchor(node) ? undefined : node.listeners?.get(listenerName);
517
517
  if (listener) {
518
- // engine owner adds currentTarget + stopPropagation to SymbioteEvent
519
518
  const event = {
520
519
  type: listenerName,
521
520
  target,
@@ -535,7 +534,6 @@ function deliverDirect(target, listenerName, nativeEvent) {
535
534
  const listener = target.listeners?.get(listenerName);
536
535
  if (!listener)
537
536
  return;
538
- // engine owner adds currentTarget + stopPropagation to SymbioteEvent
539
537
  listener({
540
538
  type: listenerName,
541
539
  target,
@@ -13,7 +13,7 @@
13
13
  // forceRTL(forceRTL: boolean)
14
14
  // swapLeftAndRightInRTL(flipStyles: boolean)
15
15
  import { dlog } from '../debug.js';
16
- import { getNativeModule } from '../native-modules.js';
16
+ import { getNativeModule } from '../native-modules/index.js';
17
17
  import { isBoolean } from '../type-guards.js';
18
18
  // The iOS native module name RN registers this under (the same name on both
19
19
  // platforms). A module name is only provable on a real host (a headless fake
@@ -15,7 +15,7 @@
15
15
  // native bridge).
16
16
  import { dlog } from './debug.js';
17
17
  import { resolveImageSource } from './image-source-resolver.js';
18
- import { getNativeModule } from './native-modules.js';
18
+ import { getNativeModule } from './native-modules/index.js';
19
19
  import { Platform } from './platform';
20
20
  import { isNumber } from './type-guards.js';
21
21
  // The iOS native module name RN registers this under (NativeImageLoaderIOS.js resolves
package/build/index.d.ts CHANGED
@@ -37,11 +37,13 @@ export type { IPlatformStatic, IPlatformOSType, IPlatformSelectSpec } from './pl
37
37
  export type { IPlatformConstantsIOS } from './platform/index.ios';
38
38
  export type { IPlatformConstantsAndroid } from './platform/index.android';
39
39
  export { dlog, isDebug } from './debug';
40
+ export { reportUncaughtError } from './report-error';
41
+ export type { IUncaughtErrorInfo } from './report-error';
40
42
  export { getNativeModule, getEnforcingNativeModule } from './native-modules';
41
43
  export { installDeviceEventHub, NativeEventEmitter, setDeviceEventSource } from './native-events';
42
44
  export type { IEventSubscription, IEventEmitterModule, INativeEventListener, IDeviceEventSource, } from './native-events';
43
- export { AnimatedNode, AnimatedWithChildren, AnimatedValue, AnimatedValueXY, AnimatedColor, AnimatedInterpolation, AnimatedAddition, AnimatedSubtraction, AnimatedMultiplication, AnimatedDivision, AnimatedModulo, AnimatedDiffClamp, AnimatedEvent, add, subtract, multiply, divide, modulo, diffClamp, event, forkEvent, unforkEvent, attachNativeEvent, attachNativeEventHandler, flushValue, Easing, createNumericInterpolation, checkValidRanges, TimingAnimation, SpringAnimation, DecayAnimation, AnimatedTracking, timing, spring, decay, parallel, sequence, stagger, loop, delay, nativeAnimated, isNativeAnimatedAvailable, AnimatedProps, AnimatedStyle, AnimatedTransform, AnimatedMock, reduceProps, isAnimatedNode, readPassthroughStyle, resolveHostNode, } from './animated';
44
- export type { IValueXY, IRgbaValue, IColorInput, IEventConfig, IEventListener, IAnimatedEventHandler, INativeEventAttachment, IValueListener, IEasingFunction, IInterpolationConfig, IExtrapolateType, IAnimation, IEndCallback, IEndResult, ICompositeAnimation, ITimingConfig, ISpringConfig, IDecayConfig, IParallelConfig, ILoopAnimationConfig, INativeNodeConfig, INativeAnimationConfig, INativeEventMapping, IPlatformConfig, } from './animated';
45
+ export { AnimatedNode, AnimatedWithChildren, AnimatedValue, AnimatedValueXY, AnimatedColor, AnimatedInterpolation, AnimatedAddition, AnimatedSubtraction, AnimatedMultiplication, AnimatedDivision, AnimatedModulo, AnimatedDiffClamp, AnimatedEvent, add, subtract, multiply, divide, modulo, diffClamp, event, forkEvent, unforkEvent, attachNativeEvent, attachNativeEventHandler, flushValue, Easing, createNumericInterpolation, checkValidRanges, TimingAnimation, SpringAnimation, DecayAnimation, AnimatedTracking, timing, spring, decay, parallel, sequence, stagger, loop, delay, nativeAnimated, isNativeAnimatedAvailable, AnimatedProps, createAnimatedLeafLifecycle, AnimatedStyle, AnimatedTransform, AnimatedMock, reduceProps, isAnimatedNode, readPassthroughStyle, resolveHostNode, } from './animated';
46
+ export type { IAnimatedLeafLifecycle, IValueXY, IRgbaValue, IColorInput, IEventConfig, IEventListener, IAnimatedEventHandler, INativeEventAttachment, IValueListener, IEasingFunction, IInterpolationConfig, IExtrapolateType, IAnimation, IEndCallback, IEndResult, ICompositeAnimation, ITimingConfig, ISpringConfig, IDecayConfig, IParallelConfig, ILoopAnimationConfig, INativeNodeConfig, INativeAnimationConfig, INativeEventMapping, IPlatformConfig, } from './animated';
45
47
  export { getSlot } from './fabric';
46
48
  export type { IFabricSlot, IFabricNode, IFabricChildSet, IFabricProps, IFabricEventHandler, IRootTag, IMeasureOnSuccess, IMeasureInWindowOnSuccess, IMeasureLayoutOnSuccess, } from './fabric';
47
49
  export { Alert } from './alert';
package/build/index.js CHANGED
@@ -37,9 +37,10 @@ export { registerStyles, resolveClassName, clearGlobalStyles, isClassNameValue,
37
37
  export { scopeClassName } from './style-registry/scope.js';
38
38
  export { Platform } from './platform';
39
39
  export { dlog, isDebug } from './debug.js';
40
- export { getNativeModule, getEnforcingNativeModule } from './native-modules.js';
40
+ export { reportUncaughtError } from './report-error.js';
41
+ export { getNativeModule, getEnforcingNativeModule } from './native-modules/index.js';
41
42
  export { installDeviceEventHub, NativeEventEmitter, setDeviceEventSource } from './native-events.js';
42
- export { AnimatedNode, AnimatedWithChildren, AnimatedValue, AnimatedValueXY, AnimatedColor, AnimatedInterpolation, AnimatedAddition, AnimatedSubtraction, AnimatedMultiplication, AnimatedDivision, AnimatedModulo, AnimatedDiffClamp, AnimatedEvent, add, subtract, multiply, divide, modulo, diffClamp, event, forkEvent, unforkEvent, attachNativeEvent, attachNativeEventHandler, flushValue, Easing, createNumericInterpolation, checkValidRanges, TimingAnimation, SpringAnimation, DecayAnimation, AnimatedTracking, timing, spring, decay, parallel, sequence, stagger, loop, delay, nativeAnimated, isNativeAnimatedAvailable, AnimatedProps, AnimatedStyle, AnimatedTransform, AnimatedMock, reduceProps, isAnimatedNode, readPassthroughStyle, resolveHostNode, } from './animated/index.js';
43
+ export { AnimatedNode, AnimatedWithChildren, AnimatedValue, AnimatedValueXY, AnimatedColor, AnimatedInterpolation, AnimatedAddition, AnimatedSubtraction, AnimatedMultiplication, AnimatedDivision, AnimatedModulo, AnimatedDiffClamp, AnimatedEvent, add, subtract, multiply, divide, modulo, diffClamp, event, forkEvent, unforkEvent, attachNativeEvent, attachNativeEventHandler, flushValue, Easing, createNumericInterpolation, checkValidRanges, TimingAnimation, SpringAnimation, DecayAnimation, AnimatedTracking, timing, spring, decay, parallel, sequence, stagger, loop, delay, nativeAnimated, isNativeAnimatedAvailable, AnimatedProps, createAnimatedLeafLifecycle, AnimatedStyle, AnimatedTransform, AnimatedMock, reduceProps, isAnimatedNode, readPassthroughStyle, resolveHostNode, } from './animated/index.js';
43
44
  export { getSlot } from './fabric.js';
44
45
  // Imperative runtime modules: framework-agnostic native-bridge consumers (no visual, no
45
46
  // lifecycle), moved here from @symbiote-native/react so every adapter re-exports the SAME module.
@@ -3,7 +3,7 @@
3
3
  // subscribes through a NativeEventEmitter bound to the KeyboardObserver native
4
4
  // module, which RN keys its keyboard events off of. Mirrors RN's
5
5
  // Libraries/Components/Keyboard/Keyboard.js, slimmed to the parts we need.
6
- import { createDeviceEventModule } from '../native-modules.js';
6
+ import { createDeviceEventModule } from '../native-modules/index.js';
7
7
  import {} from '../native-events.js';
8
8
  import { dlog } from '../debug.js';
9
9
  import { blurTextInput, currentlyFocusedInput } from '../text-input-state.js';
@@ -7,7 +7,7 @@
7
7
  //
8
8
  // It is a native-bridge consumer (RN's `Platform`/`StyleSheet` purity split puts
9
9
  // these in the adapter, not in shared, like Keyboard / StatusBar).
10
- import { getNativeModule } from '../native-modules.js';
10
+ import { getNativeModule } from '../native-modules/index.js';
11
11
  import { dlog } from '../debug.js';
12
12
  import { isRecord } from '../type-guards.js';
13
13
  // ---- native module routing ----------------------------------------------
@@ -148,7 +148,6 @@ function configureNext(config, onAnimationDidEnd, onAnimationDidFail) {
148
148
  onAnimationDidEnd?.();
149
149
  };
150
150
  dlog(`LayoutAnimation.configureNext: dispatching config (duration=${config.duration})`);
151
- // Default to a no-op when the caller doesn't supply one.
152
151
  manager.configureNextLayoutAnimation(config, onComplete, onAnimationDidFail ?? (() => { }));
153
152
  }
154
153
  // ---- the LayoutAnimation facade ------------------------------------------
@@ -8,7 +8,7 @@
8
8
  // the base linking.ts re-exports the iOS build for web/headless. There is no runtime
9
9
  // `Platform.OS` read; the filename is the selector.
10
10
  import { installDeviceEventHub, NativeEventEmitter, } from '../native-events.js';
11
- import { getNativeModule } from '../native-modules.js';
11
+ import { getNativeModule } from '../native-modules/index.js';
12
12
  import { dlog } from '../debug.js';
13
13
  // The one event symbiote observes: an incoming deep link. RN's LinkingEventDefinitions.
14
14
  const URL_EVENT = 'url';
@@ -17,7 +17,7 @@
17
17
  // requestPermission(permission): Promise<string>
18
18
  // shouldShowRequestPermissionRationale(permission): Promise<boolean>
19
19
  // requestMultiplePermissions(permissions): Promise<{[permission]: string}>
20
- import { getNativeModule } from '../native-modules.js';
20
+ import { getNativeModule } from '../native-modules/index.js';
21
21
  import { dlog } from '../debug.js';
22
22
  import { isBoolean } from '../type-guards.js';
23
23
  // The native module name RN registers this TurboModule under
@@ -5,7 +5,7 @@
5
5
  // select precedence, and the device-class getters. The filename is the selector (Metro
6
6
  // picks platform.ios / platform.android by host); no Platform.OS read lives here.
7
7
  import { dlog } from '../debug.js';
8
- import { getNativeModule } from '../native-modules.js';
8
+ import { getNativeModule } from '../native-modules/index.js';
9
9
  // The native module name RN registers PlatformConstants under, identical on both
10
10
  // platforms; only getConstants()'s SHAPE differs, which each platform file guards.
11
11
  export const PLATFORM_CONSTANTS = 'PlatformConstants';
@@ -12,6 +12,5 @@ export function processFontVariant(fontVariant) {
12
12
  if (typeof fontVariant === 'string') {
13
13
  return fontVariant.split(' ').filter(Boolean);
14
14
  }
15
- // Neither an array nor a string: nothing to split, hand back an empty variant list.
16
15
  return [];
17
16
  }
@@ -1,19 +1,15 @@
1
1
  // JS-side port of RN's processTransform (Libraries/StyleSheet/processTransform.js).
2
- // Same root cause as transformOrigin/boxShadow: ReactNativeStyleAttributes registers
3
- // `transform` with `nativeCSSParsing ? true : {process: processTransform}`, and
4
- // enableNativeCSSParsing() DEFAULTS TO FALSE, so RN's stock path runs processTransform
5
- // IN JS. It only does work for a STRING input (parses the CSS string into the entry
6
- // array); an ARRAY input is returned UNCHANGED. symbiote forwarded a raw string, which
7
- // Android native cast to ReadableArray and crashed with
8
- // `java.lang.String cannot be cast to com.facebook.react.bridge.ReadableArray`. This
9
- // restores the missing JS parse so native always receives the entry array.
2
+ // Same root cause as transformOrigin/boxShadow: RN only parses `transform` in JS when
3
+ // nativeCSSParsing is off (the default), and only for a STRING input - an ARRAY is
4
+ // returned unchanged. symbiote forwarded a raw string once, which Android native cast
5
+ // to ReadableArray and crashed (`String cannot be cast to ReadableArray`). This
6
+ // restores the missing JS parse.
10
7
  //
11
- // CRITICAL: the animated / sticky-header hot path produces transform ARRAYS and flows
12
- // through commit's processValue on every flush, so the array branch MUST return the
13
- // byte-identical reference (no decompose, no clone). RN's only array-path work is
14
- // _validateTransforms, which is __DEV__-only and THROWS via invariant; we never throw
15
- // into the commit path, so we run a non-throwing check that dlogs and still returns the
16
- // array unchanged (matching the boxShadow/transformOrigin idiom).
8
+ // CRITICAL: the animated / sticky-header hot path produces transform ARRAYS on every
9
+ // commit flush, so the array branch MUST return the byte-identical reference (no
10
+ // decompose, no clone). RN's only array-path check, _validateTransforms, is __DEV__-only
11
+ // and throws via invariant; we never throw in commit, so we dlog instead and still
12
+ // return the array unchanged.
17
13
  import { dlog } from '../debug.js';
18
14
  // RN processTransform.js:28: matches each `name(args)` in the CSS string.
19
15
  const TRANSFORM_REGEX = /(\w+)\(([^)]+)\)/g;
@@ -78,16 +74,14 @@ function getKeyAndValueFromCSSTransform(key, args) {
78
74
  return { key, value: isNaN(Number(args)) ? args : Number(args) };
79
75
  }
80
76
  }
81
- // RN processTransform.js:24-50. STRING input is parsed into the entry array; ARRAY input
82
- // is returned UNCHANGED (RN's only array work, _validateTransforms, is __DEV__-only and
83
- // throws; we never throw, so we skip it and run a non-throwing dlog sanity check).
77
+ // RN processTransform.js:24-50. String input is parsed into the entry array; array
78
+ // input is returned unchanged (see file header for why).
84
79
  export function processTransform(transform) {
85
80
  if (transform == null) {
86
81
  return [];
87
82
  }
88
83
  if (typeof transform !== 'string') {
89
- // Hot path: animated / sticky-header transforms arrive here as arrays. Return the
90
- // same reference (no decompose, no clone) so the flush is a no-op.
84
+ // Hot path: return the same reference, no clone (see file header).
91
85
  warnInvalidTransforms(transform);
92
86
  return transform;
93
87
  }
@@ -0,0 +1,5 @@
1
+ export type IUncaughtErrorInfo = {
2
+ readonly origin: string;
3
+ readonly componentStack?: string | null;
4
+ };
5
+ export declare function reportUncaughtError(value: unknown, info: IUncaughtErrorInfo): void;
@@ -0,0 +1,60 @@
1
+ // Hand an error to the host the way React Native itself does, for the seams where an adapter
2
+ // catches something its framework would otherwise have surfaced on its own.
3
+ //
4
+ // RN routes an uncaught render error through ReactFiberErrorDialog -> ExceptionsManager
5
+ // (Libraries/Core/ReactFiberErrorDialog.js), which is reachable only by a deep path into RN's
6
+ // Libraries. `global.ErrorUtils` is the same surface through the documented global that
7
+ // @react-native/js-polyfills installs before any module evaluates - RN's own
8
+ // Libraries/vendor/core/ErrorUtils.js is nothing but `export default global.ErrorUtils`. Off a
9
+ // native host (vitest, node smokes) the global is absent and console.error is the whole channel.
10
+ //
11
+ // This is NOT the dlog channel. dlog is DEBUG-gated and therefore the developer's; an error that
12
+ // blanked the screen has to reach the app whether or not anyone turned diagnostics on.
13
+ // Read off globalThis through Reflect rather than named as an identifier, and deliberately NOT
14
+ // declared in an ambient block: react-native ships its own `const ErrorUtils: ErrorUtils` in
15
+ // src/types/globals.d.ts, so a `declare global { var ErrorUtils }` here redeclares it (TS2451) for
16
+ // every package whose tsconfig pulls in RN's types - the engine's own does not, so it typechecks
17
+ // clean here and breaks ngc downstream. RN's declared shape also lists only the two
18
+ // get/setGlobalHandler members, not reportError, which the polyfill does install.
19
+ function nativeReporter() {
20
+ const utils = Reflect.get(globalThis, 'ErrorUtils');
21
+ if (typeof utils !== 'object' || utils === null)
22
+ return null;
23
+ const report = Reflect.get(utils, 'reportError');
24
+ if (typeof report !== 'function')
25
+ return null;
26
+ return error => {
27
+ Reflect.apply(report, utils, [error]);
28
+ };
29
+ }
30
+ // A throw is not required to be an Error - a string, or null, reaches here just as easily, and
31
+ // the host's reporter wants something with a message and a stack. RN wraps the same way, via
32
+ // SyntheticError.
33
+ function toError(value, origin) {
34
+ if (value instanceof Error)
35
+ return value;
36
+ return new Error(`${origin}: ${String(value)}`);
37
+ }
38
+ export function reportUncaughtError(value, info) {
39
+ const error = toError(value, info.origin);
40
+ if (info.componentStack) {
41
+ // LogBox reads these two off the error to render the component frames; RN's own
42
+ // ReactFiberErrorDialog sets exactly the same pair, in a try/catch because a frozen or
43
+ // sealed error still deserves to be reported, just without the frames.
44
+ try {
45
+ Object.assign(error, { componentStack: info.componentStack, isComponentError: true });
46
+ }
47
+ catch {
48
+ // Reported below regardless.
49
+ }
50
+ }
51
+ const report = nativeReporter();
52
+ if (report !== null) {
53
+ // Exactly one channel, never both: RN routes console.error into LogBox as well, so logging
54
+ // here too would double-report every error. Upstream returns false from showErrorDialog for
55
+ // this same reason (Libraries/Core/ReactFiberErrorDialog.js:57).
56
+ report(error);
57
+ return;
58
+ }
59
+ console.error(`[symbiote] ${info.origin}:`, error);
60
+ }
@@ -6,7 +6,7 @@
6
6
  // device event `settingsUpdated`, which feeds the same change->fire path. Mirrors
7
7
  // RN's Libraries/Settings/Settings.ios.js, iOS surface only.
8
8
  import { installDeviceEventHub, NativeEventEmitter, } from '../native-events.js';
9
- import { getNativeModule } from '../native-modules.js';
9
+ import { getNativeModule } from '../native-modules/index.js';
10
10
  import { dlog } from '../debug.js';
11
11
  import { isRecord } from '../type-guards.js';
12
12
  // The iOS native module name RN registers this under. NOTE: this is the name the
@@ -8,7 +8,7 @@
8
8
  // TurboModuleRegistry.get('ShareModule') from RN source, but headless fakes resolve any
9
9
  // name, so it is only proven on a real Android host (a bridgeless resolution log there).
10
10
  import { dlog } from '../debug.js';
11
- import { getNativeModule } from '../native-modules.js';
11
+ import { getNativeModule } from '../native-modules/index.js';
12
12
  import { validateContent, shareActions, SHARED_ACTION, DISMISSED_ACTION } from './shared.js';
13
13
  const SHARE_MODULE = 'ShareModule';
14
14
  // ShareModule.share resolves an untyped value at the native boundary; narrow it before
@@ -5,7 +5,7 @@
5
5
  // callback. Metro picks this file on an iOS host; the base share.ts re-exports it for
6
6
  // web/headless.
7
7
  import { dlog } from '../debug.js';
8
- import { getNativeModule } from '../native-modules.js';
8
+ import { getNativeModule } from '../native-modules/index.js';
9
9
  import { ACTION_SHEET_MANAGER, } from '../action-sheet-ios/index.js';
10
10
  import { validateContent, shareActions, SHARED_ACTION, DISMISSED_ACTION } from './shared.js';
11
11
  export const Share = {
@@ -4,13 +4,12 @@
4
4
  // setColor/setTranslucent, same module name ('StatusBarManager'). Metro picks this on an
5
5
  // Android host; iOS keeps its own shape. Each adapter wraps these with its declarative component.
6
6
  //
7
- // History: this used to be a no-op. Driving the window flags from our bridgeless surface
8
- // blanked the app: a status-bar relayout triggered stopSurface, which threw "Global was
9
- // not installed" because RN installs global.RN$stopSurface from its own renderer, which we
10
- // replace. Now that render.ts installs RN$stopSurface and tears surfaces down cleanly, the
11
- // relayout survives and the bar updates without blanking (verified on device: show/hide +
12
- // light/dark text). See render.ts's installStopSurfaceGlobal.
13
- import { getNativeModule } from '../native-modules.js';
7
+ // Driving window flags from our bridgeless surface used to blank the app: a status-bar
8
+ // relayout triggered stopSurface, which threw "Global was not installed" because RN's
9
+ // own renderer installs global.RN$stopSurface and we replace that renderer. Fixed by
10
+ // render.ts installing RN$stopSurface itself (see installStopSurfaceGlobal) - verified
11
+ // on device: show/hide + light/dark text survive the relayout without blanking.
12
+ import { getNativeModule } from '../native-modules/index.js';
14
13
  import { dlog } from '../debug.js';
15
14
  import { processColor } from '../platform-color/index.js';
16
15
  import { STATUS_BAR_MANAGER } from './shared.js';
@@ -8,7 +8,7 @@
8
8
  // boundary getNativeModule<T> resolves against. Metro picks this on an iOS host; the base
9
9
  // status-bar.ts re-exports it for tsc / tsx / headless. Each adapter wraps these with its own
10
10
  // declarative component.
11
- import { getNativeModule } from '../native-modules.js';
11
+ import { getNativeModule } from '../native-modules/index.js';
12
12
  import { dlog } from '../debug.js';
13
13
  import { STATUS_BAR_MANAGER, STATIC_HIDE_TRANSITION, hideTransition, } from './shared.js';
14
14
  // Apply a StatusBar component's props to the native module. The adapter calls this from its