@tamagui/animations-reanimated 2.0.0-rc.9 → 2.0.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.
@@ -1,143 +1,263 @@
1
- import { normalizeTransition, getEffectiveAnimation } from "@tamagui/animation-helpers";
2
- import { getSplitStyles, hooks, isWeb, Text, useComposedRefs, useIsomorphicLayoutEffect, useThemeWithState, View } from "@tamagui/core";
1
+ import { getEffectiveAnimation, normalizeTransition } from "@tamagui/animation-helpers";
2
+ import { getSplitStyles, hooks, isWeb, Text, useComposedRefs, useEvent, useIsomorphicLayoutEffect, useThemeWithState, View } from "@tamagui/core";
3
3
  import { ResetPresence, usePresence } from "@tamagui/use-presence";
4
4
  import React, { forwardRef, useMemo, useRef } from "react";
5
- import Animated_, { cancelAnimation, runOnJS, useAnimatedReaction, useAnimatedStyle, useDerivedValue, useSharedValue, withDelay, withSpring, withTiming } from "react-native-reanimated";
5
+ import Animated_, { cancelAnimation, runOnJS, runOnUI, useAnimatedReaction, useAnimatedStyle, useDerivedValue, useSharedValue, withDelay, withSpring, withTiming } from "react-native-reanimated";
6
6
  import { jsx } from "react/jsx-runtime";
7
7
  const getDefaultExport = module => {
8
- const mod = module;
9
- return (mod.__esModule || mod[Symbol.toStringTag] === "Module") && mod.default || mod;
10
- },
11
- Animated = getDefaultExport(Animated_),
12
- resolveDynamicValue = (value, isDark) => {
13
- if (value !== null && typeof value == "object" && "dynamic" in value && typeof value.dynamic == "object") {
14
- const dynamic = value.dynamic;
15
- return isDark ? dynamic.dark : dynamic.light;
8
+ const mod = module;
9
+ if (mod.__esModule || mod[Symbol.toStringTag] === "Module") {
10
+ return mod.default || mod;
11
+ }
12
+ return mod;
13
+ };
14
+ const Animated = getDefaultExport(Animated_);
15
+ const silenceAnimatedComponentDevCheck = style => {
16
+ if (process.env.NODE_ENV !== "development" || isWeb || !style || typeof style !== "object") {
17
+ return;
18
+ }
19
+ Object.defineProperty(style, "_requiresAnimatedComponent", {
20
+ configurable: true,
21
+ enumerable: false,
22
+ value: true
23
+ });
24
+ };
25
+ const resolveDynamicValue = (value, isDark) => {
26
+ if (value !== null && typeof value === "object" && "dynamic" in value && typeof value.dynamic === "object") {
27
+ const dynamic = value.dynamic;
28
+ return isDark ? dynamic.dark : dynamic.light;
29
+ }
30
+ return value;
31
+ };
32
+ const cloneAnimationValue = value => {
33
+ if (Array.isArray(value)) {
34
+ return value.map(cloneAnimationValue);
35
+ }
36
+ if (value && typeof value === "object") {
37
+ const next = {};
38
+ for (const key in value) {
39
+ next[key] = cloneAnimationValue(value[key]);
16
40
  }
17
- return value;
18
- },
19
- applyAnimation = (targetValue, config) => {
20
- "worklet";
41
+ return next;
42
+ }
43
+ return value;
44
+ };
45
+ const cloneTransitionConfig = config => {
46
+ return cloneAnimationValue(config);
47
+ };
48
+ const createReanimatedConfig = config => {
49
+ "worklet";
21
50
 
22
- const delay = config.delay;
23
- let animatedValue;
24
- return config.type === "timing" ? animatedValue = withTiming(targetValue, config) : animatedValue = withSpring(targetValue, config), delay && delay > 0 && (animatedValue = withDelay(delay, animatedValue)), animatedValue;
25
- },
26
- ANIMATABLE_PROPERTIES = {
27
- // Transform
28
- transform: !0,
29
- // Opacity
30
- opacity: !0,
31
- // Dimensions
32
- height: !0,
33
- width: !0,
34
- minWidth: !0,
35
- minHeight: !0,
36
- maxWidth: !0,
37
- maxHeight: !0,
38
- // Background
39
- backgroundColor: !0,
40
- // Border colors
41
- borderColor: !0,
42
- borderLeftColor: !0,
43
- borderRightColor: !0,
44
- borderTopColor: !0,
45
- borderBottomColor: !0,
46
- // Border radius
47
- borderRadius: !0,
48
- borderTopLeftRadius: !0,
49
- borderTopRightRadius: !0,
50
- borderBottomLeftRadius: !0,
51
- borderBottomRightRadius: !0,
52
- // Border width
53
- borderWidth: !0,
54
- borderLeftWidth: !0,
55
- borderRightWidth: !0,
56
- borderTopWidth: !0,
57
- borderBottomWidth: !0,
58
- // Text
59
- color: !0,
60
- fontSize: !0,
61
- fontWeight: !0,
62
- lineHeight: !0,
63
- letterSpacing: !0,
64
- // Position
65
- left: !0,
66
- right: !0,
67
- top: !0,
68
- bottom: !0,
69
- // Margin
70
- margin: !0,
71
- marginTop: !0,
72
- marginBottom: !0,
73
- marginLeft: !0,
74
- marginRight: !0,
75
- marginHorizontal: !0,
76
- marginVertical: !0,
77
- // Padding
78
- padding: !0,
79
- paddingTop: !0,
80
- paddingBottom: !0,
81
- paddingLeft: !0,
82
- paddingRight: !0,
83
- paddingHorizontal: !0,
84
- paddingVertical: !0,
85
- // Flex/Gap
86
- gap: !0,
87
- rowGap: !0,
88
- columnGap: !0,
89
- flex: !0,
90
- flexGrow: !0,
91
- flexShrink: !0
92
- },
93
- canAnimateProperty = (key, value, animateOnly) => !(!ANIMATABLE_PROPERTIES[key] || value === "auto" || typeof value == "string" && value.startsWith("calc") || animateOnly && !animateOnly.includes(key));
51
+ const next = {};
52
+ const source = config;
53
+ for (const key in source) {
54
+ if (key === "type" || key === "delay") continue;
55
+ const value = source[key];
56
+ if (value !== void 0) {
57
+ next[key] = value;
58
+ }
59
+ }
60
+ return next;
61
+ };
62
+ const applyAnimation = (targetValue, config, callback) => {
63
+ "worklet";
64
+
65
+ const delay = config.delay;
66
+ const reanimatedConfig = createReanimatedConfig(config);
67
+ let animatedValue;
68
+ if (config.type === "timing") {
69
+ animatedValue = withTiming(targetValue, reanimatedConfig, callback);
70
+ } else {
71
+ animatedValue = withSpring(targetValue, reanimatedConfig, callback);
72
+ }
73
+ if (delay && delay > 0) {
74
+ animatedValue = withDelay(delay, animatedValue);
75
+ }
76
+ return animatedValue;
77
+ };
78
+ const ANIMATABLE_PROPERTIES = {
79
+ // Transform
80
+ transform: true,
81
+ // Opacity
82
+ opacity: true,
83
+ // Dimensions
84
+ height: true,
85
+ width: true,
86
+ minWidth: true,
87
+ minHeight: true,
88
+ maxWidth: true,
89
+ maxHeight: true,
90
+ // Background
91
+ backgroundColor: true,
92
+ // Border colors
93
+ borderColor: true,
94
+ borderLeftColor: true,
95
+ borderRightColor: true,
96
+ borderTopColor: true,
97
+ borderBottomColor: true,
98
+ // Border radius
99
+ borderRadius: true,
100
+ borderTopLeftRadius: true,
101
+ borderTopRightRadius: true,
102
+ borderBottomLeftRadius: true,
103
+ borderBottomRightRadius: true,
104
+ // Border width
105
+ borderWidth: true,
106
+ borderLeftWidth: true,
107
+ borderRightWidth: true,
108
+ borderTopWidth: true,
109
+ borderBottomWidth: true,
110
+ // Text
111
+ color: true,
112
+ fontSize: true,
113
+ fontWeight: true,
114
+ lineHeight: true,
115
+ letterSpacing: true,
116
+ // Position
117
+ left: true,
118
+ right: true,
119
+ top: true,
120
+ bottom: true,
121
+ // Margin
122
+ margin: true,
123
+ marginTop: true,
124
+ marginBottom: true,
125
+ marginLeft: true,
126
+ marginRight: true,
127
+ marginHorizontal: true,
128
+ marginVertical: true,
129
+ // Padding
130
+ padding: true,
131
+ paddingTop: true,
132
+ paddingBottom: true,
133
+ paddingLeft: true,
134
+ paddingRight: true,
135
+ paddingHorizontal: true,
136
+ paddingVertical: true,
137
+ // Flex/Gap
138
+ gap: true,
139
+ rowGap: true,
140
+ columnGap: true,
141
+ flex: true,
142
+ flexGrow: true,
143
+ flexShrink: true
144
+ };
145
+ const canAnimateProperty = (key, value, animateOnly) => {
146
+ if (!ANIMATABLE_PROPERTIES[key]) return false;
147
+ if (value === "auto") return false;
148
+ if (typeof value === "string" && value.startsWith("calc")) return false;
149
+ if (animateOnly && !animateOnly.includes(key)) return false;
150
+ return true;
151
+ };
94
152
  function createWebAnimatedComponent(defaultTag) {
95
- const isText = defaultTag === "span",
96
- Component = Animated.createAnimatedComponent(forwardRef((propsIn, ref) => {
97
- const {
98
- forwardedRef,
99
- render = defaultTag,
100
- ...rest
101
- } = propsIn,
102
- hostRef = useRef(null),
103
- composedRefs = useComposedRefs(forwardedRef, ref, hostRef),
104
- stateRef = useRef({
105
- get host() {
106
- return hostRef.current;
107
- }
108
- }),
109
- [, themeState] = useThemeWithState({}),
110
- viewProps = getSplitStyles(rest, isText ? Text.staticConfig : View.staticConfig, themeState?.theme ?? {}, themeState?.name ?? "", {
111
- unmounted: !1
112
- }, {
113
- isAnimated: !1,
114
- noClass: !0
115
- })?.viewProps ?? {},
116
- Element = render,
117
- transformedProps = hooks.usePropsTransform?.(render, viewProps, stateRef, !1);
118
- return /* @__PURE__ */jsx(Element, {
119
- ...transformedProps,
120
- ref: composedRefs
153
+ const isText = defaultTag === "span";
154
+ const Component = Animated.createAnimatedComponent(forwardRef((propsIn, ref) => {
155
+ const {
156
+ forwardedRef,
157
+ render = defaultTag,
158
+ ...rest
159
+ } = propsIn;
160
+ const hostRef = useRef(null);
161
+ const composedRefs = useComposedRefs(forwardedRef, ref, hostRef);
162
+ const stateRef = useRef({
163
+ get host() {
164
+ return hostRef.current;
165
+ }
166
+ });
167
+ const [, themeState] = useThemeWithState({});
168
+ const result = getSplitStyles(rest, isText ? Text.staticConfig : View.staticConfig, themeState?.theme ?? {}, themeState?.name ?? "", {
169
+ unmounted: false
170
+ }, {
171
+ isAnimated: false,
172
+ noClass: true
173
+ });
174
+ const viewProps = result?.viewProps ?? {};
175
+ const Element = render;
176
+ const transformedProps = hooks.usePropsTransform?.(render, viewProps, stateRef, false);
177
+ return /* @__PURE__ */jsx(Element, {
178
+ ...transformedProps,
179
+ ref: composedRefs
180
+ });
181
+ }));
182
+ Component.acceptRenderProp = true;
183
+ return Component;
184
+ }
185
+ const AnimatedView = createWebAnimatedComponent("div");
186
+ const AnimatedText = createWebAnimatedComponent("span");
187
+ function buildTransitionConfig(transition, animations, animationState, styleKeys) {
188
+ const normalized = normalizeTransition(transition);
189
+ const effectiveKey = getEffectiveAnimation(normalized, animationState);
190
+ let base = cloneTransitionConfig(effectiveKey ? animations[effectiveKey] ?? {
191
+ type: "spring"
192
+ } : {
193
+ type: "spring"
194
+ });
195
+ if (normalized.delay) {
196
+ base = cloneTransitionConfig({
197
+ ...base,
198
+ delay: normalized.delay
199
+ });
200
+ }
201
+ if (normalized.config) {
202
+ base = cloneTransitionConfig({
203
+ ...base,
204
+ ...normalized.config
205
+ });
206
+ if (base.type !== "timing" && normalized.config.duration !== void 0 && normalized.config.damping === void 0 && normalized.config.stiffness === void 0 && normalized.config.mass === void 0) {
207
+ base = cloneTransitionConfig({
208
+ ...base,
209
+ type: "timing"
210
+ });
211
+ }
212
+ }
213
+ const propertyConfigs = {};
214
+ for (const key of styleKeys) {
215
+ const propAnimation = normalized.properties[key];
216
+ if (typeof propAnimation === "string") {
217
+ propertyConfigs[key] = cloneTransitionConfig(animations[propAnimation] ?? base);
218
+ } else if (propAnimation && typeof propAnimation === "object") {
219
+ const configType = propAnimation.type;
220
+ const baseForProp = configType ? animations[configType] ?? base : base;
221
+ propertyConfigs[key] = cloneTransitionConfig({
222
+ ...baseForProp,
223
+ ...propAnimation
121
224
  });
122
- }));
123
- return Component.acceptTagProp = !0, Component;
225
+ } else {
226
+ propertyConfigs[key] = cloneTransitionConfig(base);
227
+ }
228
+ }
229
+ return {
230
+ baseConfig: base,
231
+ propertyConfigs
232
+ };
233
+ }
234
+ function getStyleKeys(style) {
235
+ const keys = new Set(Object.keys(style));
236
+ if (style.transform && Array.isArray(style.transform)) {
237
+ for (const t of style.transform) {
238
+ if (t && typeof t === "object") {
239
+ keys.add(Object.keys(t)[0]);
240
+ }
241
+ }
242
+ }
243
+ return keys;
124
244
  }
125
- const AnimatedView = createWebAnimatedComponent("div"),
126
- AnimatedText = createWebAnimatedComponent("span");
127
245
  function createAnimations(animationsConfig) {
128
246
  const animations = {};
129
- for (const key in animationsConfig) animations[key] = {
130
- type: "spring",
131
- ...animationsConfig[key]
132
- };
247
+ for (const key in animationsConfig) {
248
+ animations[key] = cloneTransitionConfig({
249
+ type: "spring",
250
+ ...animationsConfig[key]
251
+ });
252
+ }
133
253
  return {
254
+ needsCustomComponent: true,
134
255
  View: isWeb ? AnimatedView : Animated.View,
135
256
  Text: isWeb ? AnimatedText : Animated.Text,
136
- isReactNative: !0,
137
- supportsCSS: !1,
257
+ isReactNative: true,
138
258
  inputStyle: "value",
139
259
  outputStyle: "inline",
140
- avoidReRenders: !0,
260
+ avoidReRenders: true,
141
261
  animations,
142
262
  usePresence,
143
263
  ResetPresence,
@@ -160,18 +280,28 @@ function createAnimations(animationsConfig) {
160
280
  setValue(next, config = {
161
281
  type: "spring"
162
282
  }, onFinish) {
163
- "worklet";
283
+ if (config.type === "direct") {
284
+ sharedValue.value = next;
285
+ onFinish?.();
286
+ } else {
287
+ const cb = onFinish ? () => {
288
+ "worklet";
164
289
 
165
- const handleFinish = onFinish ? () => {
166
- "worklet";
290
+ runOnJS(onFinish)();
291
+ } : void 0;
292
+ const animationConfig = cloneTransitionConfig(config);
293
+ if (isWeb) {
294
+ sharedValue.value = applyAnimation(next, animationConfig, cb);
295
+ } else {
296
+ runOnUI((targetValue, animationConfig2) => {
297
+ "worklet";
167
298
 
168
- runOnJS(onFinish)();
169
- } : void 0;
170
- config.type === "direct" ? (sharedValue.value = next, onFinish?.()) : config.type === "spring" ? sharedValue.value = withSpring(next, config, handleFinish) : sharedValue.value = withTiming(next, config, handleFinish);
299
+ sharedValue.value = applyAnimation(targetValue, animationConfig2, cb);
300
+ })(next, animationConfig);
301
+ }
302
+ }
171
303
  },
172
304
  stop() {
173
- "worklet";
174
-
175
305
  cancelAnimation(sharedValue);
176
306
  }
177
307
  }), [sharedValue]);
@@ -184,193 +314,295 @@ function createAnimations(animationsConfig) {
184
314
  }, onValue) {
185
315
  const instance = value.getInstance();
186
316
  return useAnimatedReaction(() => instance.value, (next, prev) => {
187
- prev !== next && runOnJS(onValue)(next);
317
+ if (prev !== next) {
318
+ runOnJS(onValue)(next);
319
+ }
188
320
  }, [onValue, instance]);
189
321
  },
190
322
  // =========================================================================
191
323
  // useAnimatedNumberStyle - Create animated styles from values
192
324
  // =========================================================================
193
325
  useAnimatedNumberStyle(val, getStyle) {
194
- const instance = val.getInstance(),
195
- derivedValue = useDerivedValue(() => instance.value, [instance, getStyle]);
196
- return useAnimatedStyle(() => getStyle(derivedValue.value), [val, getStyle, derivedValue, instance]);
326
+ const instance = val.getInstance();
327
+ if (isWeb) {
328
+ return useAnimatedStyle(() => {
329
+ "worklet";
330
+
331
+ return getStyle(instance.value);
332
+ }, [instance, getStyle]);
333
+ }
334
+ const styleVal = useDerivedValue(() => {
335
+ "worklet";
336
+
337
+ return getStyle(instance.value);
338
+ });
339
+ const animatedStyle = useAnimatedStyle(() => {
340
+ "worklet";
341
+
342
+ return styleVal.value;
343
+ });
344
+ silenceAnimatedComponentDevCheck(animatedStyle);
345
+ return animatedStyle;
346
+ },
347
+ useAnimatedNumbersStyle(vals, getStyle) {
348
+ const instances = vals.map(v => v.getInstance());
349
+ const animatedStyle = useAnimatedStyle(() => {
350
+ "worklet";
351
+
352
+ const currentValues = instances.map(inst => inst.value);
353
+ return getStyle(...currentValues);
354
+ }, isWeb ? [getStyle, ...instances] : void 0);
355
+ silenceAnimatedComponentDevCheck(animatedStyle);
356
+ return animatedStyle;
197
357
  },
198
358
  // =========================================================================
199
359
  // useAnimations - Main animation hook for components
200
360
  // =========================================================================
201
361
  useAnimations(animationProps) {
202
362
  const {
203
- props,
204
- presence,
205
- style,
206
- componentState,
207
- useStyleEmitter,
208
- themeName
209
- } = animationProps,
210
- isHydrating = componentState.unmounted === !0,
211
- isMounting = componentState.unmounted === "should-enter",
212
- isEntering = !!componentState.unmounted,
213
- isExiting = presence?.[0] === !1,
214
- wasEnteringRef = useRef(isEntering),
215
- justFinishedEntering = wasEnteringRef.current && !isEntering;
363
+ props,
364
+ presence,
365
+ style,
366
+ componentState,
367
+ useStyleEmitter,
368
+ themeName,
369
+ stateRef,
370
+ styleState
371
+ } = animationProps;
372
+ const isHydrating = componentState.unmounted === true;
373
+ const isMounting = componentState.unmounted === "should-enter";
374
+ const isEntering = !!componentState.unmounted;
375
+ const isExiting = presence?.[0] === false;
376
+ const wasEnteringRef = useRef(isEntering);
377
+ const justFinishedEntering = wasEnteringRef.current && !isEntering;
216
378
  React.useEffect(() => {
217
379
  wasEnteringRef.current = isEntering;
218
380
  });
219
- const normalized = normalizeTransition(props.transition),
220
- animationState = isExiting ? "exit" : isMounting || justFinishedEntering ? "enter" : "default",
221
- animationKey = getEffectiveAnimation(normalized, animationState),
222
- disableAnimation = isHydrating || !animationKey,
223
- isDark = themeName?.startsWith("dark") || !1,
224
- sendExitComplete = presence?.[1],
225
- exitProgress = useSharedValue(0),
226
- animatedTargetsRef = useSharedValue(null),
227
- staticTargetsRef = useSharedValue(null),
228
- transformTargetsRef = useSharedValue(null),
229
- {
230
- animatedStyles,
231
- staticStyles
232
- } = useMemo(() => {
233
- const animated = {},
234
- staticStyles2 = {},
235
- animateOnly = props.animateOnly;
236
- for (const key in style) {
237
- const rawValue = style[key],
238
- value = resolveDynamicValue(rawValue, isDark);
239
- if (value !== void 0) {
240
- if (disableAnimation) {
241
- staticStyles2[key] = value;
242
- continue;
243
- }
244
- canAnimateProperty(key, value, animateOnly) ? animated[key] = value : staticStyles2[key] = value;
245
- }
381
+ const effectiveTransition = styleState?.effectiveTransition ?? props.transition;
382
+ const normalized = normalizeTransition(effectiveTransition);
383
+ const animationState = isExiting ? "exit" : isMounting || justFinishedEntering ? "enter" : "default";
384
+ const animationKey = getEffectiveAnimation(normalized, animationState);
385
+ const disableAnimation = isHydrating || !animationKey;
386
+ const isDark = themeName?.startsWith("dark") || false;
387
+ const sendExitComplete = presence?.[1];
388
+ const exitCycleIdRef = useRef(0);
389
+ const pendingExitKeysRef = useRef(/* @__PURE__ */new Set());
390
+ const exitCompletedRef = useRef(false);
391
+ const wasExitingRef = useRef(false);
392
+ const justStartedExiting = isExiting && !wasExitingRef.current;
393
+ const justStoppedExiting = !isExiting && wasExitingRef.current;
394
+ const markExitKeyDone = useEvent((key, cycleId, finished) => {
395
+ if (cycleId !== exitCycleIdRef.current) return;
396
+ if (exitCompletedRef.current) return;
397
+ pendingExitKeysRef.current.delete(key);
398
+ if (pendingExitKeysRef.current.size === 0) {
399
+ exitCompletedRef.current = true;
400
+ sendExitComplete?.();
401
+ }
402
+ });
403
+ const isExitingRef = useSharedValue(isExiting);
404
+ const exitCycleIdShared = useSharedValue(exitCycleIdRef.current);
405
+ if (justStartedExiting) {
406
+ exitCycleIdRef.current++;
407
+ exitCompletedRef.current = false;
408
+ pendingExitKeysRef.current.clear();
409
+ }
410
+ if (justStoppedExiting) {
411
+ exitCycleIdRef.current++;
412
+ pendingExitKeysRef.current.clear();
413
+ }
414
+ useIsomorphicLayoutEffect(() => {
415
+ isExitingRef.value = isExiting;
416
+ exitCycleIdShared.value = exitCycleIdRef.current;
417
+ }, [isExiting, exitCycleIdRef.current]);
418
+ React.useEffect(() => {
419
+ wasExitingRef.current = isExiting;
420
+ });
421
+ const animatedTargetsRef = useSharedValue(null);
422
+ const staticTargetsRef = useSharedValue(null);
423
+ const transformTargetsRef = useSharedValue(null);
424
+ const {
425
+ animatedStyles,
426
+ staticStyles
427
+ } = useMemo(() => {
428
+ const animated = {};
429
+ const staticStyles2 = {};
430
+ const animateOnly = props.animateOnly;
431
+ for (const key in style) {
432
+ const rawValue = style[key];
433
+ const value = resolveDynamicValue(rawValue, isDark);
434
+ if (value === void 0) continue;
435
+ if (disableAnimation) {
436
+ staticStyles2[key] = cloneAnimationValue(value);
437
+ continue;
246
438
  }
247
- if (isMounting) for (const key in animated) staticStyles2[key] = animated[key];
439
+ if (canAnimateProperty(key, value, animateOnly)) {
440
+ animated[key] = cloneAnimationValue(value);
441
+ } else {
442
+ staticStyles2[key] = cloneAnimationValue(value);
443
+ }
444
+ }
445
+ if (isMounting) {
446
+ for (const key in animated) {
447
+ staticStyles2[key] = animated[key];
448
+ }
449
+ }
450
+ return {
451
+ animatedStyles: animated,
452
+ staticStyles: staticStyles2
453
+ };
454
+ }, [disableAnimation, style, isDark, isMounting, props.animateOnly]);
455
+ const {
456
+ baseConfig,
457
+ propertyConfigs
458
+ } = useMemo(() => {
459
+ if (isHydrating) {
248
460
  return {
249
- animatedStyles: animated,
250
- staticStyles: staticStyles2
251
- };
252
- }, [disableAnimation, style, isDark, isMounting, props.animateOnly]),
253
- {
254
- baseConfig,
255
- propertyConfigs
256
- } = useMemo(() => {
257
- if (isHydrating) return {
258
461
  baseConfig: {
259
462
  type: "timing",
260
463
  duration: 0
261
464
  },
262
465
  propertyConfigs: {}
263
466
  };
264
- const normalized2 = normalizeTransition(props.transition),
265
- effectiveKey = getEffectiveAnimation(normalized2, animationState);
266
- let base = effectiveKey ? animations[effectiveKey] ?? {
267
- type: "spring"
268
- } : {
269
- type: "spring"
270
- };
271
- normalized2.delay && (base = {
272
- ...base,
273
- delay: normalized2.delay
274
- }), normalized2.config && (base = {
275
- ...base,
276
- ...normalized2.config
277
- });
278
- const overrides = {};
279
- for (const key in normalized2.properties) {
280
- const animationNameOrConfig = normalized2.properties[key];
281
- if (typeof animationNameOrConfig == "string") overrides[key] = animations[animationNameOrConfig] ?? base;else if (animationNameOrConfig && typeof animationNameOrConfig == "object") {
282
- const configType = animationNameOrConfig.type,
283
- baseForProp = configType ? animations[configType] ?? base : base;
284
- overrides[key] = {
285
- ...baseForProp,
286
- ...animationNameOrConfig
287
- };
288
- }
289
- }
290
- const configs = {},
291
- allKeys = new Set(Object.keys(animatedStyles));
292
- if (animatedStyles.transform && Array.isArray(animatedStyles.transform)) for (const t of animatedStyles.transform) allKeys.add(Object.keys(t)[0]);
293
- for (const key of allKeys) configs[key] = overrides[key] ?? base;
294
- return {
295
- baseConfig: base,
296
- propertyConfigs: configs
297
- };
298
- }, [isHydrating, props.transition, animatedStyles, animationState]),
299
- configRef = useSharedValue({
300
- baseConfig,
301
- propertyConfigs,
302
- disableAnimation,
303
- isHydrating
304
- });
467
+ }
468
+ return buildTransitionConfig(props.transition, animations, animationState, getStyleKeys(animatedStyles));
469
+ }, [isHydrating, props.transition, animatedStyles, animationState]);
470
+ const configRef = useSharedValue({
471
+ baseConfig,
472
+ propertyConfigs,
473
+ disableAnimation,
474
+ isHydrating
475
+ });
305
476
  useIsomorphicLayoutEffect(() => {
306
- configRef.set({
477
+ configRef.value = {
307
478
  baseConfig,
308
479
  propertyConfigs,
309
480
  disableAnimation,
310
481
  isHydrating
311
- });
312
- }, [baseConfig, propertyConfigs, disableAnimation, isHydrating]), useStyleEmitter?.(nextStyle => {
313
- const animateOnly = props.animateOnly,
314
- animated = {},
315
- statics = {},
316
- transforms = [];
482
+ };
483
+ }, [baseConfig, propertyConfigs, disableAnimation, isHydrating]);
484
+ useStyleEmitter?.((nextStyle, effectiveTransition2) => {
485
+ const animateOnly = props.animateOnly;
486
+ const animated = {};
487
+ const statics = {};
488
+ const transforms = [];
489
+ const transitionToUse = effectiveTransition2 ?? props.transition;
490
+ const {
491
+ baseConfig: newBase,
492
+ propertyConfigs: newPropertyConfigs
493
+ } = buildTransitionConfig(transitionToUse, animations, animationState, getStyleKeys(nextStyle));
494
+ configRef.value = {
495
+ baseConfig: newBase,
496
+ propertyConfigs: newPropertyConfigs,
497
+ disableAnimation: configRef.value.disableAnimation,
498
+ isHydrating: configRef.value.isHydrating
499
+ };
317
500
  for (const key in nextStyle) {
318
- const rawValue = nextStyle[key],
319
- value = resolveDynamicValue(rawValue, isDark);
320
- if (value != null) {
321
- if (configRef.get().disableAnimation) {
322
- statics[key] = value;
323
- continue;
501
+ const rawValue = nextStyle[key];
502
+ const value = resolveDynamicValue(rawValue, isDark);
503
+ if (value == void 0) continue;
504
+ if (configRef.value.disableAnimation) {
505
+ statics[key] = cloneAnimationValue(value);
506
+ continue;
507
+ }
508
+ if (key === "transform" && Array.isArray(value)) {
509
+ for (const t of value) {
510
+ if (t && typeof t === "object") {
511
+ const tKey = Object.keys(t)[0];
512
+ const tVal = t[tKey];
513
+ if (typeof tVal === "number" || typeof tVal === "string") {
514
+ transforms.push(cloneAnimationValue(t));
515
+ }
516
+ }
324
517
  }
325
- if (key === "transform" && Array.isArray(value)) {
326
- for (const t of value) if (t && typeof t == "object") {
327
- const tKey = Object.keys(t)[0],
328
- tVal = t[tKey];
329
- (typeof tVal == "number" || typeof tVal == "string") && transforms.push(t);
518
+ continue;
519
+ }
520
+ if (canAnimateProperty(key, value, animateOnly)) {
521
+ animated[key] = cloneAnimationValue(value);
522
+ } else {
523
+ statics[key] = cloneAnimationValue(value);
524
+ }
525
+ }
526
+ animatedTargetsRef.value = animated;
527
+ staticTargetsRef.value = statics;
528
+ transformTargetsRef.value = transforms;
529
+ if (process.env.NODE_ENV === "development" && props.debug && props.debug !== "profile") {
530
+ console.info("[animations-reanimated] useStyleEmitter update", {
531
+ animated,
532
+ statics,
533
+ transforms
534
+ });
535
+ }
536
+ });
537
+ const exitKeysRegistered = useRef(false);
538
+ if (justStartedExiting && sendExitComplete) {
539
+ const exitKeys = [];
540
+ const animateOnly = props.animateOnly;
541
+ for (const key in animatedStyles) {
542
+ if (key === "transform") continue;
543
+ if (canAnimateProperty(key, animatedStyles[key], animateOnly)) {
544
+ exitKeys.push(key);
545
+ }
546
+ }
547
+ const transforms = animatedStyles.transform;
548
+ if (transforms && Array.isArray(transforms)) {
549
+ for (const t of transforms) {
550
+ if (!t) continue;
551
+ const tKey = Object.keys(t)[0];
552
+ if (tKey) {
553
+ if (animateOnly && !animateOnly.includes(tKey)) {
554
+ continue;
330
555
  }
331
- continue;
556
+ exitKeys.push(`transform:${tKey}`);
332
557
  }
333
- canAnimateProperty(key, value, animateOnly) ? animated[key] = value : statics[key] = value;
334
558
  }
335
559
  }
336
- animatedTargetsRef.set(animated), staticTargetsRef.set(statics), transformTargetsRef.set(transforms), process.env.NODE_ENV === "development" && props.debug && props.debug !== "profile" && console.info("[animations-reanimated] useStyleEmitter update", {
337
- animated,
338
- statics,
339
- transforms
340
- });
341
- }), React.useEffect(() => {
342
- if (!isExiting || !sendExitComplete) return;
343
- const config = configRef.get().baseConfig;
344
- return config.type === "timing" ? exitProgress.set(withTiming(1, config, finished => {
345
- "worklet";
346
-
347
- finished && runOnJS(sendExitComplete)();
348
- })) : exitProgress.set(withSpring(1, config, finished => {
349
- "worklet";
350
-
351
- finished && runOnJS(sendExitComplete)();
352
- })), () => {
353
- cancelAnimation(exitProgress);
354
- };
355
- }, [isExiting, sendExitComplete]);
560
+ pendingExitKeysRef.current = new Set(exitKeys);
561
+ exitKeysRegistered.current = exitKeys.length > 0;
562
+ }
563
+ React.useEffect(() => {
564
+ if (!justStartedExiting || !sendExitComplete) return;
565
+ if (!exitKeysRegistered.current && pendingExitKeysRef.current.size === 0) {
566
+ if (!exitCompletedRef.current) {
567
+ exitCompletedRef.current = true;
568
+ sendExitComplete();
569
+ }
570
+ }
571
+ }, [justStartedExiting, sendExitComplete]);
356
572
  const animatedStyle = useAnimatedStyle(() => {
357
573
  "worklet";
358
574
 
359
- if (disableAnimation || isHydrating) return {};
360
- const result = {},
361
- config = configRef.get(),
362
- emitterAnimated = animatedTargetsRef.value,
363
- emitterStatic = staticTargetsRef.value,
364
- emitterTransforms = transformTargetsRef.value,
365
- hasEmitterUpdates = emitterAnimated !== null,
366
- animatedValues = hasEmitterUpdates ? emitterAnimated : animatedStyles,
367
- staticValues = hasEmitterUpdates ? emitterStatic : {};
368
- for (const key in staticValues) result[key] = staticValues[key];
575
+ if (disableAnimation || isHydrating) {
576
+ return {};
577
+ }
578
+ const result = {};
579
+ const config = configRef.value;
580
+ const emitterAnimated = animatedTargetsRef.value;
581
+ const emitterStatic = staticTargetsRef.value;
582
+ const emitterTransforms = transformTargetsRef.value;
583
+ const hasEmitterUpdates = emitterAnimated !== null;
584
+ const animatedValues = hasEmitterUpdates ? emitterAnimated : animatedStyles;
585
+ const staticValues = hasEmitterUpdates ? emitterStatic : {};
586
+ const currentlyExiting = isExitingRef.value;
587
+ const currentCycleId = exitCycleIdShared.value;
588
+ for (const key in staticValues) {
589
+ result[key] = staticValues[key];
590
+ }
369
591
  for (const key in animatedValues) {
370
592
  if (key === "transform") continue;
371
- const targetValue = animatedValues[key],
372
- propConfig = config.propertyConfigs[key] ?? config.baseConfig;
373
- result[key] = applyAnimation(targetValue, propConfig);
593
+ const targetValue = animatedValues[key];
594
+ const propConfig = config.propertyConfigs[key] ?? config.baseConfig;
595
+ let callback;
596
+ if (currentlyExiting) {
597
+ const capturedKey = key;
598
+ const capturedCycleId = currentCycleId;
599
+ callback = finished => {
600
+ "worklet";
601
+
602
+ runOnJS(markExitKeyDone)(capturedKey, capturedCycleId, finished ?? false);
603
+ };
604
+ }
605
+ result[key] = applyAnimation(targetValue, propConfig, callback);
374
606
  }
375
607
  const transforms = hasEmitterUpdates ? emitterTransforms : animatedStyles.transform;
376
608
  if (transforms && Array.isArray(transforms)) {
@@ -380,30 +612,46 @@ function createAnimations(animationsConfig) {
380
612
  const keys = Object.keys(t);
381
613
  if (keys.length === 0) continue;
382
614
  const value = t[keys[0]];
383
- if (typeof value == "number" || typeof value == "string") {
384
- const transformKey = Object.keys(t)[0],
385
- targetValue = t[transformKey],
386
- propConfig = config.propertyConfigs[transformKey] ?? config.baseConfig;
615
+ if (typeof value === "number" || typeof value === "string") {
616
+ const transformKey = Object.keys(t)[0];
617
+ const targetValue = t[transformKey];
618
+ const propConfig = config.propertyConfigs[transformKey] ?? config.baseConfig;
619
+ let callback;
620
+ if (currentlyExiting) {
621
+ const capturedKey = `transform:${transformKey}`;
622
+ const capturedCycleId = currentCycleId;
623
+ callback = finished => {
624
+ "worklet";
625
+
626
+ runOnJS(markExitKeyDone)(capturedKey, capturedCycleId, finished ?? false);
627
+ };
628
+ }
387
629
  validTransforms.push({
388
- [transformKey]: applyAnimation(targetValue, propConfig)
630
+ [transformKey]: applyAnimation(targetValue, propConfig, callback)
389
631
  });
390
632
  }
391
633
  }
392
- validTransforms.length > 0 && (result.transform = validTransforms);
634
+ if (validTransforms.length > 0) {
635
+ result.transform = validTransforms;
636
+ }
393
637
  }
394
638
  return result;
395
- }, [animatedStyles, baseConfig, propertyConfigs, disableAnimation, isHydrating,
396
- // Pass SharedValues so the mapper watches them on web (see useAnimatedStyle.ts line 470-472)
397
- animatedTargetsRef, staticTargetsRef, transformTargetsRef]);
398
- return process.env.NODE_ENV === "development" && props.debug && props.debug !== "profile" && console.info("[animations-reanimated] useAnimations", {
399
- animationKey,
400
- componentState,
401
- isExiting,
402
- animatedStyles,
403
- staticStyles,
404
- baseConfig,
405
- propertyConfigs
406
- }), {
639
+ }, isWeb ? [animatedStyles, baseConfig, propertyConfigs, disableAnimation, isHydrating,
640
+ // pass SharedValues so the mapper watches them on web (no babel plugin)
641
+ animatedTargetsRef, staticTargetsRef, transformTargetsRef, isExitingRef, exitCycleIdShared, markExitKeyDone] : void 0);
642
+ silenceAnimatedComponentDevCheck(animatedStyle);
643
+ if (process.env.NODE_ENV === "development" && props.debug && props.debug !== "profile") {
644
+ console.info("[animations-reanimated] useAnimations", {
645
+ animationKey,
646
+ componentState,
647
+ isExiting,
648
+ animatedStyles,
649
+ staticStyles,
650
+ baseConfig,
651
+ propertyConfigs
652
+ });
653
+ }
654
+ return {
407
655
  style: [staticStyles, animatedStyle]
408
656
  };
409
657
  }