@tamagui/animations-react-native 2.7.7 → 3.0.0-beta.637.1

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.
@@ -3,522 +3,582 @@ import { isWeb, useIsomorphicLayoutEffect } from "@tamagui/constants";
3
3
  import { ResetPresence, usePresence } from "@tamagui/use-presence";
4
4
  import { useEvent, useThemeWithState } from "@tamagui/web";
5
5
  import React from "react";
6
- import { Animated } from "react-native-web";
6
+ import { Animated, processColor } from "react-native-web";
7
+
7
8
  const isFabric = !isWeb && typeof global !== "undefined" && !!global.__nativeFabricUIManager;
8
9
  const resolveDynamicValue = (value, isDark) => {
9
- if (value && typeof value === "object" && "dynamic" in value) {
10
- const dynamicValue = isDark ? value.dynamic.dark : value.dynamic.light;
11
- return dynamicValue;
12
- }
13
- return value;
10
+ if (value && typeof value === "object" && "dynamic" in value) {
11
+ const dynamicValue = isDark ? value.dynamic.dark : value.dynamic.light;
12
+ return dynamicValue;
13
+ }
14
+ return value;
14
15
  };
15
16
  const animatedStyleKey = {
16
- transform: true,
17
- opacity: true
17
+ transform: true,
18
+ opacity: true
18
19
  };
19
20
  const colorStyleKey = {
20
- backgroundColor: true,
21
- color: true,
22
- borderColor: true,
23
- borderLeftColor: true,
24
- borderRightColor: true,
25
- borderTopColor: true,
26
- borderBottomColor: true
21
+ backgroundColor: true,
22
+ color: true,
23
+ borderColor: true,
24
+ borderLeftColor: true,
25
+ borderRightColor: true,
26
+ borderTopColor: true,
27
+ borderBottomColor: true
27
28
  };
28
29
  const layoutStyleKey = {
29
- height: true,
30
- width: true,
31
- minHeight: true,
32
- maxHeight: true,
33
- minWidth: true,
34
- maxWidth: true
30
+ height: true,
31
+ width: true,
32
+ minHeight: true,
33
+ maxHeight: true,
34
+ minWidth: true,
35
+ maxWidth: true
35
36
  };
36
37
  function hasAnimatedLayoutKey(style, isDark, animateOnly) {
37
- for (const key in layoutStyleKey) {
38
- if (animateOnly && !animateOnly.includes(key)) continue;
39
- if (typeof resolveDynamicValue(style[key], isDark) === "number") return true;
40
- }
41
- return false;
38
+ for (const key in layoutStyleKey) {
39
+ if (animateOnly && !animateOnly.includes(key)) continue;
40
+ if (typeof resolveDynamicValue(style[key], isDark) === "number") return true;
41
+ }
42
+ return false;
42
43
  }
43
44
  function isAnimatableColor(value) {
44
- if (typeof value !== "string") return false;
45
- if (value === "") return false;
46
- if (value.includes("var(") || value.includes("calc(")) return false;
47
- return true;
45
+ return typeof value === "string" && processColor(value) != null;
48
46
  }
49
47
  const costlyToAnimateStyleKey = {
50
- borderRadius: true,
51
- borderTopLeftRadius: true,
52
- borderTopRightRadius: true,
53
- borderBottomLeftRadius: true,
54
- borderBottomRightRadius: true,
55
- borderWidth: true,
56
- borderLeftWidth: true,
57
- borderRightWidth: true,
58
- borderTopWidth: true,
59
- borderBottomWidth: true,
60
- ...colorStyleKey
48
+ borderRadius: true,
49
+ borderTopLeftRadius: true,
50
+ borderTopRightRadius: true,
51
+ borderBottomLeftRadius: true,
52
+ borderBottomRightRadius: true,
53
+ borderWidth: true,
54
+ borderLeftWidth: true,
55
+ borderRightWidth: true,
56
+ borderTopWidth: true,
57
+ borderBottomWidth: true,
58
+ ...colorStyleKey
61
59
  };
62
60
  const AnimatedView = Animated.View;
63
61
  const AnimatedText = Animated.Text;
64
62
  function useAnimatedNumber(initial) {
65
- const state = React.useRef(null);
66
- if (!state.current) {
67
- state.current = {
68
- composite: null,
69
- val: new Animated.Value(initial),
70
- strategy: {
71
- type: "spring"
72
- }
73
- };
74
- }
75
- return {
76
- getInstance() {
77
- return state.current.val;
78
- },
79
- getValue() {
80
- return state.current.val["_value"];
81
- },
82
- stop() {
83
- state.current.composite?.stop();
84
- state.current.composite = null;
85
- },
86
- setValue(next, {
87
- type,
88
- ...config
89
- } = {
90
- type: "spring"
91
- }, onFinish) {
92
- const val = state.current.val;
93
- const handleFinish = onFinish ? ({
94
- finished
95
- }) => finished ? onFinish() : null : void 0;
96
- if (type === "direct") {
97
- val.setValue(next);
98
- } else if (type === "spring") {
99
- state.current.composite?.stop();
100
- const composite = Animated.spring(val, {
101
- ...config,
102
- toValue: next,
103
- useNativeDriver: isFabric
104
- });
105
- composite.start(handleFinish);
106
- state.current.composite = composite;
107
- } else {
108
- state.current.composite?.stop();
109
- const composite = Animated.timing(val, {
110
- ...config,
111
- toValue: next,
112
- useNativeDriver: isFabric
113
- });
114
- composite.start(handleFinish);
115
- state.current.composite = composite;
116
- }
117
- }
118
- };
63
+ const state = React.useRef(null);
64
+ if (!state.current) {
65
+ state.current = {
66
+ composite: null,
67
+ val: new Animated.Value(initial),
68
+ strategy: { type: "spring" }
69
+ };
70
+ }
71
+ return {
72
+ getInstance() {
73
+ return state.current.val;
74
+ },
75
+ getValue() {
76
+ return state.current.val["_value"];
77
+ },
78
+ stop() {
79
+ state.current.composite?.stop();
80
+ state.current.composite = null;
81
+ },
82
+ setValue(next, { type, ...config } = { type: "spring" }, onFinish) {
83
+ const val = state.current.val;
84
+ const handleFinish = onFinish ? ({ finished }) => finished ? onFinish() : null : void 0;
85
+ if (type === "direct") {
86
+ val.setValue(next);
87
+ } else if (type === "spring") {
88
+ state.current.composite?.stop();
89
+ const composite = Animated.spring(val, {
90
+ ...config,
91
+ toValue: next,
92
+ useNativeDriver: isFabric
93
+ });
94
+ composite.start(handleFinish);
95
+ state.current.composite = composite;
96
+ } else {
97
+ state.current.composite?.stop();
98
+ const composite = Animated.timing(val, {
99
+ ...config,
100
+ toValue: next,
101
+ useNativeDriver: isFabric
102
+ });
103
+ composite.start(handleFinish);
104
+ state.current.composite = composite;
105
+ }
106
+ }
107
+ };
119
108
  }
120
- const useAnimatedNumberReaction = ({
121
- value
122
- }, onValue) => {
123
- const onChange = useEvent(current => {
124
- onValue(current.value);
125
- });
126
- React.useEffect(() => {
127
- const id = value.getInstance().addListener(onChange);
128
- return () => {
129
- value.getInstance().removeListener(id);
130
- };
131
- }, [value, onChange]);
109
+ const useAnimatedNumberReaction = ({ value }, onValue) => {
110
+ const onChange = useEvent((current) => {
111
+ onValue(current.value);
112
+ });
113
+ React.useEffect(() => {
114
+ const id = value.getInstance().addListener(onChange);
115
+ return () => {
116
+ value.getInstance().removeListener(id);
117
+ };
118
+ }, [value, onChange]);
132
119
  };
133
120
  const useAnimatedNumberStyle = (value, getStyle) => {
134
- return getStyle(value.getInstance());
121
+ const instance = value.getInstance();
122
+ const animatedStyle = getStyle(instance);
123
+ const usesAnimatedNode = hasAnimatedNode(animatedStyle);
124
+ const [current, setCurrent] = React.useState(value.getValue());
125
+ React.useEffect(() => {
126
+ if (usesAnimatedNode) return;
127
+ const id = instance.addListener(({ value: next }) => {
128
+ setCurrent(next);
129
+ });
130
+ return () => {
131
+ instance.removeListener(id);
132
+ };
133
+ }, [instance, usesAnimatedNode]);
134
+ return usesAnimatedNode ? animatedStyle : getStyle(current);
135
135
  };
136
+ function hasAnimatedNode(value) {
137
+ if (!value || typeof value !== "object") return false;
138
+ if (typeof value.__getValue === "function") return true;
139
+ if (Array.isArray(value)) return value.some(hasAnimatedNode);
140
+ return Object.values(value).some(hasAnimatedNode);
141
+ }
136
142
  const useAnimatedNumbersStyle = (vals, getStyle) => {
137
- return getStyle(...vals.map(v => v.getInstance()));
143
+ return getStyle(...vals.map((v) => v.getInstance()));
138
144
  };
139
145
  function createAnimations(animations, options) {
140
- const nativeDriver = options?.useNativeDriver ?? isFabric;
141
- return {
142
- isReactNative: true,
143
- inputStyle: "value",
144
- outputStyle: "inline",
145
- avoidReRenders: true,
146
- animations,
147
- needsCustomComponent: true,
148
- View: AnimatedView,
149
- Text: AnimatedText,
150
- useAnimatedNumber,
151
- useAnimatedNumberReaction,
152
- useAnimatedNumberStyle,
153
- useAnimatedNumbersStyle,
154
- usePresence,
155
- ResetPresence,
156
- useAnimations: ({
157
- props,
158
- onDidAnimate,
159
- style,
160
- componentState,
161
- presence,
162
- stateRef,
163
- useStyleEmitter
164
- }) => {
165
- const isDisabled = isWeb && componentState.unmounted === true;
166
- const isExiting = presence?.[0] === false;
167
- const sendExitComplete = presence?.[1];
168
- const [, themeState] = useThemeWithState({});
169
- const isDark = themeState?.scheme === "dark" || themeState?.name?.startsWith("dark");
170
- const animateStyles = React.useRef({});
171
- const animatedTranforms = React.useRef([]);
172
- const animationsState = React.useRef(/* @__PURE__ */new WeakMap());
173
- const pseudoActiveRef = React.useRef(false);
174
- const exitCycleIdRef = React.useRef(0);
175
- const exitCompletedRef = React.useRef(false);
176
- const wasExitingRef = React.useRef(false);
177
- const justStartedExiting = isExiting && !wasExitingRef.current;
178
- const justStoppedExiting = !isExiting && wasExitingRef.current;
179
- if (justStartedExiting) {
180
- exitCycleIdRef.current++;
181
- exitCompletedRef.current = false;
182
- }
183
- if (justStoppedExiting) {
184
- exitCycleIdRef.current++;
185
- }
186
- const animateOnly = props.animateOnly || [];
187
- const hasTransitionOnly = !!props.animateOnly;
188
- const isEntering = !!componentState.unmounted;
189
- const wasEnteringRef = React.useRef(isEntering);
190
- const justFinishedEntering = wasEnteringRef.current && !isEntering;
191
- React.useEffect(() => {
192
- wasEnteringRef.current = isEntering;
193
- });
194
- const args = [JSON.stringify(style), componentState, isExiting, !!onDidAnimate, isDark, justFinishedEntering, hasTransitionOnly];
195
- const res = React.useMemo(() => {
196
- const runners = [];
197
- const completions = [];
198
- const animationState = isExiting ? "exit" : isEntering || justFinishedEntering ? "enter" : "default";
199
- const nonAnimatedStyle = {};
200
- const useNativeDriverForNode = nativeDriver && !hasAnimatedLayoutKey(style, isDark, hasTransitionOnly ? animateOnly : void 0);
201
- const seenAnimateKeys = /* @__PURE__ */new Set();
202
- let sawTransform = false;
203
- let transformCount = 0;
204
- for (const key in style) {
205
- const rawVal = style[key];
206
- const val = resolveDynamicValue(rawVal, isDark);
207
- if (val === void 0) continue;
208
- if (isDisabled) {
209
- continue;
210
- }
211
- if (animatedStyleKey[key] == null && !costlyToAnimateStyleKey[key] && !layoutStyleKey[key]) {
212
- nonAnimatedStyle[key] = val;
213
- continue;
214
- }
215
- if (hasTransitionOnly && !animateOnly.includes(key)) {
216
- nonAnimatedStyle[key] = val;
217
- continue;
218
- }
219
- if (layoutStyleKey[key] && typeof val !== "number") {
220
- nonAnimatedStyle[key] = val;
221
- continue;
222
- }
223
- if (colorStyleKey[key] && !isAnimatableColor(val)) {
224
- nonAnimatedStyle[key] = val;
225
- continue;
226
- }
227
- if (key !== "transform") {
228
- animateStyles.current[key] = update(key, animateStyles.current[key], val);
229
- seenAnimateKeys.add(key);
230
- continue;
231
- }
232
- if (!val) continue;
233
- if (typeof val === "string") {
234
- console.warn(`Warning: Tamagui can't animate string transforms yet!`);
235
- continue;
236
- }
237
- sawTransform = true;
238
- for (const transform of val) {
239
- if (!transform) continue;
240
- const index = transformCount++;
241
- const tkey = Object.keys(transform)[0];
242
- const currentTransform = animatedTranforms.current[index]?.[tkey];
243
- animatedTranforms.current[index] = {
244
- [tkey]: update(tkey, currentTransform, transform[tkey])
245
- };
246
- animatedTranforms.current = [...animatedTranforms.current];
247
- }
248
- }
249
- if (!isExiting && !isDisabled && !pseudoActiveRef.current) {
250
- for (const k in animateStyles.current) {
251
- if (!seenAnimateKeys.has(k)) delete animateStyles.current[k];
252
- }
253
- if (!sawTransform) {
254
- if (animatedTranforms.current.length) animatedTranforms.current = [];
255
- } else if (animatedTranforms.current.length > transformCount) {
256
- animatedTranforms.current = animatedTranforms.current.slice(0, transformCount);
257
- }
258
- }
259
- const animatedTransformStyle = animatedTranforms.current.length > 0 ? {
260
- transform: animatedTranforms.current.map(r => {
261
- const key = Object.keys(r)[0];
262
- const val = animationsState.current.get(r[key])?.interpolation || r[key];
263
- return {
264
- [key]: val
265
- };
266
- })
267
- } : {};
268
- const animatedStyle = {
269
- ...Object.fromEntries(Object.entries(animateStyles.current).map(([k, v]) => [k, animationsState.current.get(v)?.interpolation || v])),
270
- ...animatedTransformStyle
271
- };
272
- return {
273
- runners,
274
- completions,
275
- style: [nonAnimatedStyle, animatedStyle]
276
- };
277
- function update(key, animated, valIn) {
278
- const isColorStyleKey = colorStyleKey[key];
279
- const [val, type] = isColorStyleKey ? [0, void 0] : getValue(valIn);
280
- let animateToValue = val;
281
- const value = animated || new Animated.Value(val);
282
- const curInterpolation = animationsState.current.get(value);
283
- let interpolateArgs;
284
- if (type) {
285
- interpolateArgs = getInterpolated(curInterpolation?.current ?? value["_value"], val, type);
286
- animationsState.current.set(value, {
287
- interpolation: value.interpolate(interpolateArgs),
288
- current: val
289
- });
290
- }
291
- if (isColorStyleKey) {
292
- animateToValue = curInterpolation?.animateToValue ? 0 : 1;
293
- interpolateArgs = getColorInterpolated(curInterpolation?.current,
294
- // valIn is the next color
295
- valIn, animateToValue);
296
- animationsState.current.set(value, {
297
- current: valIn,
298
- interpolation: value.interpolate(interpolateArgs),
299
- animateToValue: curInterpolation?.animateToValue ? 0 : 1
300
- });
301
- }
302
- if (value) {
303
- const animationConfig = getAnimationConfig(key, animations, props.transition, animationState);
304
- let resolve;
305
- const promise = new Promise(res2 => {
306
- resolve = res2;
307
- });
308
- completions.push(promise);
309
- runners.push(() => {
310
- value.stopAnimation();
311
- function getAnimation() {
312
- return Animated[animationConfig.type || "spring"](value, {
313
- toValue: animateToValue,
314
- ...animationConfig,
315
- useNativeDriver: useNativeDriverForNode
316
- });
317
- }
318
- const animation = animationConfig.delay ? Animated.sequence([Animated.delay(animationConfig.delay), getAnimation()]) : getAnimation();
319
- animation.start(({
320
- finished
321
- }) => {
322
- if (finished || isExiting) {
323
- resolve();
324
- }
325
- });
326
- });
327
- }
328
- if (process.env.NODE_ENV === "development") {
329
- if (props["debug"] === "verbose") {
330
- console.info(" \u{1F4A0} animate", key, `from (${value["_value"]}) to`, valIn, `(${val})`, "type", type, "interpolate", interpolateArgs);
331
- }
332
- }
333
- return value;
334
- }
335
- }, args);
336
- React.useEffect(() => {
337
- wasExitingRef.current = isExiting;
338
- });
339
- useIsomorphicLayoutEffect(() => {
340
- res.runners.forEach(r => r());
341
- const cycleId = exitCycleIdRef.current;
342
- if (res.completions.length === 0) {
343
- onDidAnimate?.();
344
- if (isExiting && !exitCompletedRef.current) {
345
- exitCompletedRef.current = true;
346
- sendExitComplete?.();
347
- }
348
- return;
349
- }
350
- let cancel = false;
351
- Promise.all(res.completions).then(() => {
352
- if (cancel) return;
353
- if (isExiting && cycleId !== exitCycleIdRef.current) return;
354
- if (isExiting && exitCompletedRef.current) return;
355
- onDidAnimate?.();
356
- if (isExiting) {
357
- exitCompletedRef.current = true;
358
- sendExitComplete?.();
359
- }
360
- });
361
- return () => {
362
- cancel = true;
363
- };
364
- }, args);
365
- useStyleEmitter?.((nextStyle, _effectiveTransition, pseudoActive) => {
366
- pseudoActiveRef.current = pseudoActive === true;
367
- const runners = [];
368
- const seenAnimateKeys = /* @__PURE__ */new Set();
369
- let transformCount = 0;
370
- let animatedShapeChanged = false;
371
- const useNativeDriverForNode = nativeDriver && !hasAnimatedLayoutKey(nextStyle, isDark) && !Object.keys(animateStyles.current).some(key => layoutStyleKey[key]);
372
- for (const key in nextStyle) {
373
- const rawVal = nextStyle[key];
374
- const val = resolveDynamicValue(rawVal, isDark);
375
- if (val === void 0) continue;
376
- if (key === "transform" && Array.isArray(val)) {
377
- for (const transform of val) {
378
- if (!transform) continue;
379
- const index = transformCount++;
380
- const tkey = Object.keys(transform)[0];
381
- const currentTransform = animatedTranforms.current[index]?.[tkey];
382
- if (!currentTransform) animatedShapeChanged = true;
383
- animatedTranforms.current[index] = {
384
- [tkey]: update(tkey, currentTransform, transform[tkey])
385
- };
386
- }
387
- } else if (animatedStyleKey[key] != null || costlyToAnimateStyleKey[key] || layoutStyleKey[key]) {
388
- if (layoutStyleKey[key] && typeof val !== "number") continue;
389
- if (colorStyleKey[key] && !isAnimatableColor(val)) continue;
390
- if (!animateStyles.current[key]) animatedShapeChanged = true;
391
- animateStyles.current[key] = update(key, animateStyles.current[key], val);
392
- seenAnimateKeys.add(key);
393
- }
394
- }
395
- for (const key in animateStyles.current) {
396
- if (!seenAnimateKeys.has(key)) {
397
- delete animateStyles.current[key];
398
- animatedShapeChanged = true;
399
- }
400
- }
401
- if (animatedTranforms.current.length > transformCount) {
402
- animatedTranforms.current = animatedTranforms.current.slice(0, transformCount);
403
- animatedShapeChanged = true;
404
- }
405
- runners.forEach(r => r());
406
- if (animatedShapeChanged && stateRef.current.nextState) {
407
- stateRef.current.baseSetStateShallow?.(stateRef.current.nextState);
408
- }
409
- function update(key, animated, valIn) {
410
- const isColor = colorStyleKey[key];
411
- const [numVal, type] = isColor ? [0, void 0] : getValue(valIn);
412
- let animateToValue = numVal;
413
- const value = animated || new Animated.Value(numVal);
414
- const curInterpolation = animationsState.current.get(value);
415
- if (type) {
416
- animationsState.current.set(value, {
417
- interpolation: value.interpolate(getInterpolated(curInterpolation?.current ?? value["_value"], numVal, type)),
418
- current: numVal
419
- });
420
- }
421
- if (isColor) {
422
- animateToValue = curInterpolation?.animateToValue ? 0 : 1;
423
- animationsState.current.set(value, {
424
- current: valIn,
425
- interpolation: value.interpolate(getColorInterpolated(curInterpolation?.current, valIn, animateToValue)),
426
- animateToValue: curInterpolation?.animateToValue ? 0 : 1
427
- });
428
- }
429
- const animationConfig = getAnimationConfig(key, animations, props.transition, "default");
430
- runners.push(() => {
431
- value.stopAnimation();
432
- const anim = Animated[animationConfig.type || "spring"](value, {
433
- toValue: animateToValue,
434
- ...animationConfig,
435
- useNativeDriver: useNativeDriverForNode
436
- });
437
- (animationConfig.delay ? Animated.sequence([Animated.delay(animationConfig.delay), anim]) : anim).start();
438
- });
439
- return value;
440
- }
441
- });
442
- if (process.env.NODE_ENV === "development") {
443
- if (props["debug"] === "verbose") {
444
- console.info(`Animated`, {
445
- response: res,
446
- inputStyle: style,
447
- isExiting
448
- });
449
- }
450
- }
451
- return res;
452
- }
453
- };
146
+ const nativeDriver = options?.useNativeDriver ?? isFabric;
147
+ return {
148
+ isReactNative: true,
149
+ inputStyle: "value",
150
+ outputStyle: "inline",
151
+ avoidReRenders: true,
152
+ animations,
153
+ needsCustomComponent: true,
154
+ View: AnimatedView,
155
+ Text: AnimatedText,
156
+ useAnimatedNumber,
157
+ useAnimatedNumberReaction,
158
+ useAnimatedNumberStyle,
159
+ useAnimatedNumbersStyle,
160
+ usePresence,
161
+ ResetPresence,
162
+ useAnimations: ({ props, onTransition, style, componentState, presence, stateRef, useStyleEmitter }) => {
163
+ const isDisabled = isWeb && componentState.unmounted === true;
164
+ const isExiting = presence?.[0] === false;
165
+ const sendExitComplete = presence?.[1];
166
+ const onTransitionRef = React.useRef(onTransition);
167
+ onTransitionRef.current = onTransition;
168
+ const emit = (phase, cause, finished) => {
169
+ onTransitionRef.current?.(phase === "end" ? {
170
+ phase,
171
+ cause,
172
+ finished
173
+ } : {
174
+ phase,
175
+ cause
176
+ });
177
+ };
178
+ const [, themeState] = useThemeWithState({});
179
+ const isDark = themeState?.scheme === "dark" || themeState?.name?.startsWith("dark");
180
+ const animateStyles = React.useRef({});
181
+ const animatedTranforms = React.useRef([]);
182
+ const animationsState = React.useRef(/* @__PURE__ */ new WeakMap());
183
+ const pseudoActiveRef = React.useRef(false);
184
+ const exitCycleIdRef = React.useRef(0);
185
+ const exitCompletedRef = React.useRef(false);
186
+ const wasExitingRef = React.useRef(false);
187
+ const enterStartedRef = React.useRef(false);
188
+ const exitStartedRef = React.useRef(false);
189
+ const updateInFlightRef = React.useRef(false);
190
+ const updateCycleIdRef = React.useRef(0);
191
+ const prevStyleSigRef = React.useRef(null);
192
+ const justStartedExiting = isExiting && !wasExitingRef.current;
193
+ const justStoppedExiting = !isExiting && wasExitingRef.current;
194
+ if (justStartedExiting) {
195
+ exitCycleIdRef.current++;
196
+ exitCompletedRef.current = false;
197
+ }
198
+ if (justStoppedExiting) {
199
+ exitCycleIdRef.current++;
200
+ }
201
+ const animateOnly = props.animateOnly || [];
202
+ const hasTransitionOnly = !!props.animateOnly;
203
+ const isEntering = !!componentState.unmounted;
204
+ const wasEnteringRef = React.useRef(isEntering);
205
+ const justFinishedEntering = wasEnteringRef.current && !isEntering;
206
+ React.useEffect(() => {
207
+ wasEnteringRef.current = isEntering;
208
+ });
209
+ const args = [
210
+ JSON.stringify(style),
211
+ componentState,
212
+ isExiting,
213
+ !!onTransition,
214
+ isDark,
215
+ justFinishedEntering,
216
+ hasTransitionOnly
217
+ ];
218
+ const res = React.useMemo(() => {
219
+ const runners = [];
220
+ const completions = [];
221
+ const animationState = isExiting ? "exit" : isEntering || justFinishedEntering ? "enter" : "default";
222
+ const nonAnimatedStyle = {};
223
+ const useNativeDriverForNode = nativeDriver && !hasAnimatedLayoutKey(style, isDark, hasTransitionOnly ? animateOnly : void 0);
224
+ const seenAnimateKeys = /* @__PURE__ */ new Set();
225
+ let sawTransform = false;
226
+ let transformCount = 0;
227
+ for (const key in style) {
228
+ const rawVal = style[key];
229
+ const val = resolveDynamicValue(rawVal, isDark);
230
+ if (val === void 0) continue;
231
+ if (isDisabled) {
232
+ continue;
233
+ }
234
+ if (animatedStyleKey[key] == null && !costlyToAnimateStyleKey[key] && !layoutStyleKey[key]) {
235
+ nonAnimatedStyle[key] = val;
236
+ continue;
237
+ }
238
+ if (hasTransitionOnly && !animateOnly.includes(key)) {
239
+ nonAnimatedStyle[key] = val;
240
+ continue;
241
+ }
242
+ if (layoutStyleKey[key] && typeof val !== "number") {
243
+ nonAnimatedStyle[key] = val;
244
+ continue;
245
+ }
246
+ if (colorStyleKey[key] && !isAnimatableColor(val)) {
247
+ nonAnimatedStyle[key] = val;
248
+ continue;
249
+ }
250
+ if (key !== "transform") {
251
+ animateStyles.current[key] = update(key, animateStyles.current[key], val);
252
+ seenAnimateKeys.add(key);
253
+ continue;
254
+ }
255
+ if (!val) continue;
256
+ if (typeof val === "string") {
257
+ console.warn(`Warning: Tamagui can't animate string transforms yet!`);
258
+ continue;
259
+ }
260
+ sawTransform = true;
261
+ for (const transform of val) {
262
+ if (!transform) continue;
263
+ const index = transformCount++;
264
+ const tkey = Object.keys(transform)[0];
265
+ const currentTransform = animatedTranforms.current[index]?.[tkey];
266
+ animatedTranforms.current[index] = { [tkey]: update(tkey, currentTransform, transform[tkey]) };
267
+ animatedTranforms.current = [...animatedTranforms.current];
268
+ }
269
+ }
270
+ if (!isExiting && !isDisabled && !pseudoActiveRef.current) {
271
+ for (const k in animateStyles.current) {
272
+ if (!seenAnimateKeys.has(k)) delete animateStyles.current[k];
273
+ }
274
+ if (!sawTransform) {
275
+ if (animatedTranforms.current.length) animatedTranforms.current = [];
276
+ } else if (animatedTranforms.current.length > transformCount) {
277
+ animatedTranforms.current = animatedTranforms.current.slice(0, transformCount);
278
+ }
279
+ }
280
+ const animatedTransformStyle = animatedTranforms.current.length > 0 ? { transform: animatedTranforms.current.map((r) => {
281
+ const key = Object.keys(r)[0];
282
+ const val = animationsState.current.get(r[key])?.interpolation || r[key];
283
+ return { [key]: val };
284
+ }) } : {};
285
+ const animatedStyle = {
286
+ ...Object.fromEntries(Object.entries(animateStyles.current).map(([k, v]) => [k, animationsState.current.get(v)?.interpolation || v])),
287
+ ...animatedTransformStyle
288
+ };
289
+ return {
290
+ runners,
291
+ completions,
292
+ style: [nonAnimatedStyle, animatedStyle]
293
+ };
294
+ function update(key, animated, valIn) {
295
+ const isColorStyleKey = colorStyleKey[key];
296
+ const [val, type] = isColorStyleKey ? [0, void 0] : getValue(valIn);
297
+ let animateToValue = val;
298
+ const value = animated || new Animated.Value(val);
299
+ const curInterpolation = animationsState.current.get(value);
300
+ let interpolateArgs;
301
+ if (type) {
302
+ interpolateArgs = getInterpolated(curInterpolation?.current ?? value["_value"], val, type);
303
+ animationsState.current.set(value, {
304
+ interpolation: value.interpolate(interpolateArgs),
305
+ current: val
306
+ });
307
+ }
308
+ if (isColorStyleKey) {
309
+ animateToValue = curInterpolation?.animateToValue ? 0 : 1;
310
+ interpolateArgs = getColorInterpolated(curInterpolation?.current, valIn, animateToValue);
311
+ animationsState.current.set(value, {
312
+ current: valIn,
313
+ interpolation: value.interpolate(interpolateArgs),
314
+ animateToValue: curInterpolation?.animateToValue ? 0 : 1
315
+ });
316
+ }
317
+ if (value) {
318
+ const animationConfig = getAnimationConfig(key, animations, props.transition, animationState);
319
+ let resolve;
320
+ const promise = new Promise((res2) => {
321
+ resolve = res2;
322
+ });
323
+ completions.push(promise);
324
+ runners.push(() => {
325
+ value.stopAnimation();
326
+ function getAnimation() {
327
+ return Animated[animationConfig.type || "spring"](value, {
328
+ toValue: animateToValue,
329
+ ...animationConfig,
330
+ useNativeDriver: useNativeDriverForNode
331
+ });
332
+ }
333
+ const animation = animationConfig.delay ? Animated.sequence([Animated.delay(animationConfig.delay), getAnimation()]) : getAnimation();
334
+ animation.start(({ finished }) => {
335
+ if (finished || isExiting) {
336
+ resolve();
337
+ }
338
+ });
339
+ });
340
+ }
341
+ if (process.env.NODE_ENV === "development") {
342
+ if (props["debug"] === "verbose") {
343
+ console.info(" 💠 animate", key, `from (${value["_value"]}) to`, valIn, `(${val})`, "type", type, "interpolate", interpolateArgs);
344
+ }
345
+ }
346
+ return value;
347
+ }
348
+ }, args);
349
+ React.useEffect(() => {
350
+ wasExitingRef.current = isExiting;
351
+ });
352
+ useIsomorphicLayoutEffect(() => {
353
+ if (justStoppedExiting && exitStartedRef.current && !exitCompletedRef.current) {
354
+ exitStartedRef.current = false;
355
+ emit("end", "exit", false);
356
+ }
357
+ }, [justStoppedExiting]);
358
+ useIsomorphicLayoutEffect(() => {
359
+ res.runners.forEach((r) => r());
360
+ const cycleId = exitCycleIdRef.current;
361
+ const cause = isExiting ? "exit" : isEntering || justFinishedEntering ? "enter" : "update";
362
+ if (cause === "exit") {
363
+ if (enterStartedRef.current) {
364
+ enterStartedRef.current = false;
365
+ emit("end", "enter", false);
366
+ }
367
+ if (updateInFlightRef.current) {
368
+ updateInFlightRef.current = false;
369
+ updateCycleIdRef.current++;
370
+ emit("end", "update", false);
371
+ }
372
+ }
373
+ if (cause === "update") {
374
+ const sig = args[0];
375
+ if (prevStyleSigRef.current === null || prevStyleSigRef.current === sig) {
376
+ prevStyleSigRef.current = sig;
377
+ return;
378
+ }
379
+ prevStyleSigRef.current = sig;
380
+ if (res.completions.length === 0) return;
381
+ if (updateInFlightRef.current) {
382
+ emit("end", "update", false);
383
+ }
384
+ updateInFlightRef.current = true;
385
+ const uid = ++updateCycleIdRef.current;
386
+ emit("start", "update");
387
+ Promise.all(res.completions).then(() => {
388
+ if (uid !== updateCycleIdRef.current) return;
389
+ updateInFlightRef.current = false;
390
+ emit("end", "update", true);
391
+ });
392
+ return;
393
+ }
394
+ prevStyleSigRef.current = args[0];
395
+ if (res.completions.length === 0) {
396
+ emit("start", cause);
397
+ emit("end", cause, true);
398
+ if (isExiting && !exitCompletedRef.current) {
399
+ exitCompletedRef.current = true;
400
+ sendExitComplete?.();
401
+ }
402
+ return;
403
+ }
404
+ if (cause === "enter" && !enterStartedRef.current) {
405
+ enterStartedRef.current = true;
406
+ emit("start", "enter");
407
+ }
408
+ if (cause === "exit" && !exitStartedRef.current) {
409
+ exitStartedRef.current = true;
410
+ emit("start", "exit");
411
+ }
412
+ Promise.all(res.completions).then(() => {
413
+ if (isExiting && cycleId !== exitCycleIdRef.current) return;
414
+ if (isExiting && exitCompletedRef.current) return;
415
+ if (isExiting) {
416
+ if (exitStartedRef.current) {
417
+ exitStartedRef.current = false;
418
+ emit("end", "exit", true);
419
+ }
420
+ exitCompletedRef.current = true;
421
+ sendExitComplete?.();
422
+ } else if (enterStartedRef.current) {
423
+ enterStartedRef.current = false;
424
+ emit("end", "enter", true);
425
+ }
426
+ });
427
+ }, args);
428
+ useStyleEmitter?.((nextStyle, _effectiveTransition, pseudoActive) => {
429
+ pseudoActiveRef.current = pseudoActive === true;
430
+ const runners = [];
431
+ const seenAnimateKeys = /* @__PURE__ */ new Set();
432
+ let transformCount = 0;
433
+ let animatedShapeChanged = false;
434
+ const useNativeDriverForNode = nativeDriver && !hasAnimatedLayoutKey(nextStyle, isDark) && !Object.keys(animateStyles.current).some((key) => layoutStyleKey[key]);
435
+ for (const key in nextStyle) {
436
+ const rawVal = nextStyle[key];
437
+ const val = resolveDynamicValue(rawVal, isDark);
438
+ if (val === void 0) continue;
439
+ if (key === "transform" && Array.isArray(val)) {
440
+ for (const transform of val) {
441
+ if (!transform) continue;
442
+ const index = transformCount++;
443
+ const tkey = Object.keys(transform)[0];
444
+ const currentTransform = animatedTranforms.current[index]?.[tkey];
445
+ if (!currentTransform) animatedShapeChanged = true;
446
+ animatedTranforms.current[index] = { [tkey]: update(tkey, currentTransform, transform[tkey]) };
447
+ }
448
+ } else if (animatedStyleKey[key] != null || costlyToAnimateStyleKey[key] || layoutStyleKey[key]) {
449
+ if (layoutStyleKey[key] && typeof val !== "number") continue;
450
+ if (colorStyleKey[key] && !isAnimatableColor(val)) continue;
451
+ if (!animateStyles.current[key]) animatedShapeChanged = true;
452
+ animateStyles.current[key] = update(key, animateStyles.current[key], val);
453
+ seenAnimateKeys.add(key);
454
+ }
455
+ }
456
+ for (const key in animateStyles.current) {
457
+ if (!seenAnimateKeys.has(key)) {
458
+ delete animateStyles.current[key];
459
+ animatedShapeChanged = true;
460
+ }
461
+ }
462
+ if (animatedTranforms.current.length > transformCount) {
463
+ animatedTranforms.current = animatedTranforms.current.slice(0, transformCount);
464
+ animatedShapeChanged = true;
465
+ }
466
+ runners.forEach((r) => r());
467
+ if (animatedShapeChanged && stateRef.current.nextState) {
468
+ stateRef.current.baseSetStateShallow?.(stateRef.current.nextState);
469
+ }
470
+ function update(key, animated, valIn) {
471
+ const isColor = colorStyleKey[key];
472
+ const [numVal, type] = isColor ? [0, void 0] : getValue(valIn);
473
+ let animateToValue = numVal;
474
+ const value = animated || new Animated.Value(numVal);
475
+ const curInterpolation = animationsState.current.get(value);
476
+ if (type) {
477
+ animationsState.current.set(value, {
478
+ interpolation: value.interpolate(getInterpolated(curInterpolation?.current ?? value["_value"], numVal, type)),
479
+ current: numVal
480
+ });
481
+ }
482
+ if (isColor) {
483
+ animateToValue = curInterpolation?.animateToValue ? 0 : 1;
484
+ animationsState.current.set(value, {
485
+ current: valIn,
486
+ interpolation: value.interpolate(getColorInterpolated(curInterpolation?.current, valIn, animateToValue)),
487
+ animateToValue: curInterpolation?.animateToValue ? 0 : 1
488
+ });
489
+ }
490
+ const animationConfig = getAnimationConfig(key, animations, props.transition, "default");
491
+ runners.push(() => {
492
+ value.stopAnimation();
493
+ const anim = Animated[animationConfig.type || "spring"](value, {
494
+ toValue: animateToValue,
495
+ ...animationConfig,
496
+ useNativeDriver: useNativeDriverForNode
497
+ });
498
+ (animationConfig.delay ? Animated.sequence([Animated.delay(animationConfig.delay), anim]) : anim).start();
499
+ });
500
+ return value;
501
+ }
502
+ });
503
+ if (process.env.NODE_ENV === "development") {
504
+ if (props["debug"] === "verbose") {
505
+ console.info(`Animated`, {
506
+ response: res,
507
+ inputStyle: style,
508
+ isExiting
509
+ });
510
+ }
511
+ }
512
+ return res;
513
+ }
514
+ };
454
515
  }
455
516
  function getColorInterpolated(currentColor, nextColor, animateToValue) {
456
- const inputRange = [0, 1];
457
- const outputRange = [currentColor ? currentColor : nextColor, nextColor];
458
- if (animateToValue === 0) {
459
- outputRange.reverse();
460
- }
461
- return {
462
- inputRange,
463
- outputRange
464
- };
517
+ const inputRange = [0, 1];
518
+ const outputRange = [currentColor ? currentColor : nextColor, nextColor];
519
+ if (animateToValue === 0) {
520
+ outputRange.reverse();
521
+ }
522
+ return {
523
+ inputRange,
524
+ outputRange
525
+ };
465
526
  }
466
527
  function getInterpolated(current, next, postfix = "deg") {
467
- if (next === current) {
468
- current = next - 1e-9;
469
- }
470
- const inputRange = [current, next];
471
- const outputRange = [`${current}${postfix}`, `${next}${postfix}`];
472
- if (next < current) {
473
- inputRange.reverse();
474
- outputRange.reverse();
475
- }
476
- return {
477
- inputRange,
478
- outputRange
479
- };
528
+ if (next === current) {
529
+ current = next - 1e-9;
530
+ }
531
+ const inputRange = [current, next];
532
+ const outputRange = [`${current}${postfix}`, `${next}${postfix}`];
533
+ if (next < current) {
534
+ inputRange.reverse();
535
+ outputRange.reverse();
536
+ }
537
+ return {
538
+ inputRange,
539
+ outputRange
540
+ };
480
541
  }
481
542
  function getAnimationConfig(key, animations, transition, animationState = "default") {
482
- const normalized = normalizeTransition(transition);
483
- const shortKey = transformShorthands[key];
484
- const propAnimation = normalized.properties[key] ?? normalized.properties[shortKey];
485
- let animationType = null;
486
- let extraConf = {};
487
- if (typeof propAnimation === "string") {
488
- animationType = propAnimation;
489
- } else if (propAnimation && typeof propAnimation === "object") {
490
- animationType = propAnimation.type || getEffectiveAnimation(normalized, animationState);
491
- extraConf = propAnimation;
492
- } else {
493
- animationType = getEffectiveAnimation(normalized, animationState);
494
- }
495
- if (normalized.delay && !extraConf.delay) {
496
- extraConf = {
497
- ...extraConf,
498
- delay: normalized.delay
499
- };
500
- }
501
- const found = animationType ? animations[animationType] : {};
502
- return {
503
- ...found,
504
- // Apply global spring config overrides (from transition={['bouncy', { stiffness: 1000 }]})
505
- ...normalized.config,
506
- // Property-specific config takes highest precedence
507
- ...extraConf
508
- };
543
+ const normalized = normalizeTransition(transition);
544
+ const shortKey = transformShorthands[key];
545
+ const propAnimation = normalized.properties[key] ?? normalized.properties[shortKey];
546
+ let animationType = null;
547
+ let extraConf = {};
548
+ if (typeof propAnimation === "string") {
549
+ animationType = propAnimation;
550
+ } else if (propAnimation && typeof propAnimation === "object") {
551
+ animationType = propAnimation.type || getEffectiveAnimation(normalized, animationState);
552
+ extraConf = propAnimation;
553
+ } else {
554
+ animationType = getEffectiveAnimation(normalized, animationState);
555
+ }
556
+ if (normalized.delay && !extraConf.delay) {
557
+ extraConf = {
558
+ ...extraConf,
559
+ delay: normalized.delay
560
+ };
561
+ }
562
+ const found = animationType ? animations[animationType] : {};
563
+ return {
564
+ ...found,
565
+ ...normalized.config,
566
+ ...extraConf
567
+ };
509
568
  }
510
569
  const transformShorthands = {
511
- x: "translateX",
512
- y: "translateY",
513
- translateX: "x",
514
- translateY: "y"
570
+ x: "translateX",
571
+ y: "translateY",
572
+ translateX: "x",
573
+ translateY: "y"
515
574
  };
516
575
  function getValue(input, isColor = false) {
517
- if (typeof input !== "string") {
518
- return [input];
519
- }
520
- const [_, number, after] = input.match(/([-0-9]+)(deg|%|px)/) ?? [];
521
- return [+number, after];
576
+ if (typeof input !== "string") {
577
+ return [input];
578
+ }
579
+ const [_, number, after] = input.match(/(-?(?:\d+\.?\d*|\.\d+))(deg|%|px)?/) ?? [];
580
+ return [+number, after];
522
581
  }
582
+
523
583
  export { AnimatedText, AnimatedView, createAnimations, useAnimatedNumber, useAnimatedNumberReaction, useAnimatedNumberStyle, useAnimatedNumbersStyle };
524
584
  //# sourceMappingURL=createAnimations.mjs.map