@tamagui/animations-react-native 1.0.1-beta.98 → 1.0.1-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/createAnimations.js +261 -51
- package/dist/cjs/createAnimations.js.map +3 -3
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/polyfill.js +5 -0
- package/dist/cjs/polyfill.js.map +7 -0
- package/dist/esm/createAnimations.js +260 -51
- package/dist/esm/createAnimations.js.map +3 -3
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +2 -2
- package/dist/esm/polyfill.js +4 -0
- package/dist/esm/polyfill.js.map +7 -0
- package/dist/jsx/createAnimations.js +253 -51
- package/dist/jsx/createAnimations.js.map +3 -3
- package/dist/jsx/index.js +1 -0
- package/dist/jsx/index.js.map +2 -2
- package/dist/jsx/polyfill.js +4 -0
- package/dist/jsx/polyfill.js.map +7 -0
- package/package.json +11 -10
- package/src/createAnimations.tsx +307 -90
- package/src/index.tsx +2 -0
- package/src/polyfill.ts +4 -0
- package/types/createAnimations.d.ts +8 -2
- package/types/index.d.ts +1 -0
- package/types/polyfill.d.ts +1 -0
- package/types/createAnimations.d.ts.map +0 -1
- package/types/index.d.ts.map +0 -1
package/src/createAnimations.tsx
CHANGED
|
@@ -1,115 +1,332 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
AnimatedNumberStrategy,
|
|
3
|
+
AnimationDriver,
|
|
4
|
+
AnimationProp,
|
|
5
|
+
UniversalAnimatedNumber,
|
|
6
|
+
isWeb,
|
|
7
|
+
useEvent,
|
|
8
|
+
useIsomorphicLayoutEffect,
|
|
9
|
+
useSafeRef,
|
|
10
|
+
} from '@tamagui/core'
|
|
11
|
+
import { usePresence } from '@tamagui/use-presence'
|
|
12
|
+
import { useEffect, useMemo } from 'react'
|
|
4
13
|
import { Animated } from 'react-native'
|
|
5
14
|
|
|
6
15
|
type AnimationsConfig<A extends Object = any> = {
|
|
7
16
|
[Key in keyof A]: AnimationConfig
|
|
8
17
|
}
|
|
9
18
|
|
|
10
|
-
type AnimationConfig =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
type AnimationConfig = Partial<
|
|
20
|
+
Pick<
|
|
21
|
+
Animated.SpringAnimationConfig,
|
|
22
|
+
| 'delay'
|
|
23
|
+
| 'bounciness'
|
|
24
|
+
| 'damping'
|
|
25
|
+
| 'friction'
|
|
26
|
+
| 'mass'
|
|
27
|
+
| 'overshootClamping'
|
|
28
|
+
| 'speed'
|
|
29
|
+
| 'stiffness'
|
|
30
|
+
| 'tension'
|
|
31
|
+
| 'velocity'
|
|
32
|
+
>
|
|
33
|
+
>
|
|
14
34
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
35
|
+
const animatedStyleKey = {
|
|
36
|
+
transform: true,
|
|
37
|
+
opacity: true,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const AnimatedView = Animated.View
|
|
41
|
+
export const AnimatedText = Animated.Text
|
|
42
|
+
|
|
43
|
+
export function useAnimatedNumber(initial: number): UniversalAnimatedNumber<Animated.Value> {
|
|
44
|
+
const state = useSafeRef(
|
|
45
|
+
null as any as {
|
|
46
|
+
val: Animated.Value
|
|
47
|
+
composite: Animated.CompositeAnimation | null
|
|
48
|
+
strategy: AnimatedNumberStrategy
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
if (!state.current) {
|
|
52
|
+
state.current = {
|
|
53
|
+
composite: null,
|
|
54
|
+
val: new Animated.Value(initial),
|
|
55
|
+
strategy: { type: 'spring' },
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
getInstance() {
|
|
61
|
+
return state.current.val
|
|
62
|
+
},
|
|
63
|
+
getValue() {
|
|
64
|
+
return state.current.val['_value']
|
|
65
|
+
},
|
|
66
|
+
stop() {
|
|
67
|
+
state.current.composite?.stop()
|
|
68
|
+
state.current.composite = null
|
|
69
|
+
},
|
|
70
|
+
setValue(next: number, { type, ...config } = { type: 'spring' }) {
|
|
71
|
+
const val = state.current.val
|
|
72
|
+
if (type === 'direct') {
|
|
73
|
+
val.setValue(next)
|
|
74
|
+
} else if (type === 'spring') {
|
|
75
|
+
state.current.composite?.stop()
|
|
76
|
+
const composite = Animated.spring(val, {
|
|
77
|
+
...config,
|
|
78
|
+
toValue: next,
|
|
79
|
+
useNativeDriver: !isWeb,
|
|
80
|
+
})
|
|
81
|
+
composite.start()
|
|
82
|
+
state.current.composite = composite
|
|
83
|
+
} else {
|
|
84
|
+
state.current.composite?.stop()
|
|
85
|
+
const composite = Animated.timing(val, {
|
|
86
|
+
...config,
|
|
87
|
+
toValue: next,
|
|
88
|
+
useNativeDriver: !isWeb,
|
|
89
|
+
})
|
|
90
|
+
composite.start()
|
|
91
|
+
state.current.composite = composite
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
}
|
|
18
96
|
|
|
97
|
+
export function useAnimatedNumberReaction(
|
|
98
|
+
value: UniversalAnimatedNumber<Animated.Value>,
|
|
99
|
+
cb: (current: number) => void
|
|
100
|
+
) {
|
|
101
|
+
const onChange = useEvent((current) => {
|
|
102
|
+
cb(current.value)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
const id = value.getInstance().addListener(onChange)
|
|
107
|
+
return () => {
|
|
108
|
+
value.getInstance().removeListener(id)
|
|
109
|
+
}
|
|
110
|
+
}, [value, onChange])
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function useAnimatedNumberStyle<V extends UniversalAnimatedNumber<Animated.Value>>(
|
|
114
|
+
value: V,
|
|
115
|
+
getStyle: (value: any) => any
|
|
116
|
+
) {
|
|
117
|
+
return getStyle(value.getInstance())
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function createAnimations<A extends AnimationsConfig>(animations: A): AnimationDriver<A> {
|
|
19
121
|
AnimatedView['displayName'] = 'AnimatedView'
|
|
20
122
|
AnimatedText['displayName'] = 'AnimatedText'
|
|
21
123
|
|
|
22
124
|
return {
|
|
23
|
-
|
|
125
|
+
isReactNative: true,
|
|
24
126
|
animations,
|
|
25
127
|
View: AnimatedView,
|
|
26
128
|
Text: AnimatedText,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
isEntering,
|
|
49
|
-
exitVariant: presence?.exitVariant,
|
|
50
|
-
enterVariant: presence?.enterVariant,
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
const animatedValues = useRef<Record<string, Animated.Value>>({})
|
|
54
|
-
|
|
55
|
-
// TODO loop and create values, run them if they change
|
|
56
|
-
|
|
57
|
-
const [animatedStyles, nonAnimatedStyle] = [{}, {}]
|
|
58
|
-
const animatedStyleKey = {
|
|
59
|
-
transform: true,
|
|
60
|
-
opacity: true,
|
|
129
|
+
useAnimatedNumber,
|
|
130
|
+
useAnimatedNumberReaction,
|
|
131
|
+
useAnimatedNumberStyle,
|
|
132
|
+
usePresence,
|
|
133
|
+
useAnimations: ({ props, onDidAnimate, style, state, presence }) => {
|
|
134
|
+
const isExiting = presence?.[0] === false
|
|
135
|
+
const sendExitComplete = presence?.[1]
|
|
136
|
+
const mergedStyles = style
|
|
137
|
+
const animateStyles = useSafeRef<Record<string, Animated.Value>>({})
|
|
138
|
+
const animatedTranforms = useSafeRef<{ [key: string]: Animated.Value }[]>([])
|
|
139
|
+
const animationsState = useSafeRef<
|
|
140
|
+
WeakMap<
|
|
141
|
+
Animated.Value,
|
|
142
|
+
{
|
|
143
|
+
interopolation: Animated.AnimatedInterpolation
|
|
144
|
+
current?: number | undefined
|
|
145
|
+
}
|
|
146
|
+
>
|
|
147
|
+
>(null as any)
|
|
148
|
+
if (!animationsState.current) {
|
|
149
|
+
animationsState.current = new WeakMap()
|
|
61
150
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
151
|
+
|
|
152
|
+
const runners: Function[] = []
|
|
153
|
+
const completions: Promise<void>[] = []
|
|
154
|
+
|
|
155
|
+
// const args = [JSON.stringify(mergedStyles)]
|
|
156
|
+
const args = [JSON.stringify(mergedStyles), JSON.stringify(state), isExiting, !!onDidAnimate]
|
|
157
|
+
|
|
158
|
+
const res = useMemo(() => {
|
|
159
|
+
const nonAnimatedStyle = {}
|
|
160
|
+
for (const key in mergedStyles) {
|
|
161
|
+
const val = mergedStyles[key]
|
|
162
|
+
if (!animatedStyleKey[key]) {
|
|
163
|
+
nonAnimatedStyle[key] = val
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
if (key !== 'transform') {
|
|
167
|
+
animateStyles.current[key] = update(key, animateStyles.current[key], val)
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
// key: 'transform'
|
|
171
|
+
// for now just support one transform key
|
|
172
|
+
if (!val) continue
|
|
173
|
+
for (const [index, transform] of val.entries()) {
|
|
174
|
+
if (!transform) continue
|
|
175
|
+
const tkey = Object.keys(transform)[0]
|
|
176
|
+
const currentTransform = animatedTranforms.current[index]?.[tkey]
|
|
177
|
+
animatedTranforms.current[index] = {
|
|
178
|
+
[tkey]: update(tkey, currentTransform, transform[tkey]),
|
|
179
|
+
}
|
|
180
|
+
animatedTranforms.current = [...animatedTranforms.current]
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const animatedStyle = {
|
|
185
|
+
...Object.fromEntries(
|
|
186
|
+
Object.entries({
|
|
187
|
+
...animateStyles.current,
|
|
188
|
+
}).map(([k, v]) => [k, animationsState.current!.get(v)?.interopolation || v])
|
|
189
|
+
),
|
|
190
|
+
transform: animatedTranforms.current.map((r) => {
|
|
191
|
+
const key = Object.keys(r)[0]
|
|
192
|
+
const val = animationsState.current!.get(r[key])?.interopolation || r[key]
|
|
193
|
+
return { [key]: val }
|
|
194
|
+
}),
|
|
67
195
|
}
|
|
68
|
-
}
|
|
69
196
|
|
|
70
|
-
const animatedStyle = animatedStyleKey
|
|
71
|
-
|
|
72
|
-
const args = [
|
|
73
|
-
JSON.stringify(all),
|
|
74
|
-
state.mounted,
|
|
75
|
-
state.hover,
|
|
76
|
-
state.press,
|
|
77
|
-
state.pressIn,
|
|
78
|
-
state.focus,
|
|
79
|
-
delay,
|
|
80
|
-
isPresent,
|
|
81
|
-
onDidAnimate,
|
|
82
|
-
onDidAnimateCb,
|
|
83
|
-
presence?.exitVariant,
|
|
84
|
-
presence?.enterVariant,
|
|
85
|
-
]
|
|
86
|
-
|
|
87
|
-
// const callback = (
|
|
88
|
-
// isExiting: boolean,
|
|
89
|
-
// exitingStyleProps: Record<string, boolean>,
|
|
90
|
-
// key: string,
|
|
91
|
-
// value: any
|
|
92
|
-
// ) => {
|
|
93
|
-
// return (completed, current) => {
|
|
94
|
-
// onDidAnimateCb(key, completed, current, {
|
|
95
|
-
// attemptedValue: value,
|
|
96
|
-
// })
|
|
97
|
-
// if (isExiting) {
|
|
98
|
-
// exitingStyleProps[key] = false
|
|
99
|
-
// const areStylesExiting = Object.values(exitingStyleProps).some(Boolean)
|
|
100
|
-
// // if this is true, then we've finished our exit animations
|
|
101
|
-
// if (!areStylesExiting) {
|
|
102
|
-
// sendExitComplete?.()
|
|
103
|
-
// }
|
|
104
|
-
// }
|
|
105
|
-
// }
|
|
106
|
-
// }
|
|
107
|
-
|
|
108
|
-
return useMemo(() => {
|
|
109
197
|
return {
|
|
110
198
|
style: [nonAnimatedStyle, animatedStyle],
|
|
111
199
|
}
|
|
200
|
+
|
|
201
|
+
function update(key: string, animated: Animated.Value | undefined, valIn: string | number) {
|
|
202
|
+
const [val, type] = getValue(valIn)
|
|
203
|
+
const value = animated || new Animated.Value(val)
|
|
204
|
+
let interpolateArgs: any
|
|
205
|
+
if (type) {
|
|
206
|
+
const curInterpolation = animationsState.current.get(value)
|
|
207
|
+
interpolateArgs = getInterpolated(
|
|
208
|
+
curInterpolation?.current ?? value['_value'],
|
|
209
|
+
val,
|
|
210
|
+
type
|
|
211
|
+
)
|
|
212
|
+
animationsState.current!.set(value, {
|
|
213
|
+
interopolation: value.interpolate(interpolateArgs),
|
|
214
|
+
current: val,
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
if (value) {
|
|
218
|
+
const animationConfig = getAnimationConfig(key, animations, props.animation)
|
|
219
|
+
|
|
220
|
+
let resolve
|
|
221
|
+
const promise = new Promise<void>((res) => {
|
|
222
|
+
resolve = res
|
|
223
|
+
})
|
|
224
|
+
completions.push(promise)
|
|
225
|
+
|
|
226
|
+
runners.push(() => {
|
|
227
|
+
value.stopAnimation()
|
|
228
|
+
Animated.spring(value, {
|
|
229
|
+
toValue: val,
|
|
230
|
+
useNativeDriver: !isWeb,
|
|
231
|
+
...animationConfig,
|
|
232
|
+
}).start(({ finished }) => {
|
|
233
|
+
if (finished) {
|
|
234
|
+
resolve()
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
if (process.env.NODE_ENV === 'development') {
|
|
240
|
+
if (props['debug']) {
|
|
241
|
+
// prettier-ignore
|
|
242
|
+
// eslint-disable-next-line no-console
|
|
243
|
+
console.log(' 💠 animate', key, `from ${value['_value']} to`, valIn, `(${val})`, 'type', type, 'interpolate', interpolateArgs)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return value
|
|
247
|
+
}
|
|
248
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
112
249
|
}, args)
|
|
250
|
+
|
|
251
|
+
useIsomorphicLayoutEffect(() => {
|
|
252
|
+
runners.forEach((r) => r())
|
|
253
|
+
let cancel = false
|
|
254
|
+
Promise.all(completions).then(() => {
|
|
255
|
+
if (cancel) return
|
|
256
|
+
onDidAnimate?.()
|
|
257
|
+
if (isExiting) {
|
|
258
|
+
sendExitComplete?.()
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
return () => {
|
|
262
|
+
cancel = true
|
|
263
|
+
}
|
|
264
|
+
}, args)
|
|
265
|
+
|
|
266
|
+
if (process.env.NODE_ENV === 'development') {
|
|
267
|
+
if (props['debug']) {
|
|
268
|
+
// eslint-disable-next-line no-console
|
|
269
|
+
console.log(`Returning animated`, res)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return res
|
|
113
274
|
},
|
|
114
275
|
}
|
|
115
276
|
}
|
|
277
|
+
|
|
278
|
+
function getInterpolated(current: number, next: number, postfix = 'deg') {
|
|
279
|
+
if (next === current) {
|
|
280
|
+
current = next - 0.000000001
|
|
281
|
+
}
|
|
282
|
+
const inputRange = [current, next]
|
|
283
|
+
const outputRange = [`${current}${postfix}`, `${next}${postfix}`]
|
|
284
|
+
if (next < current) {
|
|
285
|
+
inputRange.reverse()
|
|
286
|
+
outputRange.reverse()
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
inputRange,
|
|
290
|
+
outputRange,
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function getAnimationConfig(key: string, animations: AnimationsConfig, animation?: AnimationProp) {
|
|
295
|
+
if (typeof animation === 'string') {
|
|
296
|
+
return animations[animation]
|
|
297
|
+
}
|
|
298
|
+
let type = ''
|
|
299
|
+
let extraConf: any
|
|
300
|
+
if (Array.isArray(animation)) {
|
|
301
|
+
type = animation[0] as string
|
|
302
|
+
const conf = animation[1] && animation[1][key]
|
|
303
|
+
if (conf) {
|
|
304
|
+
if (typeof conf === 'string') {
|
|
305
|
+
type = conf
|
|
306
|
+
} else {
|
|
307
|
+
type = (conf as any).type || type
|
|
308
|
+
extraConf = conf
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
const val = animation?.[key]
|
|
313
|
+
type = val?.type
|
|
314
|
+
extraConf = val
|
|
315
|
+
}
|
|
316
|
+
const found = animations[type]
|
|
317
|
+
if (!found) {
|
|
318
|
+
throw new Error(`No animation of type "${type}" for key "${key}"`)
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
...found,
|
|
322
|
+
...extraConf,
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function getValue(input: number | string) {
|
|
327
|
+
if (typeof input !== 'string') {
|
|
328
|
+
return [input] as const
|
|
329
|
+
}
|
|
330
|
+
const [_, number, after] = input.match(/([-0-9]+)(deg|%|px)/) ?? []
|
|
331
|
+
return [+number, after] as const
|
|
332
|
+
}
|
package/src/index.tsx
CHANGED
package/src/polyfill.ts
ADDED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import { AnimationDriver } from '@tamagui/core';
|
|
1
|
+
import { AnimationDriver, UniversalAnimatedNumber } from '@tamagui/core';
|
|
2
|
+
import { Animated } from 'react-native';
|
|
2
3
|
declare type AnimationsConfig<A extends Object = any> = {
|
|
3
4
|
[Key in keyof A]: AnimationConfig;
|
|
4
5
|
};
|
|
5
|
-
declare type AnimationConfig =
|
|
6
|
+
declare type AnimationConfig = Partial<Pick<Animated.SpringAnimationConfig, 'delay' | 'bounciness' | 'damping' | 'friction' | 'mass' | 'overshootClamping' | 'speed' | 'stiffness' | 'tension' | 'velocity'>>;
|
|
7
|
+
export declare const AnimatedView: Animated.AnimatedComponent<typeof import("react-native").View>;
|
|
8
|
+
export declare const AnimatedText: Animated.AnimatedComponent<typeof import("react-native").Text>;
|
|
9
|
+
export declare function useAnimatedNumber(initial: number): UniversalAnimatedNumber<Animated.Value>;
|
|
10
|
+
export declare function useAnimatedNumberReaction(value: UniversalAnimatedNumber<Animated.Value>, cb: (current: number) => void): void;
|
|
11
|
+
export declare function useAnimatedNumberStyle<V extends UniversalAnimatedNumber<Animated.Value>>(value: V, getStyle: (value: any) => any): any;
|
|
6
12
|
export declare function createAnimations<A extends AnimationsConfig>(animations: A): AnimationDriver<A>;
|
|
7
13
|
export {};
|
|
8
14
|
//# sourceMappingURL=createAnimations.d.ts.map
|
package/types/index.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=polyfill.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createAnimations.d.ts","sourceRoot":"","sources":["../src/createAnimations.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAiB,MAAM,eAAe,CAAA;AAI9D,aAAK,gBAAgB,CAAC,CAAC,SAAS,MAAM,GAAG,GAAG,IAAI;KAC7C,GAAG,IAAI,MAAM,CAAC,GAAG,eAAe;CAClC,CAAA;AAED,aAAK,eAAe,GAAG,EAAE,CAAA;AAKzB,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,EAAE,UAAU,EAAE,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAoG9F"}
|
package/types/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAA"}
|