@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
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ current one, clones only what changed, and calls `completeRoot` — the persiste
|
|
|
9
9
|
dance Fabric requires, done **once**, for every framework.
|
|
10
10
|
|
|
11
11
|
> New to SymbioteNative? The [root README](../../README.md) has the architecture and the one fact it
|
|
12
|
-
> rests on — React is just
|
|
12
|
+
> rests on — React is just _one client_ of `nativeFabricUIManager`. This package is what sits
|
|
13
13
|
> between every adapter and that native slot.
|
|
14
14
|
|
|
15
15
|
---
|
|
@@ -48,17 +48,24 @@ The entire surface a renderer seam drives:
|
|
|
48
48
|
|
|
49
49
|
```ts
|
|
50
50
|
import {
|
|
51
|
-
createElement,
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
createElement,
|
|
52
|
+
createRawText,
|
|
53
|
+
createAnchor,
|
|
54
|
+
appendChild,
|
|
55
|
+
insertBefore,
|
|
56
|
+
removeChild,
|
|
57
|
+
routeProp,
|
|
58
|
+
setEventListener,
|
|
59
|
+
setProp,
|
|
60
|
+
setText,
|
|
54
61
|
} from '@symbiote-native/engine';
|
|
55
62
|
|
|
56
|
-
const node = createElement('RCTView');
|
|
63
|
+
const node = createElement('RCTView'); // component IS the Fabric view name
|
|
57
64
|
const text = createRawText('Hello');
|
|
58
65
|
appendChild(node, text);
|
|
59
|
-
routeProp(node, 'onPress', () => {});
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
routeProp(node, 'onPress', () => {}); // ← the flat-bag entry point (React/Vue/Solid):
|
|
67
|
+
// decides event-vs-prop via the ViewConfig,
|
|
68
|
+
// NOT by the "onX" naming convention
|
|
62
69
|
```
|
|
63
70
|
|
|
64
71
|
`routeProp` is the one call a flat-bag adapter should route every prop through — a **structural**
|
|
@@ -72,7 +79,7 @@ import { createSurface } from '@symbiote-native/engine';
|
|
|
72
79
|
|
|
73
80
|
const surface = createSurface(rootTag);
|
|
74
81
|
surface.appendChild(root, node);
|
|
75
|
-
surface.commit();
|
|
82
|
+
surface.commit(); // synchronous — for a framework that already batches (React)
|
|
76
83
|
// surface.requestCommit(); // microtask-coalesced — for reactive frameworks (Vue/Svelte/Angular)
|
|
77
84
|
```
|
|
78
85
|
|
|
@@ -98,7 +105,7 @@ before a tag is guaranteed to exist.
|
|
|
98
105
|
- **The style pipeline** — `flattenStyle`, the CSS-style processors RN itself runs in JS
|
|
99
106
|
(`processBoxShadow`, `processFilter`, `processTransform`, `processTransformOrigin`,
|
|
100
107
|
`processAspectRatio`, `processFontVariant`, `processBackgroundImage`), and the runtime
|
|
101
|
-
**class-name registry** (`
|
|
108
|
+
**class-name registry** (`registerRules` / `resolveClassName` / `renameClassTokens`) that
|
|
102
109
|
`@symbiote-native/css-parser`'s build-time output resolves against — shared by every adapter's
|
|
103
110
|
`class` / `className` / `addClass` prop path.
|
|
104
111
|
- **`AppRegistry` core** (`createAppRegistry`) — registry bookkeeping + headless-task plumbing;
|
|
@@ -41,7 +41,10 @@ export const ActionSheetIOS = {
|
|
|
41
41
|
else if (typeof destructiveButtonIndex === 'number') {
|
|
42
42
|
destructiveButtonIndices = [destructiveButtonIndex];
|
|
43
43
|
}
|
|
44
|
-
const nativeOptions = {
|
|
44
|
+
const nativeOptions = {
|
|
45
|
+
...remainingOptions,
|
|
46
|
+
destructiveButtonIndices,
|
|
47
|
+
};
|
|
45
48
|
manager.showActionSheetWithOptions(nativeOptions, buttonIndex => {
|
|
46
49
|
dlog(`ActionSheetIOS callback buttonIndex=${buttonIndex}`);
|
|
47
50
|
callback(buttonIndex);
|
package/build/alert/index.ios.js
CHANGED
|
@@ -43,6 +43,16 @@ function prompt(title, message, callbackOrButtons, type = 'plain-text', defaultV
|
|
|
43
43
|
if (btn.isPreferred) {
|
|
44
44
|
preferredButtonKey = String(index);
|
|
45
45
|
}
|
|
46
|
+
// UPSTREAM-BUG(react-native): Libraries/Alert/Alert.js:173 - a TRAILING button with no text
|
|
47
|
+
// is left out of the native buttons array while its onPress still occupies callbacks[index],
|
|
48
|
+
// so that handler can never fire: native has no id to return for a button it was never told
|
|
49
|
+
// about. Only the last button, only when textless, which reads more like leftover
|
|
50
|
+
// length - 1 bookkeeping than an intended "omit a blank trailing button" affordance.
|
|
51
|
+
// Ported for parity; do NOT fix without recording a deliberate divergence.
|
|
52
|
+
//
|
|
53
|
+
// The `!== undefined` here IS a deliberate divergence from upstream's truthy `btn.text`:
|
|
54
|
+
// an explicit `text: ''` is a caller asking for a blank label, not an omission, and this
|
|
55
|
+
// codebase treats falsy-vs-absent as distinct on principle. Upstream drops that button.
|
|
46
56
|
if (btn.text !== undefined || index < callbackOrButtons.length - 1) {
|
|
47
57
|
buttons.push({ [index]: btn.text ?? '' });
|
|
48
58
|
}
|
|
@@ -211,7 +211,9 @@ export function loop(animation, config = {}) {
|
|
|
211
211
|
return;
|
|
212
212
|
}
|
|
213
213
|
const restart = (result = { finished: true }) => {
|
|
214
|
-
if (isFinished ||
|
|
214
|
+
if (isFinished ||
|
|
215
|
+
iterationsSoFar === iterations ||
|
|
216
|
+
result.finished === false) {
|
|
215
217
|
callback?.(result);
|
|
216
218
|
}
|
|
217
219
|
else {
|
|
@@ -34,7 +34,10 @@ export function fromBouncinessAndSpeed(bounciness, speed) {
|
|
|
34
34
|
return 0.000044 * Math.pow(x, 3) - 0.006 * Math.pow(x, 2) + 0.36 * x + 2;
|
|
35
35
|
}
|
|
36
36
|
function b3Friction3(x) {
|
|
37
|
-
return 0.00000045 * Math.pow(x, 3) -
|
|
37
|
+
return (0.00000045 * Math.pow(x, 3) -
|
|
38
|
+
0.000332 * Math.pow(x, 2) +
|
|
39
|
+
0.1078 * x +
|
|
40
|
+
5.84);
|
|
38
41
|
}
|
|
39
42
|
function b3Nobounce(tension) {
|
|
40
43
|
if (tension <= 18)
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
// continuous. The native-config export is dropped.
|
|
7
7
|
import { dlog } from '../../debug.js';
|
|
8
8
|
import { BaseAnimation } from './base.js';
|
|
9
|
-
import { cancelFrame, clearTimer, requestFrame, setTimer } from './raf.js';
|
|
10
|
-
import { fromBouncinessAndSpeed, fromOrigamiTensionAndFriction } from './spring-config.js';
|
|
9
|
+
import { cancelFrame, clearTimer, requestFrame, setTimer, } from './raf.js';
|
|
10
|
+
import { fromBouncinessAndSpeed, fromOrigamiTensionAndFriction, } from './spring-config.js';
|
|
11
11
|
function resolveStiffnessDampingMass(config) {
|
|
12
|
-
if (config.stiffness !== undefined ||
|
|
12
|
+
if (config.stiffness !== undefined ||
|
|
13
|
+
config.damping !== undefined ||
|
|
14
|
+
config.mass !== undefined) {
|
|
13
15
|
return {
|
|
14
16
|
stiffness: config.stiffness ?? 100,
|
|
15
17
|
damping: config.damping ?? 10,
|
|
@@ -18,10 +20,18 @@ function resolveStiffnessDampingMass(config) {
|
|
|
18
20
|
}
|
|
19
21
|
if (config.bounciness !== undefined || config.speed !== undefined) {
|
|
20
22
|
const springConfig = fromBouncinessAndSpeed(config.bounciness ?? 8, config.speed ?? 12);
|
|
21
|
-
return {
|
|
23
|
+
return {
|
|
24
|
+
stiffness: springConfig.stiffness,
|
|
25
|
+
damping: springConfig.damping,
|
|
26
|
+
mass: 1,
|
|
27
|
+
};
|
|
22
28
|
}
|
|
23
29
|
const springConfig = fromOrigamiTensionAndFriction(config.tension ?? 40, config.friction ?? 7);
|
|
24
|
-
return {
|
|
30
|
+
return {
|
|
31
|
+
stiffness: springConfig.stiffness,
|
|
32
|
+
damping: springConfig.damping,
|
|
33
|
+
mass: 1,
|
|
34
|
+
};
|
|
25
35
|
}
|
|
26
36
|
export class SpringAnimation extends BaseAnimation {
|
|
27
37
|
overshootClamping;
|
|
@@ -141,7 +151,8 @@ export class SpringAnimation extends BaseAnimation {
|
|
|
141
151
|
position =
|
|
142
152
|
this.toValue -
|
|
143
153
|
envelope *
|
|
144
|
-
(((v0 + zeta * omega0 * x0) / omega1) * Math.sin(omega1 * t) +
|
|
154
|
+
(((v0 + zeta * omega0 * x0) / omega1) * Math.sin(omega1 * t) +
|
|
155
|
+
x0 * Math.cos(omega1 * t));
|
|
145
156
|
velocity =
|
|
146
157
|
zeta *
|
|
147
158
|
omega0 *
|
|
@@ -149,13 +160,15 @@ export class SpringAnimation extends BaseAnimation {
|
|
|
149
160
|
((Math.sin(omega1 * t) * (v0 + zeta * omega0 * x0)) / omega1 +
|
|
150
161
|
x0 * Math.cos(omega1 * t)) -
|
|
151
162
|
envelope *
|
|
152
|
-
(Math.cos(omega1 * t) * (v0 + zeta * omega0 * x0) -
|
|
163
|
+
(Math.cos(omega1 * t) * (v0 + zeta * omega0 * x0) -
|
|
164
|
+
omega1 * x0 * Math.sin(omega1 * t));
|
|
153
165
|
}
|
|
154
166
|
else {
|
|
155
167
|
// Critically damped.
|
|
156
168
|
const envelope = Math.exp(-omega0 * t);
|
|
157
169
|
position = this.toValue - envelope * (x0 + (v0 + omega0 * x0) * t);
|
|
158
|
-
velocity =
|
|
170
|
+
velocity =
|
|
171
|
+
envelope * (v0 * (t * omega0 - 1) + t * x0 * (omega0 * omega0));
|
|
159
172
|
}
|
|
160
173
|
this.lastTime = now;
|
|
161
174
|
this.lastPosition = position;
|
|
@@ -177,7 +190,8 @@ export class SpringAnimation extends BaseAnimation {
|
|
|
177
190
|
const isVelocity = Math.abs(velocity) <= this.restSpeedThreshold;
|
|
178
191
|
let isDisplacement = true;
|
|
179
192
|
if (this.stiffness !== 0) {
|
|
180
|
-
isDisplacement =
|
|
193
|
+
isDisplacement =
|
|
194
|
+
Math.abs(this.toValue - position) <= this.restDisplacementThreshold;
|
|
181
195
|
}
|
|
182
196
|
if (isOvershooting || (isVelocity && isDisplacement)) {
|
|
183
197
|
if (this.stiffness !== 0) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { Easing } from '../easing.js';
|
|
6
6
|
import { dlog } from '../../debug.js';
|
|
7
7
|
import { BaseAnimation } from './base.js';
|
|
8
|
-
import { cancelFrame, clearTimer, requestFrame, setTimer } from './raf.js';
|
|
8
|
+
import { cancelFrame, clearTimer, requestFrame, setTimer, } from './raf.js';
|
|
9
9
|
let cachedEaseInOut;
|
|
10
10
|
function easeInOut() {
|
|
11
11
|
if (cachedEaseInOut === undefined) {
|
|
@@ -80,7 +80,8 @@ export class TimingAnimation extends BaseAnimation {
|
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
82
82
|
this.onUpdate(this.fromValue +
|
|
83
|
-
this.easing((now - this.startTime) / this.duration) *
|
|
83
|
+
this.easing((now - this.startTime) / this.duration) *
|
|
84
|
+
(this.toValue - this.fromValue));
|
|
84
85
|
if (this.__active) {
|
|
85
86
|
this.animationFrame = requestFrame(() => this.onFrame());
|
|
86
87
|
}
|
package/build/animated/bezier.js
CHANGED
|
@@ -40,7 +40,8 @@ function binarySubdivide(x, lower, upper, mX1, mX2) {
|
|
|
40
40
|
else {
|
|
41
41
|
lo = currentT;
|
|
42
42
|
}
|
|
43
|
-
} while (Math.abs(currentX) > SUBDIVISION_PRECISION &&
|
|
43
|
+
} while (Math.abs(currentX) > SUBDIVISION_PRECISION &&
|
|
44
|
+
++i < SUBDIVISION_MAX_ITERATIONS);
|
|
44
45
|
return currentT;
|
|
45
46
|
}
|
|
46
47
|
function newtonRaphsonIterate(x, guess, mX1, mX2) {
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { AnimatedWithChildren } from './graph';
|
|
2
2
|
import { AnimatedValue } from './value';
|
|
3
3
|
import type { INativeNodeConfig, IPlatformConfig } from './native/native-animated';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
g: number;
|
|
7
|
-
b: number;
|
|
8
|
-
a: number;
|
|
9
|
-
}
|
|
4
|
+
import { normalizeColor, type IRgbaValue } from './rgba';
|
|
5
|
+
export { normalizeColor, type IRgbaValue };
|
|
10
6
|
type IChannel = number | AnimatedValue;
|
|
11
7
|
interface IRgbaInput {
|
|
12
8
|
r: IChannel;
|
|
@@ -15,7 +11,6 @@ interface IRgbaInput {
|
|
|
15
11
|
a: IChannel;
|
|
16
12
|
}
|
|
17
13
|
export type IColorInput = IRgbaInput | IRgbaValue | string | number;
|
|
18
|
-
export declare function normalizeColor(color: string | number): IRgbaValue | undefined;
|
|
19
14
|
export declare class AnimatedColor extends AnimatedWithChildren {
|
|
20
15
|
readonly r: AnimatedValue;
|
|
21
16
|
readonly g: AnimatedValue;
|
|
@@ -34,4 +29,3 @@ export declare class AnimatedColor extends AnimatedWithChildren {
|
|
|
34
29
|
__makeNative(platformConfig?: IPlatformConfig): void;
|
|
35
30
|
__getNativeConfig(): INativeNodeConfig;
|
|
36
31
|
}
|
|
37
|
-
export {};
|
package/build/animated/color.js
CHANGED
|
@@ -6,57 +6,16 @@
|
|
|
6
6
|
// normalizeColor / processColorObject, which live outside shared).
|
|
7
7
|
import { AnimatedWithChildren, flushValue } from './graph.js';
|
|
8
8
|
import { AnimatedValue } from './value.js';
|
|
9
|
-
|
|
9
|
+
import { DEFAULT_COLOR, normalizeColor } from './rgba.js';
|
|
10
|
+
// Re-exported so every existing `from './color'` import keeps working; the definitions now
|
|
11
|
+
// live in the dependency-free ./rgba leaf (see its header for the cycle it breaks).
|
|
12
|
+
export { normalizeColor };
|
|
10
13
|
function isRgbaInput(value) {
|
|
11
|
-
return typeof value === 'object' && value !== null && 'r' in value && 'g' in value;
|
|
14
|
+
return (typeof value === 'object' && value !== null && 'r' in value && 'g' in value);
|
|
12
15
|
}
|
|
13
16
|
function toChannel(value) {
|
|
14
17
|
return value instanceof AnimatedValue ? value : new AnimatedValue(value);
|
|
15
18
|
}
|
|
16
|
-
// Decompose a #hex (3/4/6/8), rgb()/rgba(), or 0xRRGGBBAA number into channels.
|
|
17
|
-
// undefined when unparseable (a named/platform color), so the caller falls back to
|
|
18
|
-
// the default rather than throwing inside a render. Exported so interpolation's
|
|
19
|
-
// color path parses through the same RGBA decoder (DRY) rather than duplicating it.
|
|
20
|
-
export function normalizeColor(color) {
|
|
21
|
-
if (typeof color === 'number') {
|
|
22
|
-
const c = color >>> 0;
|
|
23
|
-
return { r: (c >>> 24) & 255, g: (c >>> 16) & 255, b: (c >>> 8) & 255, a: (c & 255) / 255 };
|
|
24
|
-
}
|
|
25
|
-
const trimmed = color.trim();
|
|
26
|
-
if (trimmed.startsWith('#'))
|
|
27
|
-
return parseHex(trimmed);
|
|
28
|
-
if (/^rgba?\(/i.test(trimmed))
|
|
29
|
-
return parseRgb(trimmed);
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
function parseHex(hex) {
|
|
33
|
-
let body = hex.slice(1);
|
|
34
|
-
if (body.length === 3 || body.length === 4) {
|
|
35
|
-
body = body
|
|
36
|
-
.split('')
|
|
37
|
-
.map(c => c + c)
|
|
38
|
-
.join('');
|
|
39
|
-
}
|
|
40
|
-
if (body.length !== 6 && body.length !== 8)
|
|
41
|
-
return undefined;
|
|
42
|
-
const int = Number.parseInt(body, 16);
|
|
43
|
-
if (Number.isNaN(int))
|
|
44
|
-
return undefined;
|
|
45
|
-
if (body.length === 6) {
|
|
46
|
-
return { r: (int >>> 16) & 255, g: (int >>> 8) & 255, b: int & 255, a: 1 };
|
|
47
|
-
}
|
|
48
|
-
const c = int >>> 0;
|
|
49
|
-
return { r: (c >>> 24) & 255, g: (c >>> 16) & 255, b: (c >>> 8) & 255, a: (c & 255) / 255 };
|
|
50
|
-
}
|
|
51
|
-
function parseRgb(str) {
|
|
52
|
-
const match = /^rgba?\(([^)]+)\)$/i.exec(str);
|
|
53
|
-
if (match === null)
|
|
54
|
-
return undefined;
|
|
55
|
-
const parts = match[1].split(',').map(part => Number.parseFloat(part.trim()));
|
|
56
|
-
if (parts.length < 3 || parts.some(n => Number.isNaN(n)))
|
|
57
|
-
return undefined;
|
|
58
|
-
return { r: parts[0], g: parts[1], b: parts[2], a: parts.length >= 4 ? parts[3] : 1 };
|
|
59
|
-
}
|
|
60
19
|
// Resolve any input form to four concrete channel values (numbers or pre-built
|
|
61
20
|
// AnimatedValues), so the constructor can wrap each in an AnimatedValue.
|
|
62
21
|
function resolveInput(value) {
|
|
@@ -100,7 +59,9 @@ export class AnimatedColor extends AnimatedWithChildren {
|
|
|
100
59
|
// pattern). flushValue dedupes by leaf identity, so a single flush rebuilds
|
|
101
60
|
// every bound prop once even though four channels changed.
|
|
102
61
|
setValue(value) {
|
|
103
|
-
const rgba = typeof value === 'object'
|
|
62
|
+
const rgba = typeof value === 'object'
|
|
63
|
+
? value
|
|
64
|
+
: (normalizeColor(value) ?? DEFAULT_COLOR);
|
|
104
65
|
this.withSuspendedCallbacks(() => {
|
|
105
66
|
this.r.setValue(rgba.r);
|
|
106
67
|
this.g.setValue(rgba.g);
|
package/build/animated/easing.js
CHANGED
|
@@ -4,15 +4,12 @@
|
|
|
4
4
|
import { bezier } from './bezier.js';
|
|
5
5
|
let cachedEase;
|
|
6
6
|
export const Easing = {
|
|
7
|
-
// A stepping function: 1 for any positive n, else 0.
|
|
8
7
|
step0(n) {
|
|
9
8
|
return n > 0 ? 1 : 0;
|
|
10
9
|
},
|
|
11
|
-
// A stepping function: 1 once n >= 1, else 0.
|
|
12
10
|
step1(n) {
|
|
13
11
|
return n >= 1 ? 1 : 0;
|
|
14
12
|
},
|
|
15
|
-
// Linear, f(t) = t.
|
|
16
13
|
linear(t) {
|
|
17
14
|
return t;
|
|
18
15
|
},
|
|
@@ -23,11 +20,9 @@ export const Easing = {
|
|
|
23
20
|
}
|
|
24
21
|
return cachedEase(t);
|
|
25
22
|
},
|
|
26
|
-
// Quadratic, f(t) = t * t.
|
|
27
23
|
quad(t) {
|
|
28
24
|
return t * t;
|
|
29
25
|
},
|
|
30
|
-
// Cubic, f(t) = t * t * t.
|
|
31
26
|
cubic(t) {
|
|
32
27
|
return t * t * t;
|
|
33
28
|
},
|
|
@@ -35,15 +30,12 @@ export const Easing = {
|
|
|
35
30
|
poly(n) {
|
|
36
31
|
return t => Math.pow(t, n);
|
|
37
32
|
},
|
|
38
|
-
// Sinusoidal.
|
|
39
33
|
sin(t) {
|
|
40
34
|
return 1 - Math.cos((t * Math.PI) / 2);
|
|
41
35
|
},
|
|
42
|
-
// Circular.
|
|
43
36
|
circle(t) {
|
|
44
37
|
return 1 - Math.sqrt(1 - t * t);
|
|
45
38
|
},
|
|
46
|
-
// Exponential.
|
|
47
39
|
exp(t) {
|
|
48
40
|
return Math.pow(2, 10 * (t - 1));
|
|
49
41
|
},
|
package/build/animated/event.js
CHANGED
|
@@ -17,14 +17,16 @@ import { dlog } from '../debug.js';
|
|
|
17
17
|
import { getNativeTag, whenCommitted } from '../commit.js';
|
|
18
18
|
import { isSymbioteNode } from '../node.js';
|
|
19
19
|
import { AnimatedNode, flushValue } from './graph.js';
|
|
20
|
-
import { isNativeAnimatedAvailable, nativeAnimated } from './native/native-animated.js';
|
|
20
|
+
import { isNativeAnimatedAvailable, nativeAnimated, } from './native/native-animated.js';
|
|
21
21
|
import { isRecord } from '../type-guards.js';
|
|
22
22
|
// A value node settable from an event field. AnimatedValue carries setValue; we
|
|
23
23
|
// duck-type it (rather than importing AnimatedValue) to keep this leaf-agnostic
|
|
24
24
|
// and avoid a value<->event import cycle.
|
|
25
25
|
function settableValue(node) {
|
|
26
26
|
const candidate = Reflect.get(node, 'setValue');
|
|
27
|
-
return typeof candidate === 'function'
|
|
27
|
+
return typeof candidate === 'function'
|
|
28
|
+
? value => candidate.call(node, value)
|
|
29
|
+
: undefined;
|
|
28
30
|
}
|
|
29
31
|
// Walk the mapping to every leaf AnimatedNode, recording its key path. Shared by
|
|
30
32
|
// the JS handler (to set values) and the native attach (to register paths).
|
|
@@ -128,7 +130,9 @@ export class AnimatedEvent {
|
|
|
128
130
|
if (!this.attachedNatively) {
|
|
129
131
|
// Paths are stored relative to `nativeEvent` (the native module excludes
|
|
130
132
|
// that prefix); the JS event arg still carries it, so re-add it here.
|
|
131
|
-
const nativeEvent = isRecord(args[0])
|
|
133
|
+
const nativeEvent = isRecord(args[0])
|
|
134
|
+
? Reflect.get(args[0], 'nativeEvent')
|
|
135
|
+
: undefined;
|
|
132
136
|
for (const mapped of this.mappedValues) {
|
|
133
137
|
const extracted = extractAtPath(nativeEvent, mapped.path);
|
|
134
138
|
if (extracted === undefined)
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { type INativeNodeConfig, type IPlatformConfig } from './native/native-animated';
|
|
2
|
-
import type
|
|
3
|
-
import type { AnimatedInterpolation } from './interpolation-node';
|
|
2
|
+
import { type IInterpolationConfig } from './interpolation';
|
|
4
3
|
export type IValueListener = (state: {
|
|
5
4
|
value: number | string;
|
|
6
5
|
}) => void;
|
|
7
|
-
type IInterpolationFactory = (parent: AnimatedNode, config: IInterpolationConfig) => AnimatedInterpolation;
|
|
8
|
-
export declare function registerInterpolationFactory(factory: IInterpolationFactory): void;
|
|
9
6
|
export declare class AnimatedNode {
|
|
10
7
|
private readonly listeners;
|
|
11
8
|
private suspendCallbacks;
|
|
@@ -41,4 +38,15 @@ export declare class AnimatedWithChildren extends AnimatedNode {
|
|
|
41
38
|
__callListeners(value: number | string): void;
|
|
42
39
|
}
|
|
43
40
|
export declare function flushValue(rootNode: AnimatedNode): void;
|
|
44
|
-
export {
|
|
41
|
+
export declare class AnimatedInterpolation extends AnimatedWithChildren {
|
|
42
|
+
private readonly parent;
|
|
43
|
+
private readonly config;
|
|
44
|
+
private interpolation;
|
|
45
|
+
constructor(parent: AnimatedNode, config: IInterpolationConfig);
|
|
46
|
+
private getInterpolation;
|
|
47
|
+
__getValue(): number | string;
|
|
48
|
+
__attach(): void;
|
|
49
|
+
__detach(): void;
|
|
50
|
+
__makeNative(platformConfig?: IPlatformConfig): void;
|
|
51
|
+
__getNativeConfig(): INativeNodeConfig;
|
|
52
|
+
}
|
package/build/animated/graph.js
CHANGED
|
@@ -10,11 +10,9 @@
|
|
|
10
10
|
// B) bottom-up: each flagged leaf re-pulls its whole subtree via __getValue()
|
|
11
11
|
// to rebuild composite props (e.g. transform from many parents).
|
|
12
12
|
import { generateNativeNodeTag, nativeAnimated, } from './native/native-animated.js';
|
|
13
|
+
import { checkValidRanges, createInterpolation, } from './interpolation.js';
|
|
14
|
+
import { dlog } from '../debug.js';
|
|
13
15
|
let nextListenerId = 1;
|
|
14
|
-
let createInterpolationNode;
|
|
15
|
-
export function registerInterpolationFactory(factory) {
|
|
16
|
-
createInterpolationNode = factory;
|
|
17
|
-
}
|
|
18
16
|
// Depth of the currently-active withSuspendedCallbacks blocks. While > 0, a
|
|
19
17
|
// composite setter (AnimatedColor.setValue) is driving several channels in a row;
|
|
20
18
|
// each channel's own flushValue is suppressed so the bound leaf commits ONCE (at
|
|
@@ -83,7 +81,10 @@ export class AnimatedNode {
|
|
|
83
81
|
// (AnimatedNode.js:146-148) rather than in each node's config method.
|
|
84
82
|
const config = this.platformConfig === undefined
|
|
85
83
|
? this.__getNativeConfig()
|
|
86
|
-
: {
|
|
84
|
+
: {
|
|
85
|
+
...this.__getNativeConfig(),
|
|
86
|
+
platformConfig: this.platformConfig,
|
|
87
|
+
};
|
|
87
88
|
nativeAnimated.createAnimatedNode(this.nativeTag, config);
|
|
88
89
|
}
|
|
89
90
|
return this.nativeTag;
|
|
@@ -104,13 +105,10 @@ export class AnimatedNode {
|
|
|
104
105
|
}
|
|
105
106
|
// Map this node's value before it reaches a prop, e.g. 0..1 -> 0..10. Defined once
|
|
106
107
|
// here (Information Expert: every node subclass wants the identical one-liner)
|
|
107
|
-
// instead of duplicated per subclass
|
|
108
|
-
//
|
|
108
|
+
// instead of duplicated per subclass. AnimatedInterpolation lives at the bottom of
|
|
109
|
+
// THIS file rather than its own module on purpose — see the comment there.
|
|
109
110
|
interpolate(config) {
|
|
110
|
-
|
|
111
|
-
throw new Error('AnimatedNode.interpolate: interpolation factory not registered');
|
|
112
|
-
}
|
|
113
|
-
return createInterpolationNode(this, config);
|
|
111
|
+
return new AnimatedInterpolation(this, config);
|
|
114
112
|
}
|
|
115
113
|
__addChild(_child) { }
|
|
116
114
|
__removeChild(_child) { }
|
|
@@ -199,8 +197,18 @@ export class AnimatedWithChildren extends AnimatedNode {
|
|
|
199
197
|
super.__callListeners(value);
|
|
200
198
|
// A native-driven node's children are updated natively; don't also walk them
|
|
201
199
|
// here (their values aren't tracked in JS while native owns the animation).
|
|
202
|
-
|
|
200
|
+
//
|
|
201
|
+
// Diagnostic seam, permanent: this early return is what silences a JS listener sitting on a
|
|
202
|
+
// CHILD of a value that was made native (a sticky header's interpolation under a natively
|
|
203
|
+
// attached scroll value). A consumer whose only driver is that listener then goes dead with no
|
|
204
|
+
// other symptom - see the sticky-header bootstrap notes. Logged only when children are
|
|
205
|
+
// actually being skipped, so a childless native value stays quiet.
|
|
206
|
+
if (this.isNative) {
|
|
207
|
+
if (this.children.length > 0) {
|
|
208
|
+
dlog(`STICKY[cascade] native value silences ${this.children.length} child JS listener(s) value=${String(value)}`);
|
|
209
|
+
}
|
|
203
210
|
return;
|
|
211
|
+
}
|
|
204
212
|
for (const child of this.children) {
|
|
205
213
|
child.__callListeners(numericValueOf(child));
|
|
206
214
|
}
|
|
@@ -216,7 +224,9 @@ function numericValueOf(node) {
|
|
|
216
224
|
// under useDefineForClassFields. So leaves are detected structurally instead.
|
|
217
225
|
function leafUpdate(node) {
|
|
218
226
|
const candidate = Reflect.get(node, 'update');
|
|
219
|
-
return typeof candidate === 'function'
|
|
227
|
+
return typeof candidate === 'function'
|
|
228
|
+
? () => candidate.call(node)
|
|
229
|
+
: undefined;
|
|
220
230
|
}
|
|
221
231
|
// Top-down walk to the leaves, then re-pull each leaf (deduped by node identity,
|
|
222
232
|
// so a diamond in the graph still updates a leaf once). Suppressed inside a
|
|
@@ -238,3 +248,72 @@ export function flushValue(rootNode) {
|
|
|
238
248
|
collect(rootNode);
|
|
239
249
|
leaves.forEach(update => update());
|
|
240
250
|
}
|
|
251
|
+
// AnimatedInterpolation: a graph node that maps its parent's numeric value through an
|
|
252
|
+
// interpolation. Ported from RN's AnimatedInterpolation.js (numeric, string-with-units and
|
|
253
|
+
// color output ranges; platform colors stay out of scope, color.ts defers them).
|
|
254
|
+
//
|
|
255
|
+
// IT LIVES HERE, NOT IN ITS OWN MODULE, AND MUST STAY HERE. It used to sit in
|
|
256
|
+
// `interpolation-node.ts` and hand its constructor back to `AnimatedNode.interpolate()` through
|
|
257
|
+
// a `registerInterpolationFactory` call executed at module load — the usual trick for breaking
|
|
258
|
+
// an import cycle (this module owns the base classes that module extended). That trick is
|
|
259
|
+
// UNSOUND under Metro's production bundle: `inlineRequires` (production-only) defers a module's
|
|
260
|
+
// `require()` to the first use of its binding, nothing in the codebase ever names
|
|
261
|
+
// `AnimatedInterpolation` as a value, and a barrel's `export { X } from './x'` compiles to a
|
|
262
|
+
// lazy getter — so the module never evaluated, the factory was never registered, and the first
|
|
263
|
+
// `.interpolate()` on a device threw `interpolation factory not registered`, blanking the screen
|
|
264
|
+
// in RELEASE builds only (dev was fine, every headless test was fine, and the code was even
|
|
265
|
+
// present in the bundle — it just never ran). Adding a bare `import './interpolation-node'` to
|
|
266
|
+
// the barrel does NOT fix it either: Babel merges that import with the adjacent re-export of the
|
|
267
|
+
// same specifier, and the merged dependency stays lazy. Both were tried on device 2026-08-14.
|
|
268
|
+
//
|
|
269
|
+
// Keeping the class next to its base class removes the cycle, the factory, and the entire class
|
|
270
|
+
// of load-order bug with it: `interpolate()` constructs it directly and there is nothing left to
|
|
271
|
+
// register. Do not "tidy" it back into its own file.
|
|
272
|
+
export class AnimatedInterpolation extends AnimatedWithChildren {
|
|
273
|
+
parent;
|
|
274
|
+
config;
|
|
275
|
+
interpolation;
|
|
276
|
+
constructor(parent, config) {
|
|
277
|
+
super();
|
|
278
|
+
this.parent = parent;
|
|
279
|
+
this.config = config;
|
|
280
|
+
// Validate eagerly so a bad range fails at construction, not first frame.
|
|
281
|
+
checkValidRanges(config.inputRange, config.outputRange);
|
|
282
|
+
}
|
|
283
|
+
getInterpolation() {
|
|
284
|
+
if (this.interpolation === undefined) {
|
|
285
|
+
this.interpolation = createInterpolation(this.config);
|
|
286
|
+
}
|
|
287
|
+
return this.interpolation;
|
|
288
|
+
}
|
|
289
|
+
__getValue() {
|
|
290
|
+
const parentValue = this.parent.__getValue();
|
|
291
|
+
if (typeof parentValue !== 'number') {
|
|
292
|
+
throw new Error('Cannot interpolate an input which is not a number');
|
|
293
|
+
}
|
|
294
|
+
return this.getInterpolation()(parentValue);
|
|
295
|
+
}
|
|
296
|
+
__attach() {
|
|
297
|
+
this.parent.__addChild(this);
|
|
298
|
+
super.__attach();
|
|
299
|
+
}
|
|
300
|
+
__detach() {
|
|
301
|
+
this.parent.__removeChild(this);
|
|
302
|
+
super.__detach();
|
|
303
|
+
}
|
|
304
|
+
// Make the upstream value native first, so the parent->interpolation edge can be
|
|
305
|
+
// wired when this node is reached from a leaf rather than from the value.
|
|
306
|
+
__makeNative(platformConfig) {
|
|
307
|
+
this.parent.__makeNative(platformConfig);
|
|
308
|
+
super.__makeNative(platformConfig);
|
|
309
|
+
}
|
|
310
|
+
__getNativeConfig() {
|
|
311
|
+
return {
|
|
312
|
+
type: 'interpolation',
|
|
313
|
+
inputRange: this.config.inputRange,
|
|
314
|
+
outputRange: this.config.outputRange,
|
|
315
|
+
extrapolateLeft: this.config.extrapolateLeft ?? this.config.extrapolate ?? 'extend',
|
|
316
|
+
extrapolateRight: this.config.extrapolateRight ?? this.config.extrapolate ?? 'extend',
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
export { AnimatedNode, AnimatedWithChildren, flushValue, type IValueListener } from './graph';
|
|
1
|
+
export { AnimatedNode, AnimatedWithChildren, AnimatedInterpolation, flushValue, type IValueListener, } from './graph';
|
|
2
2
|
export { AnimatedValue } from './value';
|
|
3
3
|
export { AnimatedValueXY, type IValueXY } from './value-xy';
|
|
4
|
-
export { AnimatedInterpolation } from './interpolation-node';
|
|
5
4
|
export { AnimatedAddition, AnimatedSubtraction, AnimatedMultiplication, AnimatedDivision, AnimatedModulo, AnimatedDiffClamp, add, subtract, multiply, divide, modulo, diffClamp, } from './operators';
|
|
6
5
|
export { AnimatedEvent, event, forkEvent, unforkEvent, attachNativeEvent, attachNativeEventHandler, type IEventConfig, type IEventListener, type IAnimatedEventHandler, type INativeEventAttachment, } from './event';
|
|
7
6
|
export { AnimatedColor, type IRgbaValue, type IColorInput } from './color';
|
|
@@ -15,6 +14,7 @@ export { AnimatedTracking } from './animations/tracking';
|
|
|
15
14
|
export { timing, spring, decay, parallel, sequence, stagger, loop, delay, type ICompositeAnimation, type ITimingConfig, type ISpringConfig, type IDecayConfig, type IParallelConfig, type ILoopAnimationConfig, } from './animations/composition';
|
|
16
15
|
export { nativeAnimated, isNativeAnimatedAvailable, type INativeNodeConfig, type INativeAnimationConfig, type INativeEventMapping, type IPlatformConfig, } from './native/native-animated';
|
|
17
16
|
export { AnimatedProps } from './props';
|
|
17
|
+
export { createAnimatedLeafLifecycle, type IAnimatedLeafLifecycle, } from './leaf-lifecycle';
|
|
18
18
|
export { AnimatedStyle, AnimatedTransform } from './style';
|
|
19
19
|
export { AnimatedMock } from './mock';
|
|
20
|
-
export { reduceProps, isAnimatedNode, readPassthroughStyle, resolveHostNode } from './shared';
|
|
20
|
+
export { reduceProps, isAnimatedNode, readPassthroughStyle, resolveHostNode, } from './shared';
|
package/build/animated/index.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
// @symbiote-native/engine/animated: the framework-agnostic, JS-driven Animated engine.
|
|
2
2
|
// The value graph, easing, interpolation and drivers are pure JS with no React
|
|
3
3
|
// and no native dependency; every adapter re-exports them.
|
|
4
|
-
export { AnimatedNode, AnimatedWithChildren, flushValue } from './graph.js';
|
|
4
|
+
export { AnimatedNode, AnimatedWithChildren, AnimatedInterpolation, flushValue, } from './graph.js';
|
|
5
5
|
export { AnimatedValue } from './value.js';
|
|
6
6
|
export { AnimatedValueXY } from './value-xy.js';
|
|
7
|
-
export { AnimatedInterpolation } from './interpolation-node.js';
|
|
8
7
|
export { AnimatedAddition, AnimatedSubtraction, AnimatedMultiplication, AnimatedDivision, AnimatedModulo, AnimatedDiffClamp, add, subtract, multiply, divide, modulo, diffClamp, } from './operators.js';
|
|
9
8
|
export { AnimatedEvent, event, forkEvent, unforkEvent, attachNativeEvent, attachNativeEventHandler, } from './event.js';
|
|
10
9
|
export { AnimatedColor } from './color.js';
|
|
@@ -22,7 +21,8 @@ export { nativeAnimated, isNativeAnimatedAvailable, } from './native/native-anim
|
|
|
22
21
|
// no React/Vue). They live here with the rest of the graph; every adapter's
|
|
23
22
|
// createAnimatedComponent + Animated namespace re-exports them.
|
|
24
23
|
export { AnimatedProps } from './props.js';
|
|
24
|
+
export { createAnimatedLeafLifecycle, } from './leaf-lifecycle.js';
|
|
25
25
|
export { AnimatedStyle, AnimatedTransform } from './style.js';
|
|
26
26
|
export { AnimatedMock } from './mock.js';
|
|
27
27
|
// Framework-agnostic createAnimatedComponent helpers. Both adapters import them.
|
|
28
|
-
export { reduceProps, isAnimatedNode, readPassthroughStyle, resolveHostNode } from './shared.js';
|
|
28
|
+
export { reduceProps, isAnimatedNode, readPassthroughStyle, resolveHostNode, } from './shared.js';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// extrapolation. A numeric output range stays number -> number; a string output
|
|
5
5
|
// range (values-with-units like '0deg' or colors like '#000') is interpolated
|
|
6
6
|
// per numeric token and reassembled. See createStringInterpolation below.
|
|
7
|
-
import { normalizeColor } from './
|
|
7
|
+
import { normalizeColor } from './rgba.js';
|
|
8
8
|
import { Easing } from './easing.js';
|
|
9
9
|
function interpolateSegment(input, inputMin, inputMax, outputMin, outputMax, easing, extrapolateLeft, extrapolateRight) {
|
|
10
10
|
let result = input;
|
|
@@ -171,7 +171,9 @@ function createStringInterpolation(config, outputRange) {
|
|
|
171
171
|
return input => {
|
|
172
172
|
const values = interpolations.map(interpolation => interpolation(input));
|
|
173
173
|
let i = 0;
|
|
174
|
-
return template
|
|
174
|
+
return template
|
|
175
|
+
.map(c => (typeof c === 'number' ? values[i++] : c))
|
|
176
|
+
.join('');
|
|
175
177
|
};
|
|
176
178
|
}
|
|
177
179
|
// Colors: r,g,b must be integers, so round them; alpha stays continuous but is
|
|
@@ -195,5 +197,8 @@ export function createInterpolation(config) {
|
|
|
195
197
|
if (isStringOutputRange(config.outputRange)) {
|
|
196
198
|
return createStringInterpolation(config, config.outputRange);
|
|
197
199
|
}
|
|
198
|
-
return createNumericInterpolation({
|
|
200
|
+
return createNumericInterpolation({
|
|
201
|
+
...config,
|
|
202
|
+
outputRange: config.outputRange,
|
|
203
|
+
});
|
|
199
204
|
}
|