@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.
- package/README.md +17 -10
- package/build/action-sheet-ios/index.js +4 -1
- package/build/alert/index.ios.js +10 -0
- package/build/animated/animations/composition.js +3 -1
- package/build/animated/animations/spring-config.js +4 -1
- package/build/animated/animations/spring.js +23 -9
- package/build/animated/animations/timing.js +3 -2
- package/build/animated/bezier.js +2 -1
- package/build/animated/color.d.ts +2 -8
- package/build/animated/color.js +8 -47
- package/build/animated/easing.js +0 -8
- package/build/animated/event.js +7 -3
- package/build/animated/graph.d.ts +13 -5
- package/build/animated/graph.js +92 -13
- package/build/animated/index.d.ts +3 -3
- package/build/animated/index.js +3 -3
- package/build/animated/interpolation.js +8 -3
- package/build/animated/leaf-lifecycle.d.ts +7 -0
- package/build/animated/leaf-lifecycle.js +185 -0
- package/build/animated/mock.js +1 -1
- package/build/animated/native/native-animated.js +2 -1
- package/build/animated/operators.js +27 -6
- package/build/animated/props.js +1 -1
- package/build/animated/rgba.d.ts +8 -0
- package/build/animated/rgba.js +70 -0
- package/build/animated/style.js +13 -3
- package/build/animated/value-xy.d.ts +8 -5
- package/build/animated/value-xy.js +4 -1
- package/build/animated/value.js +12 -7
- package/build/appearance/index.js +4 -1
- package/build/commit.d.ts +13 -0
- package/build/commit.js +146 -14
- package/build/debug.d.ts +1 -1
- package/build/debug.js +10 -3
- package/build/events/index.js +19 -10
- package/build/fabric-props.js +8 -2
- package/build/image-loader.js +3 -2
- package/build/index.d.ts +23 -19
- package/build/index.js +19 -11
- package/build/interaction-manager/index.js +2 -1
- package/build/layout-animation/index.js +12 -5
- package/build/native-events.js +2 -1
- package/build/native-modules/index.js +3 -1
- package/build/node.d.ts +25 -0
- package/build/node.js +168 -2
- package/build/pan-responder/index.js +20 -9
- package/build/permissions-android/index.js +3 -1
- package/build/platform/index.android.d.ts +1 -1
- package/build/platform/index.android.js +1 -1
- package/build/platform/index.ios.d.ts +1 -1
- package/build/platform-color/index.js +3 -1
- package/build/post-commit.d.ts +1 -0
- package/build/post-commit.js +6 -0
- package/build/process-background-image/index.js +30 -9
- package/build/process-filter.js +11 -4
- package/build/process-font-variant.js +0 -1
- package/build/process-transform/index.js +13 -19
- package/build/registry.js +10 -2
- package/build/report-error.d.ts +5 -0
- package/build/report-error.js +63 -0
- package/build/share/index.android.js +1 -1
- package/build/share/index.ios.js +1 -1
- package/build/status-bar/index.android.js +8 -10
- package/build/status-bar/index.ios.js +1 -1
- package/build/style-registry/index.d.ts +9 -2
- package/build/style-registry/index.js +149 -95
- package/build/style-registry/scope.d.ts +15 -1
- package/build/style-registry/scope.js +29 -27
- package/build/style-sheet/index.js +4 -2
- package/build/styles.d.ts +11 -1
- package/build/surface.d.ts +1 -1
- package/build/surface.js +7 -1
- package/build/tags.d.ts +1 -0
- package/build/tags.js +31 -1
- package/build/touch-history.js +9 -2
- package/build/vibration/index.android.js +2 -3
- package/build/vibration/index.ios.js +1 -1
- package/build/view-config.js +14 -2
- package/package.json +2 -2
- package/build/animated/interpolation-node.d.ts +0 -15
- package/build/animated/interpolation-node.js +0 -58
- package/build/style-registry/scope/index.d.ts +0 -3
- package/build/style-registry/scope/index.js +0 -41
- package/build/touch-history/index.d.ts +0 -23
- package/build/touch-history/index.js +0 -150
- package/build/type-guards/index.d.ts +0 -4
- 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
|
+
}
|
package/build/animated/mock.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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) {
|
package/build/animated/props.js
CHANGED
|
@@ -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,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
|
+
}
|
package/build/animated/style.js
CHANGED
|
@@ -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)
|
|
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({
|
|
101
|
+
transforms.push({
|
|
102
|
+
type: 'animated',
|
|
103
|
+
property: key,
|
|
104
|
+
nodeTag: value.__getNativeTag(),
|
|
105
|
+
});
|
|
100
106
|
}
|
|
101
107
|
else {
|
|
102
|
-
transforms.push({
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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 = {
|
|
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);
|
package/build/animated/value.js
CHANGED
|
@@ -119,13 +119,12 @@ export class AnimatedValue extends AnimatedWithChildren {
|
|
|
119
119
|
this.offset = offset;
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
-
//
|
|
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
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
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 =
|
|
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;
|