@tamagui/animations-motion 2.7.7 → 3.0.0-beta.1097.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.
- package/dist/cjs/createAnimations.cjs +881 -794
- package/dist/cjs/createAnimations.native.cjs +1115 -0
- package/dist/cjs/createAnimations.native.js +1073 -950
- package/dist/cjs/createAnimations.native.js.map +1 -1
- package/dist/cjs/index.cjs +9 -11
- package/dist/cjs/index.native.cjs +40 -0
- package/dist/cjs/index.native.js +26 -29
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/polyfill.cjs +2 -2
- package/dist/cjs/polyfill.native.cjs +5 -0
- package/dist/cjs/polyfill.native.js +2 -1
- package/dist/cjs/polyfill.native.js.map +1 -1
- package/dist/esm/createAnimations.mjs +869 -773
- package/dist/esm/createAnimations.mjs.map +1 -1
- package/dist/esm/createAnimations.native.js +1061 -930
- package/dist/esm/createAnimations.native.js.map +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/index.mjs +2 -2
- package/dist/esm/index.native.js +13 -12
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/polyfill.mjs +2 -1
- package/dist/esm/polyfill.mjs.map +1 -1
- package/dist/esm/polyfill.native.js +2 -1
- package/dist/esm/polyfill.native.js.map +1 -1
- package/package.json +6 -6
- package/src/createAnimations.tsx +442 -202
- package/src/index.native.ts +4 -5
- package/types/createAnimations.d.ts +3 -5
- package/types/createAnimations.d.ts.map +2 -2
- package/types/index.native.d.ts +2 -2
- package/types/index.native.d.ts.map +2 -2
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/index.mjs.map +0 -1
package/src/createAnimations.tsx
CHANGED
|
@@ -1,21 +1,38 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
easingToBezier,
|
|
3
|
+
forAnimationState,
|
|
4
|
+
getTransitionForKey,
|
|
5
|
+
hasTransition,
|
|
6
|
+
resolveTransition,
|
|
7
|
+
styleKeysForProperty,
|
|
8
|
+
type AnimationsConfig,
|
|
9
|
+
type ResolvedEntry,
|
|
10
|
+
type ResolvedTransition,
|
|
11
|
+
} from '@tamagui/animation-helpers'
|
|
2
12
|
import { ResetPresence, usePresence } from '@tamagui/use-presence'
|
|
3
13
|
import {
|
|
4
14
|
type AnimatedNumberStrategy,
|
|
5
|
-
type
|
|
6
|
-
fixStyles,
|
|
15
|
+
type AnimationDriverWithAnimatedNumbers,
|
|
7
16
|
getConfig,
|
|
8
17
|
getSplitStyles,
|
|
9
18
|
hooks,
|
|
10
|
-
|
|
19
|
+
nonAnimatableStyleProps,
|
|
20
|
+
type OnTransition,
|
|
11
21
|
Text,
|
|
12
22
|
TransitionProp,
|
|
13
23
|
type UniversalAnimatedNumber,
|
|
14
24
|
useComposedRefs,
|
|
15
25
|
useIsomorphicLayoutEffect,
|
|
16
|
-
useThemeWithState,
|
|
17
26
|
View,
|
|
27
|
+
createRefComponent,
|
|
18
28
|
} from '@tamagui/web'
|
|
29
|
+
import {
|
|
30
|
+
fixStyles,
|
|
31
|
+
normalizeValueWithProperty,
|
|
32
|
+
styleToCSS,
|
|
33
|
+
transformsToString,
|
|
34
|
+
useThemeWithState,
|
|
35
|
+
} from '@tamagui/web/internal-runtime'
|
|
19
36
|
import {
|
|
20
37
|
animate as animateMotionValue,
|
|
21
38
|
type AnimationOptions,
|
|
@@ -27,14 +44,7 @@ import {
|
|
|
27
44
|
useMotionValueEvent,
|
|
28
45
|
type ValueTransition,
|
|
29
46
|
} from 'motion/react'
|
|
30
|
-
import React, {
|
|
31
|
-
forwardRef,
|
|
32
|
-
useEffect,
|
|
33
|
-
useLayoutEffect,
|
|
34
|
-
useMemo,
|
|
35
|
-
useRef,
|
|
36
|
-
useState,
|
|
37
|
-
} from 'react'
|
|
47
|
+
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
|
38
48
|
|
|
39
49
|
const isServer = typeof window === 'undefined'
|
|
40
50
|
|
|
@@ -43,13 +53,12 @@ const isServer = typeof window === 'undefined'
|
|
|
43
53
|
// don't need animations so we return a no-op scope/animate pair.
|
|
44
54
|
function useAnimateSSRSafe() {
|
|
45
55
|
if (isServer) {
|
|
46
|
-
return [useRef(null), (() => {}) as any] as ReturnType<typeof useAnimate>
|
|
56
|
+
return [useRef(null), (() => {}) as any] as unknown as ReturnType<typeof useAnimate>
|
|
47
57
|
}
|
|
48
58
|
return useAnimate()
|
|
49
59
|
}
|
|
50
60
|
|
|
51
61
|
type MotionAnimatedNumber = MotionValue<number>
|
|
52
|
-
type AnimationConfig = ValueTransition
|
|
53
62
|
|
|
54
63
|
type MotionAnimatedNumberStyle = {
|
|
55
64
|
getStyle: (...args: any[]) => Record<string, unknown>
|
|
@@ -57,6 +66,20 @@ type MotionAnimatedNumberStyle = {
|
|
|
57
66
|
motionValues?: MotionValue<number>[]
|
|
58
67
|
}
|
|
59
68
|
|
|
69
|
+
const asStyleRecord = (style: unknown): Record<string, unknown> =>
|
|
70
|
+
style && typeof style === 'object' && !Array.isArray(style)
|
|
71
|
+
? (style as Record<string, unknown>)
|
|
72
|
+
: {}
|
|
73
|
+
|
|
74
|
+
// raw CSSOM assignment (unlike React's style prop) silently ignores unitless
|
|
75
|
+
// numbers, and the core hands the inline-output driver RN-format numeric
|
|
76
|
+
// values — suffix px the way React would before writing
|
|
77
|
+
function assignInlineStyles(node: HTMLElement, styles: Record<string, unknown>) {
|
|
78
|
+
for (const key in styles) {
|
|
79
|
+
node.style[key] = normalizeValueWithProperty(styles[key], key)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
60
83
|
/**
|
|
61
84
|
* Animation options with optional default and per-property configs.
|
|
62
85
|
* This extends AnimationOptions to support the default key.
|
|
@@ -137,21 +160,27 @@ type MotionRefs = {
|
|
|
137
160
|
animationState: 'enter' | 'exit' | 'default'
|
|
138
161
|
frozenExitTarget: Record<string, unknown> | null
|
|
139
162
|
exitCompleteScheduled: boolean
|
|
163
|
+
enterCompleteScheduled: boolean
|
|
164
|
+
disableAnimation: boolean
|
|
140
165
|
wasEntering: boolean
|
|
141
166
|
wasDisabled: boolean
|
|
167
|
+
onTransition: OnTransition | null | undefined
|
|
168
|
+
enterStarted: boolean
|
|
169
|
+
exitStarted: boolean
|
|
170
|
+
updateInFlight: boolean
|
|
171
|
+
updateControls: AnimationPlaybackControlsWithThen | null
|
|
142
172
|
wasNoClass: boolean
|
|
143
173
|
}
|
|
144
174
|
|
|
145
|
-
export function createAnimations<A extends
|
|
175
|
+
export function createAnimations<A extends AnimationsConfig>(
|
|
146
176
|
animations: A
|
|
147
|
-
):
|
|
177
|
+
): AnimationDriverWithAnimatedNumbers<A> {
|
|
148
178
|
let isHydratingGlobal: boolean | undefined
|
|
149
179
|
const hydratingComponents = new Set<Function>()
|
|
150
180
|
|
|
151
181
|
return {
|
|
152
182
|
View: MotionView,
|
|
153
183
|
Text: MotionText,
|
|
154
|
-
isReactNative: false,
|
|
155
184
|
inputStyle: 'css',
|
|
156
185
|
outputStyle: 'inline',
|
|
157
186
|
avoidReRenders: true,
|
|
@@ -176,12 +205,15 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
176
205
|
stateRef,
|
|
177
206
|
useStyleEmitter,
|
|
178
207
|
presence,
|
|
208
|
+
onTransition,
|
|
179
209
|
styleProps,
|
|
210
|
+
styleState,
|
|
180
211
|
} = animationProps
|
|
181
212
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
213
|
+
// createComponent merges a colocated `transition` out of the active
|
|
214
|
+
// pseudo style (`hoverStyle={{ transition: '400ms' }}`), so this is the
|
|
215
|
+
// one that applies right now. same source the other three drivers read.
|
|
216
|
+
const effectiveTransition = styleState?.effectiveTransition ?? props.transition
|
|
185
217
|
|
|
186
218
|
const isComponentHydrating = componentState.unmounted === true
|
|
187
219
|
const isMounting = componentState.unmounted === 'should-enter'
|
|
@@ -205,12 +237,29 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
205
237
|
animationState: 'default',
|
|
206
238
|
frozenExitTarget: null,
|
|
207
239
|
exitCompleteScheduled: false,
|
|
240
|
+
enterCompleteScheduled: false,
|
|
241
|
+
disableAnimation: true,
|
|
208
242
|
wasEntering: false,
|
|
209
243
|
wasDisabled: false,
|
|
244
|
+
onTransition: undefined,
|
|
245
|
+
enterStarted: false,
|
|
246
|
+
exitStarted: false,
|
|
247
|
+
updateInFlight: false,
|
|
248
|
+
updateControls: null,
|
|
210
249
|
wasNoClass: !!styleProps.noClass,
|
|
211
250
|
}
|
|
212
251
|
}
|
|
213
252
|
|
|
253
|
+
const emit = (
|
|
254
|
+
phase: 'start' | 'end',
|
|
255
|
+
cause: 'enter' | 'exit' | 'update',
|
|
256
|
+
finished?: boolean
|
|
257
|
+
) => {
|
|
258
|
+
refs.current.onTransition?.(
|
|
259
|
+
phase === 'end' ? { phase, cause, finished } : { phase, cause }
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
|
|
214
263
|
// track entering state transitions
|
|
215
264
|
const justFinishedEntering = refs.current.wasEntering && !isEntering
|
|
216
265
|
useEffect(() => {
|
|
@@ -225,14 +274,16 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
225
274
|
: 'default'
|
|
226
275
|
|
|
227
276
|
// disable animation during hydration and mounting (prevents "flying across the page")
|
|
228
|
-
const disableAnimation = isComponentHydrating || isMounting || !
|
|
277
|
+
const disableAnimation = isComponentHydrating || isMounting || !effectiveTransition
|
|
229
278
|
|
|
230
|
-
const [scope
|
|
279
|
+
const [scope] = useAnimateSSRSafe()
|
|
231
280
|
|
|
232
281
|
// sync ref values for reliable access from callbacks
|
|
233
282
|
refs.current.isExiting = isExiting
|
|
234
283
|
refs.current.sendExitComplete = sendExitComplete
|
|
235
284
|
refs.current.animationState = animationState
|
|
285
|
+
refs.current.disableAnimation = disableAnimation
|
|
286
|
+
refs.current.onTransition = onTransition
|
|
236
287
|
|
|
237
288
|
// detect transition into exiting state
|
|
238
289
|
const justStartedExiting = isExiting && !refs.current.wasExiting
|
|
@@ -242,6 +293,7 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
242
293
|
if (justStartedExiting || justStoppedExiting) {
|
|
243
294
|
refs.current.frozenExitTarget = null
|
|
244
295
|
refs.current.exitCompleteScheduled = false
|
|
296
|
+
refs.current.enterCompleteScheduled = false
|
|
245
297
|
}
|
|
246
298
|
|
|
247
299
|
// track previous exiting state
|
|
@@ -249,11 +301,25 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
249
301
|
refs.current.wasExiting = isExiting
|
|
250
302
|
})
|
|
251
303
|
|
|
304
|
+
// exit interrupted by a re-enter: emit a finished:false end for it
|
|
305
|
+
useIsomorphicLayoutEffect(() => {
|
|
306
|
+
if (justStoppedExiting && refs.current.exitStarted) {
|
|
307
|
+
refs.current.exitStarted = false
|
|
308
|
+
emit('end', 'exit', false)
|
|
309
|
+
}
|
|
310
|
+
}, [justStoppedExiting])
|
|
311
|
+
|
|
252
312
|
const {
|
|
253
313
|
dontAnimate = {},
|
|
254
314
|
doAnimate,
|
|
255
315
|
animationOptions,
|
|
256
|
-
} = getMotionAnimatedProps(
|
|
316
|
+
} = getMotionAnimatedProps(
|
|
317
|
+
props as any,
|
|
318
|
+
style,
|
|
319
|
+
disableAnimation,
|
|
320
|
+
animationState,
|
|
321
|
+
effectiveTransition
|
|
322
|
+
)
|
|
257
323
|
|
|
258
324
|
const [firstRenderStyle] = useState(style)
|
|
259
325
|
|
|
@@ -285,6 +351,7 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
285
351
|
// read current state from refs (closure variables can be stale)
|
|
286
352
|
const isCurrentlyExiting = refs.current.isExiting
|
|
287
353
|
const currentSendExitComplete = refs.current.sendExitComplete
|
|
354
|
+
const currentAnimationState = refs.current.animationState
|
|
288
355
|
|
|
289
356
|
// freeze exit target: once the first exit animation starts, subsequent
|
|
290
357
|
// renders (e.g. direction change) should not reverse the exit animation.
|
|
@@ -296,7 +363,14 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
296
363
|
// only recompute animation options for exit animations to avoid stale state.
|
|
297
364
|
const animationOptions =
|
|
298
365
|
isCurrentlyExiting && currentSendExitComplete
|
|
299
|
-
? getAnimationOptions(
|
|
366
|
+
? getAnimationOptions(
|
|
367
|
+
forAnimationState(
|
|
368
|
+
resolveTransition(effectiveTransition ?? null, {
|
|
369
|
+
animations: animations as Record<string, unknown>,
|
|
370
|
+
}),
|
|
371
|
+
'exit'
|
|
372
|
+
)
|
|
373
|
+
)
|
|
300
374
|
: passedOptions
|
|
301
375
|
|
|
302
376
|
try {
|
|
@@ -338,18 +412,23 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
338
412
|
return
|
|
339
413
|
}
|
|
340
414
|
|
|
341
|
-
// handle case where dontAnimate changes
|
|
415
|
+
// handle case where dontAnimate changes. removal also has to run
|
|
416
|
+
// when dontAnimate disappears entirely (undefined), or a style whose
|
|
417
|
+
// last member left (e.g. a hover-conditioned cursor on unhover)
|
|
418
|
+
// stays stuck on the node forever
|
|
342
419
|
const prevDont = refs.current.lastDontAnimate
|
|
343
420
|
if (dontAnimate) {
|
|
344
421
|
if (prevDont) {
|
|
345
422
|
removeRemovedStyles(prevDont, dontAnimate, node, doAnimate)
|
|
346
423
|
const changed = getDiff(prevDont, dontAnimate)
|
|
347
424
|
if (changed) {
|
|
348
|
-
|
|
425
|
+
assignInlineStyles(node, changed)
|
|
349
426
|
}
|
|
350
427
|
} else {
|
|
351
|
-
|
|
428
|
+
assignInlineStyles(node, dontAnimate)
|
|
352
429
|
}
|
|
430
|
+
} else if (prevDont) {
|
|
431
|
+
removeRemovedStyles(prevDont, {}, node, doAnimate)
|
|
353
432
|
}
|
|
354
433
|
|
|
355
434
|
if (doAnimate) {
|
|
@@ -358,7 +437,7 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
358
437
|
if (prevDont) {
|
|
359
438
|
for (const key in prevDont) {
|
|
360
439
|
if (key in doAnimate) {
|
|
361
|
-
node.style[key] = prevDont[key]
|
|
440
|
+
node.style[key] = normalizeValueWithProperty(prevDont[key], key)
|
|
362
441
|
if (refs.current.lastDoAnimate) {
|
|
363
442
|
refs.current.lastDoAnimate[key] = prevDont[key]
|
|
364
443
|
}
|
|
@@ -449,7 +528,7 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
449
528
|
|
|
450
529
|
const opts = animationOptions as TransitionAnimationOptions
|
|
451
530
|
const positionTransition =
|
|
452
|
-
opts.transform ?? opts.default ?? animationOptions
|
|
531
|
+
opts.x ?? opts.transform ?? opts.default ?? animationOptions
|
|
453
532
|
const cx = animateMotionValue(
|
|
454
533
|
entry.x,
|
|
455
534
|
target.x,
|
|
@@ -562,7 +641,14 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
562
641
|
// PopoverClickDuringEnter / AnimatePresenceEnterExit.
|
|
563
642
|
// the motion-value spring owns transform — keep it out of the
|
|
564
643
|
// WAAPI animation so the two don't double-drive the property
|
|
565
|
-
let waapiDiff =
|
|
644
|
+
let waapiDiff = Object.fromEntries(
|
|
645
|
+
Object.entries(fixedDiff).map(([key, value]) => [
|
|
646
|
+
key,
|
|
647
|
+
Array.isArray(value)
|
|
648
|
+
? value.map((item) => normalizeValueWithProperty(item, key))
|
|
649
|
+
: normalizeValueWithProperty(value, key),
|
|
650
|
+
])
|
|
651
|
+
)
|
|
566
652
|
if (popperHandledTransform && 'transform' in waapiDiff) {
|
|
567
653
|
waapiDiff = { ...waapiDiff }
|
|
568
654
|
delete waapiDiff.transform
|
|
@@ -577,7 +663,11 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
577
663
|
}
|
|
578
664
|
|
|
579
665
|
if (Object.keys(waapiDiff).length > 0) {
|
|
580
|
-
startedControls =
|
|
666
|
+
startedControls = animateMotionValue(
|
|
667
|
+
node,
|
|
668
|
+
waapiDiff,
|
|
669
|
+
animationOptions
|
|
670
|
+
) as AnimationPlaybackControlsWithThen
|
|
581
671
|
refs.current.controls = startedControls
|
|
582
672
|
}
|
|
583
673
|
refs.current.lastAnimateAt = Date.now()
|
|
@@ -589,41 +679,102 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
589
679
|
} finally {
|
|
590
680
|
// exit completion: notify AnimatePresence when exit animation finishes
|
|
591
681
|
if (isCurrentlyExiting && currentSendExitComplete) {
|
|
682
|
+
// an enter animation that was still in flight is now interrupted
|
|
683
|
+
if (refs.current.enterStarted) {
|
|
684
|
+
refs.current.enterStarted = false
|
|
685
|
+
emit('end', 'enter', false)
|
|
686
|
+
}
|
|
592
687
|
if (startedControls) {
|
|
593
688
|
// new animation started — attach completion handler
|
|
689
|
+
if (!refs.current.exitStarted) {
|
|
690
|
+
refs.current.exitStarted = true
|
|
691
|
+
emit('start', 'exit')
|
|
692
|
+
}
|
|
594
693
|
refs.current.exitCompleteScheduled = true
|
|
694
|
+
const complete = (finished: boolean) => {
|
|
695
|
+
// guard: only complete if still exiting (prevents stale promise
|
|
696
|
+
// from calling sendExitComplete after a re-entry cancels the exit)
|
|
697
|
+
if (!refs.current.isExiting) return
|
|
698
|
+
if (refs.current.exitStarted) {
|
|
699
|
+
refs.current.exitStarted = false
|
|
700
|
+
// exit 'end' fires immediately before presence safeToRemove
|
|
701
|
+
emit('end', 'exit', finished)
|
|
702
|
+
}
|
|
703
|
+
currentSendExitComplete()
|
|
704
|
+
}
|
|
595
705
|
startedControls.finished
|
|
596
|
-
.then(() =>
|
|
597
|
-
|
|
598
|
-
// from calling sendExitComplete after a re-entry cancels the exit)
|
|
599
|
-
if (refs.current.isExiting) {
|
|
600
|
-
currentSendExitComplete()
|
|
601
|
-
}
|
|
602
|
-
})
|
|
603
|
-
.catch(() => {
|
|
604
|
-
if (refs.current.isExiting) {
|
|
605
|
-
currentSendExitComplete()
|
|
606
|
-
}
|
|
607
|
-
})
|
|
706
|
+
.then(() => complete(true))
|
|
707
|
+
.catch(() => complete(false))
|
|
608
708
|
} else if (!refs.current.exitCompleteScheduled) {
|
|
609
709
|
// no animation started AND none previously scheduled (e.g. diff=null
|
|
610
710
|
// on re-render mid-exit because frozenExitTarget matches lastDoAnimate)
|
|
611
711
|
// — complete immediately only if we've never started an exit animation
|
|
712
|
+
emit('start', 'exit')
|
|
713
|
+
emit('end', 'exit', true)
|
|
612
714
|
currentSendExitComplete()
|
|
613
715
|
}
|
|
614
716
|
// else: exit animation already scheduled via a previous flush,
|
|
615
717
|
// its .finished promise will call sendExitComplete when done
|
|
718
|
+
} else if (currentAnimationState === 'enter') {
|
|
719
|
+
if (startedControls) {
|
|
720
|
+
if (!refs.current.enterStarted) {
|
|
721
|
+
refs.current.enterStarted = true
|
|
722
|
+
emit('start', 'enter')
|
|
723
|
+
}
|
|
724
|
+
refs.current.enterCompleteScheduled = true
|
|
725
|
+
const complete = (finished: boolean) => {
|
|
726
|
+
if (refs.current.disposed || refs.current.animationState !== 'enter')
|
|
727
|
+
return
|
|
728
|
+
if (refs.current.enterStarted) {
|
|
729
|
+
refs.current.enterStarted = false
|
|
730
|
+
emit('end', 'enter', finished)
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
startedControls.finished
|
|
734
|
+
.then(() => complete(true))
|
|
735
|
+
.catch(() => complete(false))
|
|
736
|
+
} else if (
|
|
737
|
+
!refs.current.enterCompleteScheduled &&
|
|
738
|
+
!refs.current.disableAnimation
|
|
739
|
+
) {
|
|
740
|
+
// no animation needed for this enter (no style diff) — complete
|
|
741
|
+
// immediately. skipped while animation is disabled (mount flushes
|
|
742
|
+
// apply enter styles without animating; completing there would
|
|
743
|
+
// report enter done before the real transition even starts)
|
|
744
|
+
emit('start', 'enter')
|
|
745
|
+
emit('end', 'enter', true)
|
|
746
|
+
}
|
|
747
|
+
} else if (startedControls) {
|
|
748
|
+
// update: a style change while mounted. a new update that supersedes
|
|
749
|
+
// an in-flight one closes it out as finished:false (motion silently
|
|
750
|
+
// replaces the conflicting property animation without settling the
|
|
751
|
+
// old controls, so we can't rely on its promise for the interrupted
|
|
752
|
+
// one). the latest controls reports true on natural completion.
|
|
753
|
+
const controls = startedControls
|
|
754
|
+
if (refs.current.updateInFlight) {
|
|
755
|
+
emit('end', 'update', false)
|
|
756
|
+
}
|
|
757
|
+
refs.current.updateInFlight = true
|
|
758
|
+
refs.current.updateControls = controls
|
|
759
|
+
emit('start', 'update')
|
|
760
|
+
const settle = (finished: boolean) => {
|
|
761
|
+
if (refs.current.updateControls !== controls) return
|
|
762
|
+
refs.current.updateInFlight = false
|
|
763
|
+
refs.current.updateControls = null
|
|
764
|
+
emit('end', 'update', finished)
|
|
765
|
+
}
|
|
766
|
+
controls.finished.then(() => settle(true)).catch(() => settle(false))
|
|
616
767
|
}
|
|
617
768
|
}
|
|
618
769
|
}
|
|
619
770
|
|
|
620
|
-
useStyleEmitter?.((nextStyle,
|
|
771
|
+
useStyleEmitter?.((nextStyle, emittedTransition) => {
|
|
621
772
|
const animationProps = getMotionAnimatedProps(
|
|
622
773
|
props as any,
|
|
623
774
|
nextStyle,
|
|
624
775
|
disableAnimation,
|
|
625
776
|
refs.current.animationState,
|
|
626
|
-
effectiveTransition
|
|
777
|
+
emittedTransition ?? effectiveTransition
|
|
627
778
|
)
|
|
628
779
|
|
|
629
780
|
flushAnimation(animationProps)
|
|
@@ -681,8 +832,8 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
681
832
|
if ((justEnabled || justStrippedClasses) && animationState !== 'enter') {
|
|
682
833
|
const node = stateRef.current.host
|
|
683
834
|
if (node instanceof HTMLElement) {
|
|
684
|
-
if (dontAnimate)
|
|
685
|
-
if (doAnimate)
|
|
835
|
+
if (dontAnimate) assignInlineStyles(node, dontAnimate)
|
|
836
|
+
if (doAnimate) assignInlineStyles(node, doAnimate)
|
|
686
837
|
// keep the popper position motion values in sync with the direct
|
|
687
838
|
// inline write so a later retarget doesn't start from a stale spot
|
|
688
839
|
const entry = PopperPositionAnims.get(node)
|
|
@@ -831,7 +982,7 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
831
982
|
}
|
|
832
983
|
|
|
833
984
|
function getMotionAnimatedProps(
|
|
834
|
-
props: { transition?: TransitionProp | null
|
|
985
|
+
props: { transition?: TransitionProp | null },
|
|
835
986
|
style: Record<string, unknown>,
|
|
836
987
|
disable: boolean,
|
|
837
988
|
animationState: 'enter' | 'exit' | 'default' = 'default',
|
|
@@ -843,18 +994,22 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
843
994
|
}
|
|
844
995
|
}
|
|
845
996
|
|
|
846
|
-
const
|
|
847
|
-
transitionOverride ?? props.transition ?? null,
|
|
997
|
+
const resolved = forAnimationState(
|
|
998
|
+
resolveTransition(transitionOverride ?? props.transition ?? null, {
|
|
999
|
+
animations: animations as Record<string, unknown>,
|
|
1000
|
+
}),
|
|
848
1001
|
animationState
|
|
849
1002
|
)
|
|
1003
|
+
const animationOptions = getAnimationOptions(resolved)
|
|
850
1004
|
|
|
851
1005
|
let dontAnimate: Record<string, unknown> | undefined
|
|
852
1006
|
let doAnimate: Record<string, unknown> | undefined
|
|
853
1007
|
|
|
854
|
-
const animateOnly = props.animateOnly as string[] | undefined
|
|
855
1008
|
for (const key in style) {
|
|
856
1009
|
const value = style[key]
|
|
857
|
-
|
|
1010
|
+
// a key the transition does not name is set, never animated, which is
|
|
1011
|
+
// what a `transitionProperty` list narrows down to
|
|
1012
|
+
if (disableAnimationProps.has(key) || !getTransitionForKey(resolved, key)) {
|
|
858
1013
|
dontAnimate ||= {}
|
|
859
1014
|
dontAnimate[key] = value
|
|
860
1015
|
} else {
|
|
@@ -870,91 +1025,23 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
870
1025
|
}
|
|
871
1026
|
}
|
|
872
1027
|
|
|
873
|
-
function getAnimationOptions(
|
|
874
|
-
|
|
875
|
-
animationState: 'enter' | 'exit' | 'default' = 'default'
|
|
876
|
-
): TransitionAnimationOptions {
|
|
877
|
-
const normalized = normalizeTransition(transitionProp)
|
|
1028
|
+
function getAnimationOptions(resolved: ResolvedTransition): TransitionAnimationOptions {
|
|
1029
|
+
if (!hasTransition(resolved)) return {}
|
|
878
1030
|
|
|
879
|
-
let effectiveKey = getEffectiveAnimation(normalized, animationState)
|
|
880
|
-
|
|
881
|
-
// fallback: if we have enter/exit defined but state is 'default' and no default key,
|
|
882
|
-
// use enter timing as fallback to avoid empty animation options
|
|
883
|
-
if (!effectiveKey && animationState === 'default') {
|
|
884
|
-
effectiveKey = normalized.enter || normalized.exit || null
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
const globalConfigOverride: Record<string, unknown> | undefined = normalized.config
|
|
888
|
-
? { ...normalized.config }
|
|
889
|
-
: undefined
|
|
890
|
-
|
|
891
|
-
if (
|
|
892
|
-
!effectiveKey &&
|
|
893
|
-
Object.keys(normalized.properties).length === 0 &&
|
|
894
|
-
!globalConfigOverride
|
|
895
|
-
) {
|
|
896
|
-
return {}
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
const defaultConfig = effectiveKey ? withInferredType(animations[effectiveKey]) : null
|
|
900
|
-
|
|
901
|
-
const delay = normalized.delay
|
|
902
|
-
|
|
903
|
-
// framer motion's animate() expects default config at the TOP LEVEL
|
|
904
1031
|
const result: TransitionAnimationOptions = {}
|
|
905
1032
|
|
|
906
|
-
|
|
907
|
-
|
|
1033
|
+
// motion reads the default config from the TOP LEVEL as well as `default`
|
|
1034
|
+
if (resolved.all) {
|
|
1035
|
+
const base = entryToMotion(resolved.all)
|
|
1036
|
+
Object.assign(result, base)
|
|
1037
|
+
result.default = { ...base }
|
|
908
1038
|
}
|
|
909
1039
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
result
|
|
915
|
-
result.damping === undefined &&
|
|
916
|
-
result.stiffness === undefined &&
|
|
917
|
-
result.mass === undefined
|
|
918
|
-
) {
|
|
919
|
-
result.type = 'tween'
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
if (delay) {
|
|
924
|
-
result.delay = delay
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
if (defaultConfig || globalConfigOverride || delay) {
|
|
928
|
-
result.default = {
|
|
929
|
-
...defaultConfig,
|
|
930
|
-
...globalConfigOverride,
|
|
931
|
-
...(delay ? { delay } : null),
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
for (const [propName, animationNameOrConfig] of Object.entries(
|
|
936
|
-
normalized.properties
|
|
937
|
-
)) {
|
|
938
|
-
if (typeof animationNameOrConfig === 'string') {
|
|
939
|
-
result[propName] = withInferredType(animations[animationNameOrConfig])
|
|
940
|
-
} else if (animationNameOrConfig && typeof animationNameOrConfig === 'object') {
|
|
941
|
-
const baseConfig = animationNameOrConfig.type
|
|
942
|
-
? withInferredType(animations[animationNameOrConfig.type])
|
|
943
|
-
: defaultConfig
|
|
944
|
-
|
|
945
|
-
result[propName] = {
|
|
946
|
-
...baseConfig,
|
|
947
|
-
...animationNameOrConfig,
|
|
948
|
-
} as ValueTransition
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
// we standardize to ms across drivers, motion expects s
|
|
953
|
-
convertMsToS(result as ValueTransition)
|
|
954
|
-
convertMsToS(result.default)
|
|
955
|
-
for (const key in result) {
|
|
956
|
-
if (key !== 'default' && typeof result[key] === 'object') {
|
|
957
|
-
convertMsToS(result[key])
|
|
1040
|
+
for (const entry of resolved.entries) {
|
|
1041
|
+
if (entry.property === 'all') continue
|
|
1042
|
+
const config = entryToMotion(entry)
|
|
1043
|
+
for (const key of styleKeysForProperty(entry.property)) {
|
|
1044
|
+
result[key] = { ...config }
|
|
958
1045
|
}
|
|
959
1046
|
}
|
|
960
1047
|
|
|
@@ -962,32 +1049,34 @@ export function createAnimations<A extends Record<string, AnimationConfig>>(
|
|
|
962
1049
|
}
|
|
963
1050
|
}
|
|
964
1051
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
}
|
|
1052
|
+
/**
|
|
1053
|
+
* one resolved entry as a motion `ValueTransition`.
|
|
1054
|
+
*
|
|
1055
|
+
* durations are in SECONDS here. motion is the only driver that wants that, so
|
|
1056
|
+
* the conversion lives at this single boundary. it used to be a post-pass that
|
|
1057
|
+
* checked `type === 'tween'` and skipped springs and anything typed `'timing'`,
|
|
1058
|
+
* which meant a `{ type: 'spring', duration: 200 }` ran for 200 seconds.
|
|
1059
|
+
*/
|
|
1060
|
+
function entryToMotion(entry: ResolvedEntry): ValueTransition {
|
|
1061
|
+
const delay = entry.delayMs ? { delay: entry.delayMs / 1000 } : null
|
|
976
1062
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
config.stiffness === undefined &&
|
|
986
|
-
config.mass === undefined)
|
|
987
|
-
if (isTimingBased) {
|
|
988
|
-
config.duration = config.duration / 1000
|
|
989
|
-
}
|
|
1063
|
+
if (entry.timing.kind === 'spring') {
|
|
1064
|
+
return {
|
|
1065
|
+
type: 'spring',
|
|
1066
|
+
stiffness: entry.timing.stiffness,
|
|
1067
|
+
damping: entry.timing.damping,
|
|
1068
|
+
mass: entry.timing.mass,
|
|
1069
|
+
...delay,
|
|
1070
|
+
} as ValueTransition
|
|
990
1071
|
}
|
|
1072
|
+
|
|
1073
|
+
const bezier = easingToBezier(entry.timing.easing)
|
|
1074
|
+
return {
|
|
1075
|
+
type: 'tween',
|
|
1076
|
+
duration: entry.timing.durationMs / 1000,
|
|
1077
|
+
...(bezier ? { ease: [bezier[0], bezier[1], bezier[2], bezier[3]] } : null),
|
|
1078
|
+
...delay,
|
|
1079
|
+
} as ValueTransition
|
|
991
1080
|
}
|
|
992
1081
|
|
|
993
1082
|
function removeRemovedStyles(
|
|
@@ -1006,35 +1095,114 @@ function removeRemovedStyles(
|
|
|
1006
1095
|
}
|
|
1007
1096
|
}
|
|
1008
1097
|
|
|
1009
|
-
// truly non-animatable CSS properties (discrete, keyword-based, no
|
|
1010
|
-
//
|
|
1098
|
+
// truly non-animatable CSS properties (discrete, keyword-based, no
|
|
1099
|
+
// interpolation), from the same list the style emitter uses to promote base
|
|
1100
|
+
// values to atomic classes. handing one of these to motion's animate() leaves
|
|
1101
|
+
// its committed value stuck on the node after the style stops including it,
|
|
1102
|
+
// because only the dontAnimate path clears removed styles. flexBasis is
|
|
1103
|
+
// interpolable but applied discretely by driver choice.
|
|
1011
1104
|
export const disableAnimationProps: Set<string> = new Set<string>([
|
|
1012
|
-
|
|
1013
|
-
'alignItems',
|
|
1014
|
-
'boxSizing',
|
|
1015
|
-
'contain',
|
|
1016
|
-
'containerType',
|
|
1017
|
-
'display',
|
|
1105
|
+
...Object.keys(nonAnimatableStyleProps),
|
|
1018
1106
|
'flexBasis',
|
|
1019
|
-
'flexDirection',
|
|
1020
|
-
'fontFamily',
|
|
1021
|
-
'justifyContent',
|
|
1022
|
-
'overflow',
|
|
1023
|
-
'overflowX',
|
|
1024
|
-
'overflowY',
|
|
1025
|
-
'pointerEvents',
|
|
1026
|
-
'position',
|
|
1027
|
-
'textWrap',
|
|
1028
|
-
'userSelect',
|
|
1029
1107
|
])
|
|
1030
1108
|
|
|
1109
|
+
// props equality for the getSplitStyles memo: functions and children can't
|
|
1110
|
+
// affect style output (they pass through), so they don't participate
|
|
1111
|
+
function motionPropsEqual(a: Record<string, any>, b: Record<string, any>) {
|
|
1112
|
+
for (const key in a) {
|
|
1113
|
+
if (!(key in b)) return false
|
|
1114
|
+
}
|
|
1115
|
+
for (const key in b) {
|
|
1116
|
+
if (key === 'children') continue
|
|
1117
|
+
const av = a[key]
|
|
1118
|
+
const bv = b[key]
|
|
1119
|
+
if (typeof bv === 'function' && typeof av === 'function') continue
|
|
1120
|
+
if (!Object.is(av, bv)) return false
|
|
1121
|
+
}
|
|
1122
|
+
return true
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// one-level style object comparison: style arrays are recreated per render
|
|
1126
|
+
// with usually-identical contents
|
|
1127
|
+
function motionStylesEqual(a: any[], b: any[]) {
|
|
1128
|
+
if (a.length !== b.length) return false
|
|
1129
|
+
for (let i = 0; i < a.length; i++) {
|
|
1130
|
+
const sa = a[i]
|
|
1131
|
+
const sb = b[i]
|
|
1132
|
+
if (sa === sb) continue
|
|
1133
|
+
if (!sa || !sb) return false
|
|
1134
|
+
let aCount = 0
|
|
1135
|
+
for (const key in sa) {
|
|
1136
|
+
aCount++
|
|
1137
|
+
if (!Object.is(sa[key], sb[key])) return false
|
|
1138
|
+
}
|
|
1139
|
+
let bCount = 0
|
|
1140
|
+
for (const _key in sb) {
|
|
1141
|
+
bCount++
|
|
1142
|
+
}
|
|
1143
|
+
if (aCount !== bCount) return false
|
|
1144
|
+
}
|
|
1145
|
+
return true
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1031
1148
|
const MotionView = createMotionView('div')
|
|
1032
1149
|
const MotionText = createMotionView('span')
|
|
1033
1150
|
|
|
1151
|
+
const transformAliases: Record<string, string> = {
|
|
1152
|
+
x: 'translateX',
|
|
1153
|
+
y: 'translateY',
|
|
1154
|
+
scale: 'scale',
|
|
1155
|
+
scaleX: 'scaleX',
|
|
1156
|
+
scaleY: 'scaleY',
|
|
1157
|
+
rotate: 'rotate',
|
|
1158
|
+
rotateX: 'rotateX',
|
|
1159
|
+
rotateY: 'rotateY',
|
|
1160
|
+
rotateZ: 'rotateZ',
|
|
1161
|
+
skewX: 'skewX',
|
|
1162
|
+
skewY: 'skewY',
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
function compileAnimatedStyle(
|
|
1166
|
+
initialSource: Record<string, unknown>,
|
|
1167
|
+
initialResolved: Record<string, unknown>
|
|
1168
|
+
) {
|
|
1169
|
+
const shorthands = getConfig().shorthands
|
|
1170
|
+
const entries = Object.keys(initialSource).map((source) => ({
|
|
1171
|
+
source,
|
|
1172
|
+
target: shorthands?.[source] ?? source,
|
|
1173
|
+
tokenValue:
|
|
1174
|
+
typeof initialSource[source] === 'string' &&
|
|
1175
|
+
initialResolved[shorthands?.[source] ?? source] !== initialSource[source]
|
|
1176
|
+
? initialResolved[shorthands?.[source] ?? source]
|
|
1177
|
+
: undefined,
|
|
1178
|
+
}))
|
|
1179
|
+
|
|
1180
|
+
return (sourceStyle: Record<string, unknown>) => {
|
|
1181
|
+
const resolved: Record<string, unknown> = {}
|
|
1182
|
+
const transforms: Record<string, unknown>[] = []
|
|
1183
|
+
|
|
1184
|
+
for (const entry of entries) {
|
|
1185
|
+
const value = sourceStyle[entry.source]
|
|
1186
|
+
if (value === undefined) continue
|
|
1187
|
+
if (entry.source === 'transform' && Array.isArray(value)) {
|
|
1188
|
+
resolved.transform = transformsToString(value)
|
|
1189
|
+
} else if (entry.source in transformAliases) {
|
|
1190
|
+
transforms.push({ [transformAliases[entry.source]]: value })
|
|
1191
|
+
} else {
|
|
1192
|
+
resolved[entry.target] =
|
|
1193
|
+
entry.tokenValue ?? normalizeValueWithProperty(value, entry.target)
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
if (transforms.length) resolved.transform = transformsToString(transforms)
|
|
1198
|
+
return resolved
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1034
1202
|
function createMotionView(defaultTag: string) {
|
|
1035
1203
|
const isText = defaultTag === 'span'
|
|
1036
1204
|
|
|
1037
|
-
const Component =
|
|
1205
|
+
const Component = createRefComponent((propsIn: any, ref) => {
|
|
1038
1206
|
const { forwardedRef, animation, render = defaultTag, style, ...propsRest } = propsIn
|
|
1039
1207
|
const [scope, animate] = useAnimateSSRSafe()
|
|
1040
1208
|
const hostRef = useRef<HTMLElement>(null)
|
|
@@ -1070,6 +1238,13 @@ function createMotionView(defaultTag: string) {
|
|
|
1070
1238
|
})()
|
|
1071
1239
|
|
|
1072
1240
|
function getProps(props: any) {
|
|
1241
|
+
if (
|
|
1242
|
+
process.env.NODE_ENV === 'development' &&
|
|
1243
|
+
propsIn.debug === 'profile' &&
|
|
1244
|
+
typeof performance !== 'undefined'
|
|
1245
|
+
) {
|
|
1246
|
+
performance.mark('tamagui-motion-style-split')
|
|
1247
|
+
}
|
|
1073
1248
|
const out = getSplitStyles(
|
|
1074
1249
|
props,
|
|
1075
1250
|
isText ? Text.staticConfig : View.staticConfig,
|
|
@@ -1097,12 +1272,76 @@ function createMotionView(defaultTag: string) {
|
|
|
1097
1272
|
return out.viewProps
|
|
1098
1273
|
}
|
|
1099
1274
|
|
|
1100
|
-
const
|
|
1275
|
+
const resolvedAnimatedStyle = useMemo(() => {
|
|
1276
|
+
if (!animatedStyle) return null
|
|
1277
|
+
const currentValues = animatedStyle.motionValues
|
|
1278
|
+
? animatedStyle.motionValues.map((value) => value.get())
|
|
1279
|
+
: animatedStyle.motionValue
|
|
1280
|
+
? [animatedStyle.motionValue.get()]
|
|
1281
|
+
: []
|
|
1282
|
+
const initialSource = animatedStyle.getStyle(...currentValues)
|
|
1283
|
+
const initialResolved = asStyleRecord(getProps({ style: initialSource }).style)
|
|
1284
|
+
return {
|
|
1285
|
+
initial: initialResolved,
|
|
1286
|
+
resolve: compileAnimatedStyle(initialSource, initialResolved),
|
|
1287
|
+
}
|
|
1288
|
+
}, [animatedStyle, state?.theme, state?.name])
|
|
1289
|
+
|
|
1290
|
+
// memoize the full getSplitStyles pass: it costs ~90us per render and its
|
|
1291
|
+
// style-affecting inputs are usually unchanged. functions and children
|
|
1292
|
+
// pass through getSplitStyles untouched, so they refresh on cache hits
|
|
1293
|
+
// without invalidating.
|
|
1294
|
+
const memoRef = useRef<null | {
|
|
1295
|
+
propsRest: any
|
|
1296
|
+
styles: any[]
|
|
1297
|
+
theme: any
|
|
1298
|
+
themeName: string | undefined
|
|
1299
|
+
result: any
|
|
1300
|
+
}>(null)
|
|
1301
|
+
|
|
1302
|
+
let props: any
|
|
1303
|
+
const cached = memoRef.current
|
|
1304
|
+
if (
|
|
1305
|
+
cached &&
|
|
1306
|
+
cached.theme === state?.theme &&
|
|
1307
|
+
cached.themeName === state?.name &&
|
|
1308
|
+
motionStylesEqual(cached.styles, nonAnimatedStyles) &&
|
|
1309
|
+
motionPropsEqual(cached.propsRest, propsRest)
|
|
1310
|
+
) {
|
|
1311
|
+
props = { ...cached.result }
|
|
1312
|
+
for (const key in propsRest) {
|
|
1313
|
+
const val = propsRest[key]
|
|
1314
|
+
if (key === 'children' || typeof val === 'function') {
|
|
1315
|
+
props[key] = val
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
} else {
|
|
1319
|
+
props = getProps({ ...propsRest, style: nonAnimatedStyles })
|
|
1320
|
+
memoRef.current = {
|
|
1321
|
+
propsRest,
|
|
1322
|
+
styles: nonAnimatedStyles,
|
|
1323
|
+
theme: state?.theme,
|
|
1324
|
+
themeName: state?.name,
|
|
1325
|
+
result: props,
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
if (resolvedAnimatedStyle) {
|
|
1330
|
+
// reassign so the animated initial never leaks into the memo cache
|
|
1331
|
+
props = {
|
|
1332
|
+
...props,
|
|
1333
|
+
style: {
|
|
1334
|
+
...asStyleRecord(props.style),
|
|
1335
|
+
...resolvedAnimatedStyle.initial,
|
|
1336
|
+
},
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1101
1340
|
const Element = render || 'div'
|
|
1102
1341
|
const transformedProps = hooks.usePropsTransform?.(render, props, stateRef, false)
|
|
1103
1342
|
|
|
1104
|
-
//
|
|
1105
|
-
//
|
|
1343
|
+
// subscribe before passive effects so a layout effect cannot set a value
|
|
1344
|
+
// between this node mounting and its MotionValue subscription.
|
|
1106
1345
|
useIsomorphicLayoutEffect(() => {
|
|
1107
1346
|
if (!animatedStyle) return
|
|
1108
1347
|
|
|
@@ -1115,15 +1354,13 @@ function createMotionView(defaultTag: string) {
|
|
|
1115
1354
|
: animatedStyle.motionValue
|
|
1116
1355
|
? animatedStyle.getStyle(animatedStyle.motionValue.get())
|
|
1117
1356
|
: null
|
|
1118
|
-
const webStyle = currentStyle &&
|
|
1357
|
+
const webStyle = currentStyle && resolvedAnimatedStyle?.resolve(currentStyle)
|
|
1119
1358
|
if (webStyle) {
|
|
1120
|
-
|
|
1359
|
+
assignInlineStyles(node, webStyle)
|
|
1121
1360
|
seededNode.current = node
|
|
1122
1361
|
seededInCurrentFrame.current = true
|
|
1123
1362
|
requestAnimationFrame(() => {
|
|
1124
|
-
if (seededNode.current === node)
|
|
1125
|
-
seededInCurrentFrame.current = false
|
|
1126
|
-
}
|
|
1363
|
+
if (seededNode.current === node) seededInCurrentFrame.current = false
|
|
1127
1364
|
})
|
|
1128
1365
|
}
|
|
1129
1366
|
}
|
|
@@ -1132,7 +1369,7 @@ function createMotionView(defaultTag: string) {
|
|
|
1132
1369
|
animationConfig: AnimatedNumberStrategy | undefined
|
|
1133
1370
|
): AnimationOptions =>
|
|
1134
1371
|
animationConfig?.type === 'timing'
|
|
1135
|
-
? { type: 'tween', duration: (animationConfig
|
|
1372
|
+
? { type: 'tween', duration: (animationConfig.duration || 0) / 1000 }
|
|
1136
1373
|
: animationConfig?.type === 'direct'
|
|
1137
1374
|
? { type: 'tween', duration: 0 }
|
|
1138
1375
|
: { type: 'spring', ...(animationConfig as any) }
|
|
@@ -1140,44 +1377,47 @@ function createMotionView(defaultTag: string) {
|
|
|
1140
1377
|
const animateNodeTo = (
|
|
1141
1378
|
nextStyle: Record<string, unknown>,
|
|
1142
1379
|
transition: AnimationOptions,
|
|
1143
|
-
|
|
1380
|
+
motionValue: MotionValue
|
|
1144
1381
|
) => {
|
|
1145
1382
|
if (!(node instanceof HTMLElement) || hostRef.current !== node) return
|
|
1146
|
-
const webStyle =
|
|
1383
|
+
const webStyle = resolvedAnimatedStyle?.resolve(nextStyle)
|
|
1147
1384
|
if (!webStyle) return
|
|
1148
|
-
settlePendingMotionOnFinish(
|
|
1385
|
+
settlePendingMotionOnFinish(
|
|
1386
|
+
motionValue,
|
|
1387
|
+
animate(node, webStyle as any, transition)
|
|
1388
|
+
)
|
|
1149
1389
|
}
|
|
1390
|
+
|
|
1150
1391
|
const animateChangedValue = (
|
|
1151
1392
|
nextStyle: Record<string, unknown>,
|
|
1152
1393
|
transition: AnimationOptions,
|
|
1153
|
-
|
|
1394
|
+
motionValue: MotionValue
|
|
1154
1395
|
) => {
|
|
1155
1396
|
if (seededInCurrentFrame.current && seededNode.current === node) {
|
|
1156
|
-
const queuedFrame = queuedAnimationFrames.current.get(
|
|
1157
|
-
if (queuedFrame !== undefined)
|
|
1158
|
-
cancelAnimationFrame(queuedFrame)
|
|
1159
|
-
}
|
|
1397
|
+
const queuedFrame = queuedAnimationFrames.current.get(motionValue)
|
|
1398
|
+
if (queuedFrame !== undefined) cancelAnimationFrame(queuedFrame)
|
|
1160
1399
|
const frame = requestAnimationFrame(() => {
|
|
1161
|
-
if (queuedAnimationFrames.current.get(
|
|
1162
|
-
queuedAnimationFrames.current.delete(
|
|
1400
|
+
if (queuedAnimationFrames.current.get(motionValue) !== frame) return
|
|
1401
|
+
queuedAnimationFrames.current.delete(motionValue)
|
|
1163
1402
|
if (hostRef.current === node) {
|
|
1164
|
-
animateNodeTo(nextStyle, transition,
|
|
1403
|
+
animateNodeTo(nextStyle, transition, motionValue)
|
|
1165
1404
|
} else {
|
|
1166
|
-
const onFinish = PendingMotionOnFinish.get(
|
|
1167
|
-
PendingMotionOnFinish.delete(
|
|
1405
|
+
const onFinish = PendingMotionOnFinish.get(motionValue)
|
|
1406
|
+
PendingMotionOnFinish.delete(motionValue)
|
|
1168
1407
|
onFinish?.()
|
|
1169
1408
|
}
|
|
1170
1409
|
})
|
|
1171
|
-
queuedAnimationFrames.current.set(
|
|
1410
|
+
queuedAnimationFrames.current.set(motionValue, frame)
|
|
1172
1411
|
return
|
|
1173
1412
|
}
|
|
1174
|
-
animateNodeTo(nextStyle, transition,
|
|
1413
|
+
animateNodeTo(nextStyle, transition, motionValue)
|
|
1175
1414
|
}
|
|
1176
1415
|
|
|
1177
1416
|
// multi-value path: subscribe to all motion values
|
|
1178
1417
|
if (animatedStyle.motionValues) {
|
|
1179
1418
|
const mvs = animatedStyle.motionValues
|
|
1180
|
-
const styleForAll = () =>
|
|
1419
|
+
const styleForAll = () =>
|
|
1420
|
+
animatedStyle.getStyle(...mvs.map((value) => value.get()))
|
|
1181
1421
|
const unsubs = mvs.map((mv) =>
|
|
1182
1422
|
mv.on('change', () =>
|
|
1183
1423
|
animateChangedValue(
|
|
@@ -1201,7 +1441,7 @@ function createMotionView(defaultTag: string) {
|
|
|
1201
1441
|
motionValue
|
|
1202
1442
|
)
|
|
1203
1443
|
)
|
|
1204
|
-
}, [animatedStyle])
|
|
1444
|
+
}, [animatedStyle, resolvedAnimatedStyle])
|
|
1205
1445
|
|
|
1206
1446
|
return <Element {...transformedProps} ref={composedRefs} />
|
|
1207
1447
|
})
|