@symbiote-native/engine 0.1.7 → 0.3.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 (87) hide show
  1. package/README.md +17 -10
  2. package/build/action-sheet-ios/index.js +4 -1
  3. package/build/alert/index.ios.js +10 -0
  4. package/build/animated/animations/composition.js +3 -1
  5. package/build/animated/animations/spring-config.js +4 -1
  6. package/build/animated/animations/spring.js +23 -9
  7. package/build/animated/animations/timing.js +3 -2
  8. package/build/animated/bezier.js +2 -1
  9. package/build/animated/color.d.ts +2 -8
  10. package/build/animated/color.js +8 -47
  11. package/build/animated/easing.js +0 -8
  12. package/build/animated/event.js +7 -3
  13. package/build/animated/graph.d.ts +13 -5
  14. package/build/animated/graph.js +92 -13
  15. package/build/animated/index.d.ts +3 -3
  16. package/build/animated/index.js +3 -3
  17. package/build/animated/interpolation.js +8 -3
  18. package/build/animated/leaf-lifecycle.d.ts +7 -0
  19. package/build/animated/leaf-lifecycle.js +185 -0
  20. package/build/animated/mock.js +1 -1
  21. package/build/animated/native/native-animated.js +2 -1
  22. package/build/animated/operators.js +27 -6
  23. package/build/animated/props.js +1 -1
  24. package/build/animated/rgba.d.ts +8 -0
  25. package/build/animated/rgba.js +70 -0
  26. package/build/animated/style.js +13 -3
  27. package/build/animated/value-xy.d.ts +8 -5
  28. package/build/animated/value-xy.js +4 -1
  29. package/build/animated/value.js +12 -7
  30. package/build/appearance/index.js +4 -1
  31. package/build/commit.d.ts +13 -0
  32. package/build/commit.js +146 -14
  33. package/build/debug.d.ts +1 -1
  34. package/build/debug.js +10 -3
  35. package/build/events/index.js +19 -10
  36. package/build/fabric-props.js +8 -2
  37. package/build/image-loader.js +3 -2
  38. package/build/index.d.ts +23 -19
  39. package/build/index.js +19 -11
  40. package/build/interaction-manager/index.js +2 -1
  41. package/build/layout-animation/index.js +12 -5
  42. package/build/native-events.js +2 -1
  43. package/build/native-modules/index.js +3 -1
  44. package/build/node.d.ts +25 -0
  45. package/build/node.js +168 -2
  46. package/build/pan-responder/index.js +20 -9
  47. package/build/permissions-android/index.js +3 -1
  48. package/build/platform/index.android.d.ts +1 -1
  49. package/build/platform/index.android.js +1 -1
  50. package/build/platform/index.ios.d.ts +1 -1
  51. package/build/platform-color/index.js +3 -1
  52. package/build/post-commit.d.ts +1 -0
  53. package/build/post-commit.js +6 -0
  54. package/build/process-background-image/index.js +30 -9
  55. package/build/process-filter.js +11 -4
  56. package/build/process-font-variant.js +0 -1
  57. package/build/process-transform/index.js +13 -19
  58. package/build/registry.js +10 -2
  59. package/build/report-error.d.ts +5 -0
  60. package/build/report-error.js +63 -0
  61. package/build/share/index.android.js +1 -1
  62. package/build/share/index.ios.js +1 -1
  63. package/build/status-bar/index.android.js +8 -10
  64. package/build/status-bar/index.ios.js +1 -1
  65. package/build/style-registry/index.d.ts +9 -2
  66. package/build/style-registry/index.js +149 -95
  67. package/build/style-registry/scope.d.ts +15 -1
  68. package/build/style-registry/scope.js +29 -27
  69. package/build/style-sheet/index.js +4 -2
  70. package/build/styles.d.ts +11 -1
  71. package/build/surface.d.ts +1 -1
  72. package/build/surface.js +7 -1
  73. package/build/tags.d.ts +1 -0
  74. package/build/tags.js +31 -1
  75. package/build/touch-history.js +9 -2
  76. package/build/vibration/index.android.js +2 -3
  77. package/build/vibration/index.ios.js +1 -1
  78. package/build/view-config.js +14 -2
  79. package/package.json +2 -2
  80. package/build/animated/interpolation-node.d.ts +0 -15
  81. package/build/animated/interpolation-node.js +0 -58
  82. package/build/style-registry/scope/index.d.ts +0 -3
  83. package/build/style-registry/scope/index.js +0 -41
  84. package/build/touch-history/index.d.ts +0 -23
  85. package/build/touch-history/index.js +0 -150
  86. package/build/type-guards/index.d.ts +0 -4
  87. package/build/type-guards/index.js +0 -15
@@ -0,0 +1,7 @@
1
+ import type { ISymbioteNode } from '../node';
2
+ export type IScheduleNativeBind = (bind: () => void) => (() => void) | void;
3
+ export type IAnimatedLeafLifecycle = {
4
+ reconcile(props: Record<string, unknown>, node: ISymbioteNode | null, wantsNative: boolean, scheduleNativeBind?: IScheduleNativeBind): void;
5
+ teardown(): void;
6
+ };
7
+ export declare function createAnimatedLeafLifecycle(label: string): IAnimatedLeafLifecycle;
@@ -0,0 +1,185 @@
1
+ // The AnimatedProps leaf lifecycle every Animated.* wrapper needs: build a leaf from the current
2
+ // props, swap it into the value graph, bind it to the committed node, go native when asked, and
3
+ // rebind native event props. Framework-agnostic on purpose - it knows nothing about hooks,
4
+ // effects, change detection or reactivity, only the engine's own Animated primitives.
5
+ //
6
+ // WHY THIS LIVES IN THE ENGINE. It used to live four times, once per adapter (React's
7
+ // create-animated-component.tsx, Vue's create-animated-component.ts, Svelte's
8
+ // animated-props-runtime.ts, Angular's animated-leaf-binder.ts). Each re-derived the same policy,
9
+ // and they drifted: only Svelte ever grew the rebuild guard below, after a day of device
10
+ // debugging in 2026-08. React's `useMemo(..., [rest])` LOOKS like the same guard but is not - its
11
+ // dependency is a fresh rest-destructured object every render - and Vue's `pendingLeaf === null`
12
+ // answers "has render run yet", not "did anything change". A fix landing in one copy is exactly
13
+ // what <adapters_reach_full_feature_parity> forbids: parity has to be structural.
14
+ //
15
+ // What stays in the adapter: WHEN to call reconcile (an effect, onUpdated, ngOnChanges, a $effect)
16
+ // and HOW to find the host node (a ref, a ViewChild, a shim). Everything below is the same for all.
17
+ import { AnimatedProps } from './props.js';
18
+ import { attachNativeEventHandler } from './event.js';
19
+ import { dlog } from '../debug.js';
20
+ // Diagnostic-only, DEBUG-gated: a process-wide sequence number so every reconcile call across
21
+ // every Animated.* instance in a log dump is individually identifiable and orderable.
22
+ let globalReconcileSeq = 0;
23
+ // Per-key identity comparison, NOT deep equality. A style/handler/props object that is memoized
24
+ // upstream stays `===` across ticks that did not touch it, which is what distinguishes "same
25
+ // content" from a real change. Deep-comparing instead would walk live AnimatedNode graphs, which
26
+ // are circular.
27
+ //
28
+ // The caller MUST pass a stored SNAPSHOT as `a` - see `lastProps` below. Handing this the same
29
+ // live object on both sides compares it with itself and is unconditionally true.
30
+ function shallowEqualProps(a, b) {
31
+ const aKeys = Object.keys(a);
32
+ const bKeys = Object.keys(b);
33
+ if (aKeys.length !== bKeys.length)
34
+ return false;
35
+ for (const key of aKeys) {
36
+ if (a[key] !== b[key])
37
+ return false;
38
+ }
39
+ return true;
40
+ }
41
+ // Diagnostic-only. `style` here is a raw prop value, so it can be anything the caller wrote -
42
+ // an object, an array of them, a class name, undefined. Read the field, do not assume a shape.
43
+ function readTransform(style) {
44
+ if (typeof style !== 'object' || style === null)
45
+ return undefined;
46
+ return Reflect.get(style, 'transform');
47
+ }
48
+ // Diagnostic-only: a SHALLOW, non-serializing description of a style.transform array. Props here
49
+ // carry RAW values - an animated entry is a live AnimatedNode in a circular parent<->children
50
+ // graph - so JSON.stringify throws "Converting circular structure to JSON". Describe by
51
+ // constructor name instead of walking it.
52
+ function describeTransform(value) {
53
+ if (!Array.isArray(value))
54
+ return String(value);
55
+ const entries = value.map(entry => {
56
+ if (typeof entry !== 'object' || entry === null)
57
+ return String(entry);
58
+ const [key] = Object.keys(entry);
59
+ const inner = key === undefined ? undefined : Reflect.get(entry, key);
60
+ const innerDesc = typeof inner === 'number' || typeof inner === 'string'
61
+ ? String(inner)
62
+ : `<${inner !== null && typeof inner === 'object' ? inner.constructor.name : typeof inner}>`;
63
+ return `${key}:${innerDesc}`;
64
+ });
65
+ return `[${entries.join(',')}]`;
66
+ }
67
+ export function createAnimatedLeafLifecycle(label) {
68
+ let attached = null;
69
+ let eventDetachers = [];
70
+ // Diagnostic-only: the last node identity, plus a reentrancy flag to catch reconcile() being
71
+ // called AGAIN from inside its own call stack - the smoking-gun shape for a synchronous
72
+ // same-flush loop, as opposed to merely "called often".
73
+ let lastNode = null;
74
+ let inReconcile = false;
75
+ // Last-seen props/wantsNative for the skip below - a real content check, not diagnostics.
76
+ let lastProps = null;
77
+ let lastWantsNative = false;
78
+ // Canceller for a native bind the caller deferred and that has not run yet.
79
+ let cancelPendingBind;
80
+ function detachEvents() {
81
+ for (const detach of eventDetachers)
82
+ detach();
83
+ eventDetachers = [];
84
+ }
85
+ return {
86
+ reconcile(props, node, wantsNative, scheduleNativeBind) {
87
+ const seq = ++globalReconcileSeq;
88
+ const reentrant = inReconcile;
89
+ const nodeChanged = node !== lastNode;
90
+ const wantsNativeChanged = wantsNative !== lastWantsNative;
91
+ const propsChanged = lastProps === null || !shallowEqualProps(lastProps, props);
92
+ lastNode = node;
93
+ lastWantsNative = wantsNative;
94
+ // A SNAPSHOT, never the reference. A caller can legitimately hand back the SAME object on
95
+ // every update, mutating what its keys resolve to rather than allocating (Svelte's rest
96
+ // proxy does exactly that, device-confirmed 2026-08-13); storing it would make the next
97
+ // call run shallowEqualProps on that object against ITSELF - reading the same current values
98
+ // through both sides - which is unconditionally true. `propsChanged` could then never be
99
+ // true again and reconcile would be skipped forever, so every rebuilt AnimatedInterpolation
100
+ // never reaches the native graph: the view stays wired to the FIRST interpolation, built
101
+ // before measurement with range [-1,0]->[0,0]. One pixel of travel, which on device reads as
102
+ // "the sticky header ignores scrolling entirely".
103
+ lastProps = { ...props };
104
+ // Thunk, not a string: reconcile runs on EVERY update of every Animated.* wrapper in the
105
+ // app (Angular re-reads it on each change-detection pass), and describeTransform allocates.
106
+ // Built eagerly it would cost its full price with logging off.
107
+ dlog(() => `AnimatedProps[${label}] reconcile#${seq} reentrant=${reentrant} wantsNative=${wantsNative} ` +
108
+ `hasNode=${node !== null} nodeChanged=${nodeChanged} propsChanged=${propsChanged} ` +
109
+ `transform=${describeTransform(readTransform(props.style))}`);
110
+ if (reentrant) {
111
+ dlog(`AnimatedProps[${label}] reconcile#${seq} *** RE-ENTRANT CALL DETECTED - see reconcile above ***`);
112
+ }
113
+ // Nothing meaningfully changed since the last call AND we are already natively connected
114
+ // (steady state) - skip tearing down and rebuilding the native AnimatedProps graph.
115
+ //
116
+ // Deliberately scoped to `attached.__isNative()` ONLY: before the first native connection,
117
+ // reconcile must run unconditionally on every tick - that cadence is what wires a rebuilt
118
+ // interpolation node into the shared value's children (AnimatedInterpolation.__attach ->
119
+ // parent.__addChild, cascaded from AnimatedProps.__attach); skipping there can leave a
120
+ // freshly rebuilt interpolation never attached, so its listener never fires. Once native and
121
+ // staying native, that bootstrap concern is moot - and rebuilding there was the
122
+ // collision-disappearance bug: every scroll-driven passthrough tick reconnected a brand new
123
+ // native node (restoreDefaultValues + connect) even though nothing animated-relevant had
124
+ // changed, and a reconnect landing exactly when scrolling stopped left the view frozen at
125
+ // its post-reset default until the next tick.
126
+ if (attached !== null &&
127
+ attached.__isNative() &&
128
+ !nodeChanged &&
129
+ !wantsNativeChanged &&
130
+ !propsChanged) {
131
+ dlog(`AnimatedProps[${label}] reconcile#${seq} skipped (no-op: already native, props/node unchanged)`);
132
+ return;
133
+ }
134
+ inReconcile = true;
135
+ try {
136
+ // Attach the NEW leaf BEFORE detaching the OLD one: a shared Value self-detaches (dropping
137
+ // its native node) the instant its child count hits zero, so detaching first would kill a
138
+ // running native animation on any unrelated update. Mirrors RN's
139
+ // AnimatedComponent._attachProps.
140
+ const newLeaf = new AnimatedProps(props);
141
+ newLeaf.__attach();
142
+ if (attached !== null && attached !== newLeaf)
143
+ attached.__detach();
144
+ attached = newLeaf;
145
+ // The native half, which a caller may defer. Rebinds events each reconcile so a new inline
146
+ // event re-attaches, detaching first so the prior binding does not leak;
147
+ // attachNativeEventHandler no-ops unless the prop really is a native event handler on a
148
+ // committed node, so the JS path stays the fallback.
149
+ const bindNative = () => {
150
+ if (node !== null)
151
+ newLeaf.setNativeView(node);
152
+ if (wantsNative)
153
+ newLeaf.__makeNative();
154
+ detachEvents();
155
+ if (node === null)
156
+ return;
157
+ for (const key of Object.keys(props)) {
158
+ const attachment = attachNativeEventHandler(node, key, props[key]);
159
+ if (attachment !== undefined)
160
+ eventDetachers.push(attachment.detach);
161
+ }
162
+ };
163
+ cancelPendingBind?.();
164
+ cancelPendingBind = undefined;
165
+ if (scheduleNativeBind === undefined || node === null) {
166
+ bindNative();
167
+ return;
168
+ }
169
+ cancelPendingBind = scheduleNativeBind(bindNative) ?? undefined;
170
+ }
171
+ finally {
172
+ inReconcile = false;
173
+ }
174
+ },
175
+ teardown() {
176
+ cancelPendingBind?.();
177
+ cancelPendingBind = undefined;
178
+ detachEvents();
179
+ if (attached !== null) {
180
+ attached.__detach();
181
+ attached = null;
182
+ }
183
+ },
184
+ };
185
+ }
@@ -11,7 +11,7 @@ import { AnimatedValueXY } from './value-xy.js';
11
11
  import { AnimatedColor } from './color.js';
12
12
  import { AnimatedNode } from './graph.js';
13
13
  import { Easing } from './easing.js';
14
- import { add, subtract, multiply, divide, modulo, diffClamp } from './operators.js';
14
+ import { add, subtract, multiply, divide, modulo, diffClamp, } from './operators.js';
15
15
  import { event, forkEvent, unforkEvent } from './event.js';
16
16
  import { dlog } from '../debug.js';
17
17
  // Prevent a callback invocation from recursively triggering another callback,
@@ -11,7 +11,7 @@
11
11
  // native host) is inert until a native-driven animation actually starts.
12
12
  import { dlog } from '../../debug.js';
13
13
  import { getNativeModule } from '../../native-modules/index.js';
14
- import { NativeEventEmitter } from '../../native-events.js';
14
+ import { NativeEventEmitter, } from '../../native-events.js';
15
15
  import { isRecord } from '../../type-guards.js';
16
16
  // iOS bridgeless registers the Turbo variant; the legacy name is the fallback.
17
17
  const TURBO_MODULE_NAME = 'NativeAnimatedTurboModule';
@@ -101,6 +101,7 @@ export const nativeAnimated = {
101
101
  module()?.stopAnimation(animationId);
102
102
  },
103
103
  setAnimatedNodeValue(nodeTag, value) {
104
+ dlog(`native: setAnimatedNodeValue node=${nodeTag} value=${value}`);
104
105
  module()?.setAnimatedNodeValue(nodeTag, value);
105
106
  },
106
107
  setAnimatedNodeOffset(nodeTag, offset) {
@@ -57,7 +57,10 @@ export class AnimatedAddition extends AnimatedBinaryOp {
57
57
  return a + b;
58
58
  }
59
59
  __getNativeConfig() {
60
- return { type: 'addition', input: [this.a.__getNativeTag(), this.b.__getNativeTag()] };
60
+ return {
61
+ type: 'addition',
62
+ input: [this.a.__getNativeTag(), this.b.__getNativeTag()],
63
+ };
61
64
  }
62
65
  }
63
66
  export class AnimatedSubtraction extends AnimatedBinaryOp {
@@ -65,7 +68,10 @@ export class AnimatedSubtraction extends AnimatedBinaryOp {
65
68
  return a - b;
66
69
  }
67
70
  __getNativeConfig() {
68
- return { type: 'subtraction', input: [this.a.__getNativeTag(), this.b.__getNativeTag()] };
71
+ return {
72
+ type: 'subtraction',
73
+ input: [this.a.__getNativeTag(), this.b.__getNativeTag()],
74
+ };
69
75
  }
70
76
  }
71
77
  export class AnimatedMultiplication extends AnimatedBinaryOp {
@@ -73,7 +79,10 @@ export class AnimatedMultiplication extends AnimatedBinaryOp {
73
79
  return a * b;
74
80
  }
75
81
  __getNativeConfig() {
76
- return { type: 'multiplication', input: [this.a.__getNativeTag(), this.b.__getNativeTag()] };
82
+ return {
83
+ type: 'multiplication',
84
+ input: [this.a.__getNativeTag(), this.b.__getNativeTag()],
85
+ };
77
86
  }
78
87
  }
79
88
  export class AnimatedDivision extends AnimatedBinaryOp {
@@ -97,7 +106,10 @@ export class AnimatedDivision extends AnimatedBinaryOp {
97
106
  return a / b;
98
107
  }
99
108
  __getNativeConfig() {
100
- return { type: 'division', input: [this.a.__getNativeTag(), this.b.__getNativeTag()] };
109
+ return {
110
+ type: 'division',
111
+ input: [this.a.__getNativeTag(), this.b.__getNativeTag()],
112
+ };
101
113
  }
102
114
  }
103
115
  export class AnimatedModulo extends AnimatedWithChildren {
@@ -127,7 +139,11 @@ export class AnimatedModulo extends AnimatedWithChildren {
127
139
  super.__makeNative(platformConfig);
128
140
  }
129
141
  __getNativeConfig() {
130
- return { type: 'modulus', input: this.a.__getNativeTag(), modulus: this.modulus };
142
+ return {
143
+ type: 'modulus',
144
+ input: this.a.__getNativeTag(),
145
+ modulus: this.modulus,
146
+ };
131
147
  }
132
148
  }
133
149
  export class AnimatedDiffClamp extends AnimatedWithChildren {
@@ -167,7 +183,12 @@ export class AnimatedDiffClamp extends AnimatedWithChildren {
167
183
  super.__makeNative(platformConfig);
168
184
  }
169
185
  __getNativeConfig() {
170
- return { type: 'diffclamp', input: this.a.__getNativeTag(), min: this.min, max: this.max };
186
+ return {
187
+ type: 'diffclamp',
188
+ input: this.a.__getNativeTag(),
189
+ min: this.min,
190
+ max: this.max,
191
+ };
171
192
  }
172
193
  }
173
194
  export function add(a, b) {
@@ -15,7 +15,7 @@ import { AnimatedNode, AnimatedWithChildren } from './graph.js';
15
15
  import { setNativeProps, getNativeTag } from '../commit.js';
16
16
  import { isSymbioteNode } from '../node.js';
17
17
  import { registerPostCommit } from '../post-commit.js';
18
- import { nativeAnimated } from './native/native-animated.js';
18
+ import { nativeAnimated, } from './native/native-animated.js';
19
19
  import { AnimatedStyle } from './style.js';
20
20
  function isAnimatedNode(value) {
21
21
  return value instanceof AnimatedNode;
@@ -0,0 +1,8 @@
1
+ export interface IRgbaValue {
2
+ r: number;
3
+ g: number;
4
+ b: number;
5
+ a: number;
6
+ }
7
+ export declare const DEFAULT_COLOR: IRgbaValue;
8
+ export declare function normalizeColor(color: string | number): IRgbaValue | undefined;
@@ -0,0 +1,70 @@
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 {
20
+ r: (c >>> 24) & 255,
21
+ g: (c >>> 16) & 255,
22
+ b: (c >>> 8) & 255,
23
+ a: (c & 255) / 255,
24
+ };
25
+ }
26
+ const trimmed = color.trim();
27
+ if (trimmed.startsWith('#'))
28
+ return parseHex(trimmed);
29
+ if (/^rgba?\(/i.test(trimmed))
30
+ return parseRgb(trimmed);
31
+ return undefined;
32
+ }
33
+ function parseHex(hex) {
34
+ let body = hex.slice(1);
35
+ if (body.length === 3 || body.length === 4) {
36
+ body = body
37
+ .split('')
38
+ .map(c => c + c)
39
+ .join('');
40
+ }
41
+ if (body.length !== 6 && body.length !== 8)
42
+ return undefined;
43
+ const int = Number.parseInt(body, 16);
44
+ if (Number.isNaN(int))
45
+ return undefined;
46
+ if (body.length === 6) {
47
+ return { r: (int >>> 16) & 255, g: (int >>> 8) & 255, b: int & 255, a: 1 };
48
+ }
49
+ const c = int >>> 0;
50
+ return {
51
+ r: (c >>> 24) & 255,
52
+ g: (c >>> 16) & 255,
53
+ b: (c >>> 8) & 255,
54
+ a: (c & 255) / 255,
55
+ };
56
+ }
57
+ function parseRgb(str) {
58
+ const match = /^rgba?\(([^)]+)\)$/i.exec(str);
59
+ if (match === null)
60
+ return undefined;
61
+ const parts = match[1].split(',').map(part => Number.parseFloat(part.trim()));
62
+ if (parts.length < 3 || parts.some(n => Number.isNaN(n)))
63
+ return undefined;
64
+ return {
65
+ r: parts[0],
66
+ g: parts[1],
67
+ b: parts[2],
68
+ a: parts.length >= 4 ? parts[3] : 1,
69
+ };
70
+ }
@@ -73,7 +73,9 @@ export class AnimatedTransform extends AnimatedWithChildren {
73
73
  const result = {};
74
74
  for (const key of Object.keys(entry)) {
75
75
  const value = entry[key];
76
- result[key] = isAnimatedNode(value) ? value.__getAnimatedValue() : value;
76
+ result[key] = isAnimatedNode(value)
77
+ ? value.__getAnimatedValue()
78
+ : value;
77
79
  }
78
80
  return result;
79
81
  });
@@ -96,10 +98,18 @@ export class AnimatedTransform extends AnimatedWithChildren {
96
98
  const value = entry[key];
97
99
  if (isAnimatedNode(value)) {
98
100
  // __getNativeTag only: creation is edge-free; the connect is a later phase.
99
- transforms.push({ type: 'animated', property: key, nodeTag: value.__getNativeTag() });
101
+ transforms.push({
102
+ type: 'animated',
103
+ property: key,
104
+ nodeTag: value.__getNativeTag(),
105
+ });
100
106
  }
101
107
  else {
102
- transforms.push({ type: 'static', property: key, value: transformDataType(value) });
108
+ transforms.push({
109
+ type: 'static',
110
+ property: key,
111
+ value: transformDataType(value),
112
+ });
103
113
  }
104
114
  }
105
115
  }
@@ -26,10 +26,13 @@ export declare class AnimatedValueXY {
26
26
  left: AnimatedValue;
27
27
  top: AnimatedValue;
28
28
  };
29
- getTranslateTransform(): [{
30
- translateX: AnimatedValue;
31
- }, {
32
- translateY: AnimatedValue;
33
- }];
29
+ getTranslateTransform(): [
30
+ {
31
+ translateX: AnimatedValue;
32
+ },
33
+ {
34
+ translateY: AnimatedValue;
35
+ }
36
+ ];
34
37
  }
35
38
  export {};
@@ -17,7 +17,10 @@ export class AnimatedValueXY {
17
17
  // Each joint id maps to the two per-axis listener ids it registered, so
18
18
  // removeListener can tear both down together.
19
19
  jointListeners = new Map();
20
- constructor(value = { x: 0, y: 0 }) {
20
+ constructor(value = {
21
+ x: 0,
22
+ y: 0,
23
+ }) {
21
24
  if (typeof value.x === 'number' && typeof value.y === 'number') {
22
25
  this.x = new AnimatedValue(value.x);
23
26
  this.y = new AnimatedValue(value.y);
@@ -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.
@@ -62,7 +62,10 @@ export const Appearance = {
62
62
  return;
63
63
  }
64
64
  module.setColorScheme(colorScheme);
65
- cachedScheme = colorScheme === 'unspecified' ? (module.getColorScheme() ?? null) : colorScheme;
65
+ cachedScheme =
66
+ colorScheme === 'unspecified'
67
+ ? (module.getColorScheme() ?? null)
68
+ : colorScheme;
66
69
  },
67
70
  // Subscribe to color-scheme changes. The listener receives `{ colorScheme }`.
68
71
  addChangeListener(listener) {
package/build/commit.d.ts CHANGED
@@ -3,6 +3,19 @@ import { type ISymbioteNode } from './node';
3
3
  export { processColor, setColorProcessor } from './platform-color';
4
4
  export declare function disposeRoot(rootTag: IRootTag): void;
5
5
  export declare function commitChildren(rootTag: IRootTag, children: readonly ISymbioteNode[]): void;
6
+ export interface ICommitProfile {
7
+ commits: number;
8
+ walkMs: number;
9
+ nodesVisited: number;
10
+ propWrites: number;
11
+ propNoops: number;
12
+ childScans: number;
13
+ childScanProbed: number;
14
+ childFlattens: number;
15
+ childFlattenProbed: number;
16
+ childFlattenWidest: number;
17
+ }
18
+ export declare function readCommitProfile(): ICommitProfile;
6
19
  export declare function setNativeProps(node: ISymbioteNode, partial: Record<string, unknown>): void;
7
20
  export declare function getNativeTag(node: ISymbioteNode): number | undefined;
8
21
  export declare function whenCommitted(node: ISymbioteNode, action: () => void): () => void;