@tamagui/animations-reanimated 1.0.1-beta.21 → 1.0.1-beta.211

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.
@@ -0,0 +1,429 @@
1
+ import { AnimationDriver, AnimationProp, useEvent } from '@tamagui/core'
2
+ import { PresenceContext, usePresence } from '@tamagui/use-presence'
3
+ import { useContext, useMemo } from 'react'
4
+ // until we fix Sheet
5
+ import { FlatList, Animated as RNAnimated } from 'react-native'
6
+ import Animated, {
7
+ WithDecayConfig,
8
+ WithSpringConfig,
9
+ WithTimingConfig,
10
+ runOnJS,
11
+ useAnimatedStyle,
12
+ withDecay,
13
+ withDelay,
14
+ withRepeat,
15
+ withSpring,
16
+ withTiming,
17
+ } from 'react-native-reanimated'
18
+
19
+ import {
20
+ useAnimatedNumber,
21
+ useAnimatedNumberReaction,
22
+ useAnimatedNumberStyle,
23
+ } from './useAnimatedNumber'
24
+
25
+ // add nice warning for if mis-configured next config:
26
+ if (process.env.NODE_ENV === 'development') {
27
+ if (FlatList?.['_isProxyWorm']) {
28
+ // eslint-disable-next-line no-console
29
+ console.warn(
30
+ `Using reanimated with excludeReactNativeWebExports including FlatList, adjust your next.config.js, reanimated currently doesn't support tree-shaking and needs *List components around.`
31
+ )
32
+ }
33
+ }
34
+
35
+ type AnimationsConfig<A extends Object = any> = {
36
+ [Key in keyof A]: AnimationConfig
37
+ }
38
+
39
+ type AnimationConfig =
40
+ | ({ type: 'timing'; loop?: number; repeat?: number; repeatReverse?: boolean } & WithTimingConfig)
41
+ | ({ type: 'spring'; loop?: number; repeat?: number; repeatReverse?: boolean } & WithSpringConfig)
42
+ | ({ type: 'decay'; loop?: number; repeat?: number; repeatReverse?: boolean } & WithDecayConfig)
43
+
44
+ const animatedStyleKey = {
45
+ transform: true,
46
+ opacity: true,
47
+ // backgroundColor: true,
48
+ // color: true,
49
+ }
50
+
51
+ export function createAnimations<A extends AnimationsConfig>(animations: A): AnimationDriver<A> {
52
+ const AnimatedView = Animated.View
53
+ const AnimatedText = Animated.Text
54
+
55
+ AnimatedView['displayName'] = 'AnimatedView'
56
+ AnimatedText['displayName'] = 'AnimatedText'
57
+
58
+ return {
59
+ isReactNative: true,
60
+ animations,
61
+ View: AnimatedView,
62
+ Text: AnimatedText,
63
+
64
+ // temp
65
+ // @ts-ignore
66
+ NumberView: RNAnimated.View,
67
+
68
+ useAnimatedNumber,
69
+ useAnimatedNumberReaction,
70
+ useAnimatedNumberStyle,
71
+ useAnimations: (props, helpers) => {
72
+ const { pseudos, onDidAnimate, delay, getStyle, state, staticConfig } = helpers
73
+ const [isPresent, sendExitComplete] = usePresence()
74
+ const presence = useContext(PresenceContext)
75
+
76
+ const exitStyle = presence?.exitVariant
77
+ ? staticConfig.variantsParsed?.[presence.exitVariant]?.true || pseudos.exitStyle
78
+ : pseudos.exitStyle
79
+
80
+ const reanimatedOnDidAnimated = useEvent<NonNullable<typeof onDidAnimate>>((...args) => {
81
+ onDidAnimate?.(...args)
82
+ })
83
+
84
+ const isExiting = isPresent === false
85
+ const isEntering = !state.mounted
86
+
87
+ const all = getStyle({
88
+ isExiting,
89
+ isEntering,
90
+ exitVariant: presence?.exitVariant,
91
+ enterVariant: presence?.enterVariant,
92
+ })
93
+
94
+ const [animatedStyles, nonAnimatedStyle] = [{}, {}]
95
+ for (const key of Object.keys(all)) {
96
+ if (animatedStyleKey[key]) {
97
+ animatedStyles[key] = all[key]
98
+ } else {
99
+ nonAnimatedStyle[key] = all[key]
100
+ }
101
+ }
102
+
103
+ const animatedString = JSON.stringify(animatedStyles)
104
+ const key =
105
+ animatedString +
106
+ JSON.stringify([
107
+ state.mounted,
108
+ state.hover,
109
+ state.press,
110
+ state.pressIn,
111
+ state.focus,
112
+ delay,
113
+ isPresent,
114
+ onDidAnimate,
115
+ presence?.exitVariant,
116
+ presence?.enterVariant,
117
+ ])
118
+
119
+ const callback = (
120
+ isExiting: boolean,
121
+ exitingStyleProps: Record<string, boolean>,
122
+ key: string,
123
+ value: any
124
+ ) => {
125
+ 'worklet'
126
+ return (completed: boolean | undefined, current: any) => {
127
+ 'worklet'
128
+ runOnJS(reanimatedOnDidAnimated)(key, completed, current, {
129
+ attemptedValue: value,
130
+ })
131
+ if (isExiting) {
132
+ exitingStyleProps[key] = false
133
+ const areStylesExiting = Object.values(exitingStyleProps).some(Boolean)
134
+ // if this is true, then we've finished our exit animations
135
+ if (!areStylesExiting) {
136
+ if (sendExitComplete) {
137
+ runOnJS(sendExitComplete)()
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ const animatedStyle = useAnimatedStyle(() => {
145
+ 'worklet'
146
+
147
+ // getting issues with native unless this :/
148
+ const style = JSON.parse(animatedString)
149
+
150
+ const final = {
151
+ transform: [] as any[],
152
+ }
153
+
154
+ const isExiting = isPresent === false
155
+
156
+ const exitingStyleProps: Record<string, boolean> = {}
157
+ if (exitStyle) {
158
+ for (const key in exitStyle) {
159
+ if (key === 'transform') {
160
+ for (const attr of exitStyle[key]) {
161
+ const tkey = Object.keys(attr)[0]
162
+ exitingStyleProps[tkey] = true
163
+ }
164
+ } else {
165
+ exitingStyleProps[key] = true
166
+ }
167
+ }
168
+ }
169
+
170
+ for (const key in style) {
171
+ const value = style[key]
172
+ const animationConfig = getAnimationConfig(key, animations, props.animation)
173
+ const { animation, config, shouldRepeat, repeatCount, repeatReverse } = getAnimation(
174
+ key,
175
+ animationConfig,
176
+ props.animateOnly
177
+ )
178
+
179
+ const { delayMs = null } = animationDelay(key, animationConfig, delay)
180
+
181
+ if (!animation) {
182
+ // eslint-disable-next-line no-console
183
+ console.warn('No animation for', key, 'in', style)
184
+ continue
185
+ }
186
+ if (!config) {
187
+ // eslint-disable-next-line no-console
188
+ console.warn('No animation config for', key, 'in', style)
189
+ continue
190
+ }
191
+
192
+ if (key === 'transform') {
193
+ if (!Array.isArray(value)) {
194
+ // eslint-disable-next-line no-console
195
+ console.error(`Invalid transform value. Needs to be an array.`)
196
+ continue
197
+ }
198
+
199
+ for (const transformObject of value) {
200
+ const key = Object.keys(transformObject)[0]
201
+ const transformValue = transformObject[key]
202
+ let finalValue = animation(
203
+ transformValue,
204
+ config as any,
205
+ callback(isExiting, exitingStyleProps, key, value)
206
+ )
207
+ if (shouldRepeat) {
208
+ finalValue = withRepeat(finalValue, repeatCount, repeatReverse)
209
+ }
210
+ final['transform'].push({
211
+ [key]: finalValue,
212
+ })
213
+ }
214
+ continue
215
+ }
216
+
217
+ if (typeof value === 'object') {
218
+ // shadows
219
+ final[key] = {}
220
+ for (const innerStyleKey of Object.keys(value || {})) {
221
+ let finalValue = animation(
222
+ value,
223
+ config as any,
224
+ callback(isExiting, exitingStyleProps, key, value)
225
+ )
226
+ if (shouldRepeat) {
227
+ finalValue = withRepeat(finalValue, repeatCount, repeatReverse)
228
+ }
229
+ if (delayMs != null) {
230
+ final[key][innerStyleKey] = withDelay(delayMs, finalValue)
231
+ } else {
232
+ final[key][innerStyleKey] = finalValue
233
+ }
234
+ }
235
+ continue
236
+ }
237
+
238
+ let finalValue = animation(
239
+ value,
240
+ config as any,
241
+ callback(isExiting, exitingStyleProps, key, value)
242
+ )
243
+ if (shouldRepeat) {
244
+ finalValue = withRepeat(finalValue, repeatCount, repeatReverse)
245
+ }
246
+ if (delayMs != null && typeof delayMs === 'number') {
247
+ final[key] = withDelay(delayMs, finalValue)
248
+ } else {
249
+ final[key] = finalValue
250
+ }
251
+
252
+ // end for (key in mergedStyles)
253
+ }
254
+
255
+ return final
256
+ }, [key])
257
+
258
+ return useMemo(() => {
259
+ return {
260
+ style: [nonAnimatedStyle, animatedStyle],
261
+ }
262
+ // eslint-disable-next-line react-hooks/exhaustive-deps
263
+ }, [key])
264
+ },
265
+ }
266
+ }
267
+
268
+ function getAnimationConfig(key: string, animations: AnimationsConfig, animation?: AnimationProp) {
269
+ 'worklet'
270
+ if (typeof animation === 'string') {
271
+ return animations[animation]
272
+ }
273
+ let type = ''
274
+ let extraConf: any
275
+ if (Array.isArray(animation)) {
276
+ type = animation[0] as string
277
+ const conf = animation[1] && animation[1][key]
278
+ if (conf) {
279
+ if (typeof conf === 'string') {
280
+ type = conf
281
+ } else {
282
+ type = (conf as any).type || type
283
+ extraConf = conf
284
+ }
285
+ }
286
+ } else {
287
+ const val = animation?.[key]
288
+ type = val?.type
289
+ extraConf = val
290
+ }
291
+ const found = animations[type]
292
+ if (!found) {
293
+ throw new Error(`No animation of type "${type}" for key "${key}"`)
294
+ }
295
+ return {
296
+ ...found,
297
+ ...extraConf,
298
+ type: found.type,
299
+ }
300
+ }
301
+
302
+ function animationDelay(
303
+ key: string,
304
+ animation: AnimationConfig | undefined,
305
+ defaultDelay?: number
306
+ ) {
307
+ 'worklet'
308
+ if (
309
+ !animation ||
310
+ !animation[key] ||
311
+ animation[key].delayMs === undefined ||
312
+ animation[key].delayMs === null
313
+ ) {
314
+ return {
315
+ delayMs: null,
316
+ }
317
+ }
318
+ return {
319
+ delayMs: animation[key].delayMs as TransitionConfig['delay'],
320
+ }
321
+ }
322
+
323
+ type TransitionConfigWithoutRepeats = (
324
+ | ({ type?: 'spring' } & WithSpringConfig)
325
+ | ({ type: 'timing' } & WithTimingConfig)
326
+ | ({ type: 'decay' } & WithDecayConfig)
327
+ ) & {
328
+ delay?: number
329
+ }
330
+
331
+ type TransitionConfig = TransitionConfigWithoutRepeats & {
332
+ /**
333
+ * Number of times this animation should repeat. To make it infinite, use the `loop` boolean.
334
+ *
335
+ * Default: `0`
336
+ *
337
+ * It's worth noting that this value isn't *exactly* a `repeat`. Instead, it uses Reanimated's `withRepeat` function under the hood, which repeats back to the **previous value**. If you want a repeated animation, I recommend setting it to `true` from the start, and make sure you have a `from` value.
338
+ *
339
+ * As a result, this value cannot be reliably changed on the fly. If you would like animations to repeat based on the `from` value, `repeat` must be a number when the component initializes. You can set it to `0` to stop it, but you won't be able to start it again. You might be better off using the sequence array API if you need to update its repetitiveness on the fly.
340
+ */
341
+ repeat?: number
342
+ /**
343
+ * Setting this to `true` is the same as `repeat: Infinity`
344
+ *
345
+ * Default: `false`
346
+ *
347
+ * Note: this value cannot be set on the fly. If you would like animations to repeat based on the `from` value, it must be `true` when the component initializes. You can set it to `false` to stop it, but you won't be able to start it again. You might be better off using the sequence array API if you need to update its repetitiveness on the fly.
348
+ */
349
+ loop?: boolean
350
+ /**
351
+ * Whether or not the animation repetition should alternate in direction.
352
+ *
353
+ * By default, this is `true`.
354
+ *
355
+ * If `false`, any animations with `loop` or `repeat` will not go back and forth. Instead, they will go from 0 -> 1, and again from 0 -> 1.
356
+ *
357
+ * If `true`, then animations will go 0 -> 1 -> 0.
358
+ *
359
+ * Setting this to `true` is like setting `animationDirection: alternate` in CSS.
360
+ */
361
+ repeatReverse?: boolean
362
+ }
363
+
364
+ const isColor = (styleKey: string) => {
365
+ 'worklet'
366
+ return [
367
+ 'backgroundColor',
368
+ 'borderBottomColor',
369
+ 'borderColor',
370
+ 'borderEndColor',
371
+ 'borderLeftColor',
372
+ 'borderRightColor',
373
+ 'borderStartColor',
374
+ 'borderTopColor',
375
+ 'color',
376
+ ].includes(styleKey)
377
+ }
378
+
379
+ function getAnimation(
380
+ key: string,
381
+ animationConfig: AnimationConfig | undefined,
382
+ animateOnly?: string[]
383
+ ) {
384
+ 'worklet'
385
+ if (!animationConfig || (animateOnly && !animateOnly.includes(key))) {
386
+ return {}
387
+ }
388
+
389
+ let repeatCount = 0
390
+ const repeatReverse = animationConfig.repeatReverse || false
391
+ let animationType: Required<TransitionConfig>['type'] = animationConfig?.type || 'spring'
392
+
393
+ if (isColor(key) || key === 'opacity') {
394
+ animationType = 'timing'
395
+ }
396
+
397
+ if ('repeat' in animationConfig) {
398
+ repeatCount = animationConfig.repeat || 0
399
+ } else {
400
+ if (animationConfig.loop) {
401
+ repeatCount = animationConfig.loop ? -1 : 0
402
+ }
403
+ }
404
+
405
+ let config = animationConfig
406
+ let animation: typeof withTiming | typeof withSpring | typeof withDecay
407
+
408
+ if (animationType === 'timing') {
409
+ animation = withTiming
410
+ } else if (animationType === 'spring') {
411
+ animation = withSpring
412
+ } else if (animationType === 'decay') {
413
+ animation = withDecay
414
+ config = config || {
415
+ velocity: 2,
416
+ deceleration: 2,
417
+ }
418
+ } else {
419
+ animation = withSpring
420
+ }
421
+
422
+ return {
423
+ animation,
424
+ config,
425
+ repeatReverse,
426
+ repeatCount,
427
+ shouldRepeat: !!repeatCount,
428
+ }
429
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,4 @@
1
+ import './polyfill'
2
+
3
+ export * from './createAnimations'
4
+ export * from './useAnimatedNumber'
@@ -0,0 +1,4 @@
1
+ // for SSR
2
+ if (typeof requestAnimationFrame === 'undefined') {
3
+ globalThis['requestAnimationFrame'] = setImmediate
4
+ }
@@ -0,0 +1,73 @@
1
+ // there's some bug with reanimated likely related to worklets
2
+ // for now to not regress, we require react-native Animated alongside
3
+ export {
4
+ useAnimatedNumber,
5
+ useAnimatedNumberStyle,
6
+ useAnimatedNumberReaction,
7
+ } from '@tamagui/animations-react-native'
8
+
9
+ // import { UniversalAnimatedNumber } from '@tamagui/core'
10
+ // import {
11
+ // SharedValue,
12
+ // cancelAnimation,
13
+ // useAnimatedReaction,
14
+ // useAnimatedStyle,
15
+ // useSharedValue,
16
+ // withSpring,
17
+ // withTiming,
18
+ // } from 'react-native-reanimated'
19
+
20
+ // type ReanimatedAnimatedNumber = SharedValue<number>
21
+
22
+ // export function useAnimatedNumber(
23
+ // initial: number
24
+ // ): UniversalAnimatedNumber<ReanimatedAnimatedNumber> {
25
+ // const val = useSharedValue(initial)
26
+ // return {
27
+ // getInstance() {
28
+ // return val
29
+ // },
30
+ // getValue() {
31
+ // return val.value
32
+ // },
33
+ // stop() {
34
+ // cancelAnimation(val)
35
+ // },
36
+ // setValue(next: number, config = { type: 'spring' }) {
37
+ // if (config.type === 'direct') {
38
+ // val.value = next
39
+ // } else if (config.type === 'spring') {
40
+ // val.value = withSpring(next, config)
41
+ // } else {
42
+ // val.value = withTiming(next, config)
43
+ // }
44
+ // },
45
+ // }
46
+ // }
47
+
48
+ // export function useAnimatedNumberReaction(
49
+ // value: UniversalAnimatedNumber<ReanimatedAnimatedNumber>,
50
+ // cb: (current: number) => void
51
+ // ) {
52
+ // useAnimatedReaction(
53
+ // () => {
54
+ // 'worklet'
55
+ // return value.getValue()
56
+ // },
57
+ // (result, prev) => {
58
+ // 'worklet'
59
+ // if (result !== prev) cb(result)
60
+ // },
61
+ // [value]
62
+ // )
63
+ // }
64
+
65
+ // export function useAnimatedNumberStyle<V extends UniversalAnimatedNumber<ReanimatedAnimatedNumber>>(
66
+ // value: V,
67
+ // getStyle: (value: number) => any
68
+ // ) {
69
+ // return useAnimatedStyle(() => {
70
+ // 'worklet'
71
+ // return getStyle(value.getValue())
72
+ // })
73
+ // }
package/types/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  import './polyfill';
2
2
  export * from './createAnimations';
3
+ export * from './useAnimatedNumber';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { useAnimatedNumber, useAnimatedNumberStyle, useAnimatedNumberReaction, } from '@tamagui/animations-react-native';
2
+ //# sourceMappingURL=useAnimatedNumber.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createAnimations.d.ts","sourceRoot":"","sources":["../src/createAnimations.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAgC,MAAM,eAAe,CAAA;AAE7E,OAAiB,EACf,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAQjB,MAAM,yBAAyB,CAAA;AAEhC,aAAK,gBAAgB,CAAC,CAAC,SAAS,MAAM,GAAG,GAAG,IAAI;KAC7C,GAAG,IAAI,MAAM,CAAC,GAAG,eAAe;CAClC,CAAA;AAED,aAAK,eAAe,GAChB,CAAC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,gBAAgB,CAAC,GAChG,CAAC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,gBAAgB,CAAC,GAChG,CAAC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,eAAe,CAAC,CAAA;AASlG,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,EAAE,UAAU,EAAE,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAkK9F"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAA;AAEnB,cAAc,oBAAoB,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"polyfill.d.ts","sourceRoot":"","sources":["../src/polyfill.ts"],"names":[],"mappings":""}