embedded-react 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +268 -238
- package/aot/compile.mjs +2 -2
- package/aot/screenshot-smoke.mjs +110 -110
- package/assets/emit-pack.mjs +1 -1
- package/cli.mjs +147 -0
- package/package.json +12 -5
- package/persist-transform.mjs +1 -1
- package/sim/embedded-react.js +2 -0
- package/sim/embedded-react.wasm +0 -0
- package/sim/index.html +387 -0
- package/sim-server.mjs +242 -0
- package/src/embedded-react/Animated.js +357 -352
- package/src/embedded-react/AppRegistry.js +49 -49
- package/src/embedded-react/Easing.js +39 -39
- package/src/embedded-react/LayoutAnimation.js +45 -45
- package/src/embedded-react/Platform.js +26 -26
- package/src/embedded-react/StyleSheet.js +36 -36
- package/src/embedded-react/components.js +44 -44
- package/src/embedded-react/index.js +52 -52
- package/src/embedded-react/layout-anim-config.js +91 -91
- package/src/embedded-react/split-style.js +58 -58
- package/src/embedded-react/usePersistentState.js +2 -2
- package/src/host-config.js +196 -196
- package/src/native-ui.js +24 -24
- package/src/props.js +183 -183
- package/src/renderer.js +57 -57
|
@@ -1,352 +1,357 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2026 Cory Lamming
|
|
3
|
-
*
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
* you may not use this file except in compliance with the License.
|
|
6
|
-
* You may obtain a copy of the License at
|
|
7
|
-
*
|
|
8
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
*
|
|
10
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
* See the License for the specific language governing permissions and
|
|
14
|
-
* limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
// Animated — the React Native analog, backed by the engine's native-driver value system
|
|
18
|
-
// (er_anim_value_*). An Animated.Value is a handle to an engine-side float; binding it to a node
|
|
19
|
-
// prop (via Animated.View) lets the engine advance the animation each frame with NO per-frame JS.
|
|
20
|
-
import { createElement, useRef, useEffect } from 'react';
|
|
21
|
-
import { NativeUI } from '../native-ui.js';
|
|
22
|
-
import { splitAnimatedStyle } from './split-style.js';
|
|
23
|
-
|
|
24
|
-
/** A standalone animatable value bound to an engine-side float. */
|
|
25
|
-
export class AnimatedValue {
|
|
26
|
-
constructor(initial = 0) {
|
|
27
|
-
this.__animated = true;
|
|
28
|
-
this._handle = NativeUI.animValueCreate(initial);
|
|
29
|
-
this._value = initial;
|
|
30
|
-
this._destroyed = false;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
setValue(v) {
|
|
34
|
-
this._value = v;
|
|
35
|
-
NativeUI.animValueSet(this._handle, v);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Releases the engine-side value slot. The engine's value pool is fixed-size (unlike RN, where the
|
|
40
|
-
* JS value is simply garbage-collected), so a value tied to a mounting/unmounting component must be
|
|
41
|
-
* freed explicitly — see useAnimatedValue. Idempotent; the value must not be used after destroy().
|
|
42
|
-
*/
|
|
43
|
-
destroy() {
|
|
44
|
-
if (this._destroyed) return;
|
|
45
|
-
this._destroyed = true;
|
|
46
|
-
NativeUI.animValueDestroy(this._handle);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
__getValue() {
|
|
50
|
-
return NativeUI.animValueGet(this._handle);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
__bind(node, prop) {
|
|
54
|
-
// The engine applies the current value on bind and ignores duplicate (node, prop) pairs, so
|
|
55
|
-
// this is safe to call on every render.
|
|
56
|
-
NativeUI.animValueBind(this._handle, node, prop);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
interpolate(config) {
|
|
60
|
-
return new AnimatedInterpolation(this, config);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* A value derived from a parent Animated.Value through a piecewise-linear interpolation.
|
|
66
|
-
* config = { inputRange, outputRange, extrapolate?, extrapolateLeft?, extrapolateRight? }; the
|
|
67
|
-
* extrapolate tokens ('extend' | 'clamp' | 'identity') control behavior outside the input range.
|
|
68
|
-
*/
|
|
69
|
-
export class AnimatedInterpolation {
|
|
70
|
-
constructor(parent, config) {
|
|
71
|
-
this.__animated = true;
|
|
72
|
-
this._parent = parent;
|
|
73
|
-
this._config = config;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
__bind(node, prop) {
|
|
77
|
-
NativeUI.animValueBindInterpolated(this._parent._handle, node, prop, this._config);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
interpolate(config) {
|
|
81
|
-
// Chained interpolation isn't supported by the engine; re-map against the same parent.
|
|
82
|
-
return new AnimatedInterpolation(this._parent, config);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** Wraps fn so it runs at most once, regardless of how many times it is invoked. */
|
|
87
|
-
function once(fn) {
|
|
88
|
-
let called = false;
|
|
89
|
-
return (arg) => {
|
|
90
|
-
if (called) return;
|
|
91
|
-
called = true;
|
|
92
|
-
if (fn) fn(arg);
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Wraps a NativeUI animation call in an object with start()/stop() (RN's animation handle shape).
|
|
98
|
-
*
|
|
99
|
-
* start(onComplete) wires the engine's completion callback through the bridge: onComplete is called
|
|
100
|
-
* with `{ finished }` when the animation ends naturally (finished: true) or is interrupted by a new
|
|
101
|
-
* animation / stop() (finished: false). `_value` is exposed so Animated.loop can reset it between
|
|
102
|
-
* iterations.
|
|
103
|
-
*/
|
|
104
|
-
function makeAnimation(value, toValue, config) {
|
|
105
|
-
let handle = 0;
|
|
106
|
-
return {
|
|
107
|
-
_value: value,
|
|
108
|
-
start(onComplete) {
|
|
109
|
-
// Always pass a wrapper so we can null out the (possibly recycled) handle on completion.
|
|
110
|
-
const cb = (finished) => {
|
|
111
|
-
handle = 0;
|
|
112
|
-
if (onComplete) onComplete({ finished: !!finished });
|
|
113
|
-
};
|
|
114
|
-
handle = NativeUI.animValueAnimate(value._handle, toValue, config, cb);
|
|
115
|
-
},
|
|
116
|
-
stop() {
|
|
117
|
-
if (handle) NativeUI.animStop(handle);
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function timing(value, config = {}) {
|
|
123
|
-
const { toValue = 0, useNativeDriver, ...rest } = config;
|
|
124
|
-
return makeAnimation(value, toValue, { type: 'timing', ...rest });
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export function spring(value, config = {}) {
|
|
128
|
-
const { toValue = 0, useNativeDriver, ...rest } = config;
|
|
129
|
-
return makeAnimation(value, toValue, { type: 'spring', ...rest });
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export function decay(value, config = {}) {
|
|
133
|
-
const { useNativeDriver, ...rest } = config;
|
|
134
|
-
// Decay coasts from its velocity; the engine ignores the target, so pass the current value.
|
|
135
|
-
return makeAnimation(value, value.__getValue(), { type: 'decay', ...rest });
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// --- Composition -------------------------------------------------------------------------------
|
|
139
|
-
// These are pure JS over each child animation's start()/stop() + the §2 timer globals. The engine
|
|
140
|
-
// deactivates a finished animation before firing its completion callback, so chaining the next
|
|
141
|
-
// animation from inside a completion handler is safe (no per-frame JS — each child still runs in C).
|
|
142
|
-
|
|
143
|
-
/** Runs animations one after another; each starts when the previous finishes. */
|
|
144
|
-
export function sequence(animations) {
|
|
145
|
-
let current = 0;
|
|
146
|
-
let stopped = false;
|
|
147
|
-
return {
|
|
148
|
-
start(onComplete) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
},
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Cory Lamming
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// Animated — the React Native analog, backed by the engine's native-driver value system
|
|
18
|
+
// (er_anim_value_*). An Animated.Value is a handle to an engine-side float; binding it to a node
|
|
19
|
+
// prop (via Animated.View) lets the engine advance the animation each frame with NO per-frame JS.
|
|
20
|
+
import { createElement, useRef, useEffect } from 'react';
|
|
21
|
+
import { NativeUI } from '../native-ui.js';
|
|
22
|
+
import { splitAnimatedStyle } from './split-style.js';
|
|
23
|
+
|
|
24
|
+
/** A standalone animatable value bound to an engine-side float. */
|
|
25
|
+
export class AnimatedValue {
|
|
26
|
+
constructor(initial = 0) {
|
|
27
|
+
this.__animated = true;
|
|
28
|
+
this._handle = NativeUI.animValueCreate(initial);
|
|
29
|
+
this._value = initial;
|
|
30
|
+
this._destroyed = false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setValue(v) {
|
|
34
|
+
this._value = v;
|
|
35
|
+
NativeUI.animValueSet(this._handle, v);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Releases the engine-side value slot. The engine's value pool is fixed-size (unlike RN, where the
|
|
40
|
+
* JS value is simply garbage-collected), so a value tied to a mounting/unmounting component must be
|
|
41
|
+
* freed explicitly — see useAnimatedValue. Idempotent; the value must not be used after destroy().
|
|
42
|
+
*/
|
|
43
|
+
destroy() {
|
|
44
|
+
if (this._destroyed) return;
|
|
45
|
+
this._destroyed = true;
|
|
46
|
+
NativeUI.animValueDestroy(this._handle);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
__getValue() {
|
|
50
|
+
return NativeUI.animValueGet(this._handle);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
__bind(node, prop) {
|
|
54
|
+
// The engine applies the current value on bind and ignores duplicate (node, prop) pairs, so
|
|
55
|
+
// this is safe to call on every render.
|
|
56
|
+
NativeUI.animValueBind(this._handle, node, prop);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interpolate(config) {
|
|
60
|
+
return new AnimatedInterpolation(this, config);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A value derived from a parent Animated.Value through a piecewise-linear interpolation.
|
|
66
|
+
* config = { inputRange, outputRange, extrapolate?, extrapolateLeft?, extrapolateRight? }; the
|
|
67
|
+
* extrapolate tokens ('extend' | 'clamp' | 'identity') control behavior outside the input range.
|
|
68
|
+
*/
|
|
69
|
+
export class AnimatedInterpolation {
|
|
70
|
+
constructor(parent, config) {
|
|
71
|
+
this.__animated = true;
|
|
72
|
+
this._parent = parent;
|
|
73
|
+
this._config = config;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
__bind(node, prop) {
|
|
77
|
+
NativeUI.animValueBindInterpolated(this._parent._handle, node, prop, this._config);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interpolate(config) {
|
|
81
|
+
// Chained interpolation isn't supported by the engine; re-map against the same parent.
|
|
82
|
+
return new AnimatedInterpolation(this._parent, config);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Wraps fn so it runs at most once, regardless of how many times it is invoked. */
|
|
87
|
+
function once(fn) {
|
|
88
|
+
let called = false;
|
|
89
|
+
return (arg) => {
|
|
90
|
+
if (called) return;
|
|
91
|
+
called = true;
|
|
92
|
+
if (fn) fn(arg);
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Wraps a NativeUI animation call in an object with start()/stop() (RN's animation handle shape).
|
|
98
|
+
*
|
|
99
|
+
* start(onComplete) wires the engine's completion callback through the bridge: onComplete is called
|
|
100
|
+
* with `{ finished }` when the animation ends naturally (finished: true) or is interrupted by a new
|
|
101
|
+
* animation / stop() (finished: false). `_value` is exposed so Animated.loop can reset it between
|
|
102
|
+
* iterations.
|
|
103
|
+
*/
|
|
104
|
+
function makeAnimation(value, toValue, config) {
|
|
105
|
+
let handle = 0;
|
|
106
|
+
return {
|
|
107
|
+
_value: value,
|
|
108
|
+
start(onComplete) {
|
|
109
|
+
// Always pass a wrapper so we can null out the (possibly recycled) handle on completion.
|
|
110
|
+
const cb = (finished) => {
|
|
111
|
+
handle = 0;
|
|
112
|
+
if (onComplete) onComplete({ finished: !!finished });
|
|
113
|
+
};
|
|
114
|
+
handle = NativeUI.animValueAnimate(value._handle, toValue, config, cb);
|
|
115
|
+
},
|
|
116
|
+
stop() {
|
|
117
|
+
if (handle) NativeUI.animStop(handle);
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function timing(value, config = {}) {
|
|
123
|
+
const { toValue = 0, useNativeDriver, ...rest } = config;
|
|
124
|
+
return makeAnimation(value, toValue, { type: 'timing', ...rest });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function spring(value, config = {}) {
|
|
128
|
+
const { toValue = 0, useNativeDriver, ...rest } = config;
|
|
129
|
+
return makeAnimation(value, toValue, { type: 'spring', ...rest });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function decay(value, config = {}) {
|
|
133
|
+
const { useNativeDriver, ...rest } = config;
|
|
134
|
+
// Decay coasts from its velocity; the engine ignores the target, so pass the current value.
|
|
135
|
+
return makeAnimation(value, value.__getValue(), { type: 'decay', ...rest });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// --- Composition -------------------------------------------------------------------------------
|
|
139
|
+
// These are pure JS over each child animation's start()/stop() + the §2 timer globals. The engine
|
|
140
|
+
// deactivates a finished animation before firing its completion callback, so chaining the next
|
|
141
|
+
// animation from inside a completion handler is safe (no per-frame JS — each child still runs in C).
|
|
142
|
+
|
|
143
|
+
/** Runs animations one after another; each starts when the previous finishes. */
|
|
144
|
+
export function sequence(animations) {
|
|
145
|
+
let current = 0;
|
|
146
|
+
let stopped = false;
|
|
147
|
+
return {
|
|
148
|
+
start(onComplete) {
|
|
149
|
+
// Reset the per-run state so the sequence is restartable — e.g., each iteration of Animated.loop calls
|
|
150
|
+
// start() again. Without this, `current` stays at animations.length from the previous run, so the next
|
|
151
|
+
// start() completes instantly and the loop re-enters synchronously → stack overflow.
|
|
152
|
+
current = 0;
|
|
153
|
+
stopped = false;
|
|
154
|
+
const done = once(onComplete);
|
|
155
|
+
const next = (result) => {
|
|
156
|
+
if (stopped || !result || result.finished === false) {
|
|
157
|
+
done({ finished: false });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (current >= animations.length) {
|
|
161
|
+
done({ finished: true });
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
animations[current++].start(next);
|
|
165
|
+
};
|
|
166
|
+
next({ finished: true }); // kick off the first entry
|
|
167
|
+
},
|
|
168
|
+
stop() {
|
|
169
|
+
stopped = true;
|
|
170
|
+
if (current > 0 && current <= animations.length) animations[current - 1].stop();
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Runs animations simultaneously; completes when all finish. stopTogether (default true). */
|
|
176
|
+
export function parallel(animations, config) {
|
|
177
|
+
const stopTogether = !config || config.stopTogether !== false;
|
|
178
|
+
let stopped = false;
|
|
179
|
+
return {
|
|
180
|
+
start(onComplete) {
|
|
181
|
+
const done = once(onComplete);
|
|
182
|
+
const total = animations.length;
|
|
183
|
+
if (total === 0) {
|
|
184
|
+
done({ finished: true });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
let doneCount = 0;
|
|
188
|
+
let anyUnfinished = false;
|
|
189
|
+
const onChild = (result) => {
|
|
190
|
+
doneCount++;
|
|
191
|
+
if (!result || result.finished === false) {
|
|
192
|
+
anyUnfinished = true;
|
|
193
|
+
if (stopTogether && !stopped) {
|
|
194
|
+
stopped = true;
|
|
195
|
+
for (const a of animations) a.stop();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (doneCount >= total) done({ finished: !anyUnfinished });
|
|
199
|
+
};
|
|
200
|
+
for (const a of animations) a.start(onChild);
|
|
201
|
+
},
|
|
202
|
+
stop() {
|
|
203
|
+
stopped = true;
|
|
204
|
+
for (const a of animations) a.stop();
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Like parallel, but each animation starts `delay` ms after the previous one (uses setTimeout). */
|
|
210
|
+
export function stagger(delay, animations) {
|
|
211
|
+
let stopped = false;
|
|
212
|
+
const timers = [];
|
|
213
|
+
return {
|
|
214
|
+
start(onComplete) {
|
|
215
|
+
const done = once(onComplete);
|
|
216
|
+
const total = animations.length;
|
|
217
|
+
if (total === 0) {
|
|
218
|
+
done({ finished: true });
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
let doneCount = 0;
|
|
222
|
+
let anyUnfinished = false;
|
|
223
|
+
const onChild = (result) => {
|
|
224
|
+
doneCount++;
|
|
225
|
+
if (!result || result.finished === false) anyUnfinished = true;
|
|
226
|
+
if (doneCount >= total) done({ finished: !anyUnfinished });
|
|
227
|
+
};
|
|
228
|
+
animations.forEach((a, i) => {
|
|
229
|
+
if (i === 0) {
|
|
230
|
+
a.start(onChild);
|
|
231
|
+
} else {
|
|
232
|
+
timers.push(
|
|
233
|
+
setTimeout(() => {
|
|
234
|
+
if (!stopped) a.start(onChild);
|
|
235
|
+
}, delay * i),
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
stop() {
|
|
241
|
+
stopped = true;
|
|
242
|
+
for (const t of timers) clearTimeout(t);
|
|
243
|
+
for (const a of animations) a.stop();
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** An animation that simply waits `time` ms — useful as a spacer inside sequence (uses setTimeout). */
|
|
249
|
+
export function delay(time) {
|
|
250
|
+
let timer = 0;
|
|
251
|
+
return {
|
|
252
|
+
start(onComplete) {
|
|
253
|
+
const done = once(onComplete);
|
|
254
|
+
timer = setTimeout(() => {
|
|
255
|
+
timer = 0;
|
|
256
|
+
done({ finished: true });
|
|
257
|
+
}, time);
|
|
258
|
+
},
|
|
259
|
+
stop() {
|
|
260
|
+
if (timer) {
|
|
261
|
+
clearTimeout(timer);
|
|
262
|
+
timer = 0;
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Repeats an animation. iterations: -1 (default) loops forever; otherwise that many times.
|
|
270
|
+
* By default the underlying value is reset to its loop-start position before each iteration
|
|
271
|
+
* (resetBeforeIteration), matching RN — so a timing 0→1 repeats 0→1 rather than ping-ponging.
|
|
272
|
+
*/
|
|
273
|
+
export function loop(animation, config) {
|
|
274
|
+
const iterations = config && Number.isInteger(config.iterations) ? config.iterations : -1;
|
|
275
|
+
const resetBeforeIteration = !config || config.resetBeforeIteration !== false;
|
|
276
|
+
let stopped = false;
|
|
277
|
+
return {
|
|
278
|
+
_value: animation._value,
|
|
279
|
+
start(onComplete) {
|
|
280
|
+
const done = once(onComplete);
|
|
281
|
+
const startValue = resetBeforeIteration && animation._value ? animation._value.__getValue() : null;
|
|
282
|
+
let count = 0;
|
|
283
|
+
const run = (result) => {
|
|
284
|
+
if (stopped || !result || result.finished === false) {
|
|
285
|
+
done({ finished: false });
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (iterations >= 0 && count >= iterations) {
|
|
289
|
+
done({ finished: true });
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
count++;
|
|
293
|
+
if (resetBeforeIteration && animation._value && startValue != null && count > 1) {
|
|
294
|
+
animation._value.setValue(startValue);
|
|
295
|
+
}
|
|
296
|
+
animation.start(run);
|
|
297
|
+
};
|
|
298
|
+
run({ finished: true });
|
|
299
|
+
},
|
|
300
|
+
stop() {
|
|
301
|
+
stopped = true;
|
|
302
|
+
animation.stop();
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Returns a stable Animated.Value that persists across renders (the analog of
|
|
309
|
+
* `useRef(new Animated.Value(initial)).current`), and frees the engine-side value slot when the
|
|
310
|
+
* component unmounts. The unmount cleanup matters here in a way it doesn't in React Native: the
|
|
311
|
+
* engine's value pool is fixed-size, so a value owned by a mounting/unmounting component (e.g., a list
|
|
312
|
+
* row or a tab screen) would otherwise leak a slot on every unmount.
|
|
313
|
+
*/
|
|
314
|
+
export function useAnimatedValue(initial = 0) {
|
|
315
|
+
const ref = useRef(null);
|
|
316
|
+
if (ref.current == null) {
|
|
317
|
+
ref.current = new AnimatedValue(initial);
|
|
318
|
+
}
|
|
319
|
+
// Empty deps → the cleanup runs only on unmount. `initial` is read once on the first render (matching
|
|
320
|
+
// useRef semantics); later changes don't recreate the value, as in React Native.
|
|
321
|
+
useEffect(() => {
|
|
322
|
+
const value = ref.current;
|
|
323
|
+
return () => value.destroy();
|
|
324
|
+
}, []);
|
|
325
|
+
return ref.current;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Wraps a host component so animated values in its `style` are bound to the node (native driver).
|
|
330
|
+
*/
|
|
331
|
+
export function createAnimatedComponent(Component) {
|
|
332
|
+
return function AnimatedComponent(props) {
|
|
333
|
+
const { style, ...rest } = props;
|
|
334
|
+
const { staticStyle, bindings } = splitAnimatedStyle(style);
|
|
335
|
+
const ref = (node) => {
|
|
336
|
+
if (node == null) return; // unmount
|
|
337
|
+
for (const b of bindings) b.value.__bind(node, b.prop);
|
|
338
|
+
};
|
|
339
|
+
return createElement(Component, { ...rest, style: staticStyle, ref });
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export const Animated = {
|
|
344
|
+
Value: AnimatedValue,
|
|
345
|
+
View: createAnimatedComponent('View'),
|
|
346
|
+
Text: createAnimatedComponent('Text'),
|
|
347
|
+
Image: createAnimatedComponent('Image'),
|
|
348
|
+
timing,
|
|
349
|
+
spring,
|
|
350
|
+
decay,
|
|
351
|
+
sequence,
|
|
352
|
+
parallel,
|
|
353
|
+
stagger,
|
|
354
|
+
delay,
|
|
355
|
+
loop,
|
|
356
|
+
createAnimatedComponent,
|
|
357
|
+
};
|