react-native-puff-pop 1.0.2 → 1.0.3
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/lib/module/index.js +59 -38
- package/package.json +1 -1
- package/src/index.tsx +69 -43
package/lib/module/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef, useState, useCallback, } from 'react';
|
|
2
|
+
import { useEffect, useRef, useState, useCallback, useMemo, } from 'react';
|
|
3
3
|
import { View, Animated, StyleSheet, Easing, } from 'react-native';
|
|
4
4
|
/**
|
|
5
5
|
* Get easing function based on type
|
|
@@ -37,6 +37,25 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
|
|
|
37
37
|
const animatedHeight = useRef(new Animated.Value(0)).current;
|
|
38
38
|
const hasAnimated = useRef(false);
|
|
39
39
|
const loopAnimationRef = useRef(null);
|
|
40
|
+
const loopTimeoutRef = useRef(null);
|
|
41
|
+
// Memoize effect type checks to avoid repeated includes() calls
|
|
42
|
+
const effectFlags = useMemo(() => ({
|
|
43
|
+
hasScale: ['scale', 'bounce', 'zoom', 'rotateScale', 'flip'].includes(effect),
|
|
44
|
+
hasRotate: ['rotate', 'rotateScale'].includes(effect),
|
|
45
|
+
hasFlip: effect === 'flip',
|
|
46
|
+
hasTranslateX: ['slideLeft', 'slideRight'].includes(effect),
|
|
47
|
+
hasTranslateY: ['slideUp', 'slideDown', 'bounce'].includes(effect),
|
|
48
|
+
hasRotateEffect: ['rotate', 'rotateScale', 'flip'].includes(effect),
|
|
49
|
+
}), [effect]);
|
|
50
|
+
// Memoize interpolations to avoid recreating on every render
|
|
51
|
+
const rotateInterpolation = useMemo(() => rotate.interpolate({
|
|
52
|
+
inputRange: [-360, 0, 360],
|
|
53
|
+
outputRange: ['-360deg', '0deg', '360deg'],
|
|
54
|
+
}), [rotate]);
|
|
55
|
+
const flipInterpolation = useMemo(() => rotate.interpolate({
|
|
56
|
+
inputRange: [-180, 0],
|
|
57
|
+
outputRange: ['-180deg', '0deg'],
|
|
58
|
+
}), [rotate]);
|
|
40
59
|
// Handle layout measurement for non-skeleton mode
|
|
41
60
|
const onLayout = useCallback((event) => {
|
|
42
61
|
if (!skeleton && measuredHeight === null) {
|
|
@@ -62,7 +81,7 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
|
|
|
62
81
|
...config,
|
|
63
82
|
}));
|
|
64
83
|
// Scale animation
|
|
65
|
-
if (
|
|
84
|
+
if (effectFlags.hasScale) {
|
|
66
85
|
const targetScale = toVisible ? 1 : getInitialScale(effect);
|
|
67
86
|
animations.push(Animated.timing(scale, {
|
|
68
87
|
toValue: targetScale,
|
|
@@ -71,7 +90,7 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
|
|
|
71
90
|
}));
|
|
72
91
|
}
|
|
73
92
|
// Rotate animation
|
|
74
|
-
if (
|
|
93
|
+
if (effectFlags.hasRotate || effectFlags.hasFlip) {
|
|
75
94
|
const targetRotate = toVisible ? 0 : getInitialRotate(effect);
|
|
76
95
|
animations.push(Animated.timing(rotate, {
|
|
77
96
|
toValue: targetRotate,
|
|
@@ -79,7 +98,7 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
|
|
|
79
98
|
}));
|
|
80
99
|
}
|
|
81
100
|
// TranslateX animation
|
|
82
|
-
if (
|
|
101
|
+
if (effectFlags.hasTranslateX) {
|
|
83
102
|
const targetX = toVisible ? 0 : getInitialTranslateX(effect);
|
|
84
103
|
animations.push(Animated.timing(translateX, {
|
|
85
104
|
toValue: targetX,
|
|
@@ -87,7 +106,7 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
|
|
|
87
106
|
}));
|
|
88
107
|
}
|
|
89
108
|
// TranslateY animation
|
|
90
|
-
if (
|
|
109
|
+
if (effectFlags.hasTranslateY) {
|
|
91
110
|
const targetY = toVisible ? 0 : getInitialTranslateY(effect);
|
|
92
111
|
animations.push(Animated.timing(translateY, {
|
|
93
112
|
toValue: targetY,
|
|
@@ -144,7 +163,11 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
|
|
|
144
163
|
if (loopCount === -1 || currentIteration < loopCount) {
|
|
145
164
|
// Add delay between loops if specified
|
|
146
165
|
if (loopDelay > 0) {
|
|
147
|
-
|
|
166
|
+
// Clear any existing timeout before setting a new one
|
|
167
|
+
if (loopTimeoutRef.current) {
|
|
168
|
+
clearTimeout(loopTimeoutRef.current);
|
|
169
|
+
}
|
|
170
|
+
loopTimeoutRef.current = setTimeout(runLoop, loopDelay);
|
|
148
171
|
}
|
|
149
172
|
else {
|
|
150
173
|
runLoop();
|
|
@@ -176,6 +199,7 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
|
|
|
176
199
|
duration,
|
|
177
200
|
easing,
|
|
178
201
|
effect,
|
|
202
|
+
effectFlags,
|
|
179
203
|
measuredHeight,
|
|
180
204
|
onAnimationComplete,
|
|
181
205
|
opacity,
|
|
@@ -201,44 +225,30 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
|
|
|
201
225
|
animate(visible);
|
|
202
226
|
}
|
|
203
227
|
}, [visible, animate]);
|
|
204
|
-
// Cleanup loop animation on unmount
|
|
228
|
+
// Cleanup loop animation and timeout on unmount
|
|
205
229
|
useEffect(() => {
|
|
206
230
|
return () => {
|
|
207
231
|
if (loopAnimationRef.current) {
|
|
208
232
|
loopAnimationRef.current.stop();
|
|
209
233
|
}
|
|
234
|
+
if (loopTimeoutRef.current) {
|
|
235
|
+
clearTimeout(loopTimeoutRef.current);
|
|
236
|
+
}
|
|
210
237
|
};
|
|
211
238
|
}, []);
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
// Build transform based on effect
|
|
217
|
-
const getTransform = () => {
|
|
218
|
-
const hasScale = ['scale', 'bounce', 'zoom', 'rotateScale', 'flip'].includes(effect);
|
|
219
|
-
const hasRotate = ['rotate', 'rotateScale'].includes(effect);
|
|
220
|
-
const hasFlip = effect === 'flip';
|
|
221
|
-
const hasTranslateX = ['slideLeft', 'slideRight'].includes(effect);
|
|
222
|
-
const hasTranslateY = ['slideUp', 'slideDown', 'bounce'].includes(effect);
|
|
239
|
+
// Memoize transform array to avoid recreating on every render
|
|
240
|
+
// IMPORTANT: All hooks must be called before any conditional returns
|
|
241
|
+
const transform = useMemo(() => {
|
|
242
|
+
const { hasScale, hasRotate, hasFlip, hasTranslateX, hasTranslateY } = effectFlags;
|
|
223
243
|
const transforms = [];
|
|
224
244
|
if (hasScale) {
|
|
225
245
|
transforms.push({ scale });
|
|
226
246
|
}
|
|
227
247
|
if (hasRotate) {
|
|
228
|
-
transforms.push({
|
|
229
|
-
rotate: rotate.interpolate({
|
|
230
|
-
inputRange: [-360, 0, 360],
|
|
231
|
-
outputRange: ['-360deg', '0deg', '360deg'],
|
|
232
|
-
}),
|
|
233
|
-
});
|
|
248
|
+
transforms.push({ rotate: rotateInterpolation });
|
|
234
249
|
}
|
|
235
250
|
if (hasFlip) {
|
|
236
|
-
transforms.push({
|
|
237
|
-
rotateY: rotate.interpolate({
|
|
238
|
-
inputRange: [-180, 0],
|
|
239
|
-
outputRange: ['-180deg', '0deg'],
|
|
240
|
-
}),
|
|
241
|
-
});
|
|
251
|
+
transforms.push({ rotateY: flipInterpolation });
|
|
242
252
|
}
|
|
243
253
|
if (hasTranslateX) {
|
|
244
254
|
transforms.push({ translateX });
|
|
@@ -247,15 +257,26 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
|
|
|
247
257
|
transforms.push({ translateY });
|
|
248
258
|
}
|
|
249
259
|
return transforms.length > 0 ? transforms : undefined;
|
|
250
|
-
};
|
|
251
|
-
|
|
260
|
+
}, [effectFlags, scale, rotateInterpolation, flipInterpolation, translateX, translateY]);
|
|
261
|
+
// Memoize animated style
|
|
262
|
+
const animatedStyle = useMemo(() => ({
|
|
252
263
|
opacity,
|
|
253
|
-
transform
|
|
254
|
-
};
|
|
255
|
-
//
|
|
256
|
-
const containerAnimatedStyle =
|
|
257
|
-
|
|
258
|
-
|
|
264
|
+
transform,
|
|
265
|
+
}), [opacity, transform]);
|
|
266
|
+
// Memoize container style for non-skeleton mode
|
|
267
|
+
const containerAnimatedStyle = useMemo(() => {
|
|
268
|
+
if (!skeleton && measuredHeight !== null) {
|
|
269
|
+
return {
|
|
270
|
+
height: animatedHeight,
|
|
271
|
+
overflow: effectFlags.hasRotateEffect ? 'visible' : 'hidden'
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
return {};
|
|
275
|
+
}, [skeleton, measuredHeight, animatedHeight, effectFlags.hasRotateEffect]);
|
|
276
|
+
// For non-skeleton mode, measure first (after all hooks)
|
|
277
|
+
if (!skeleton && measuredHeight === null) {
|
|
278
|
+
return (_jsx(View, { style: styles.measureContainer, onLayout: onLayout, children: _jsx(View, { style: styles.hidden, children: children }) }));
|
|
279
|
+
}
|
|
259
280
|
return (_jsx(Animated.View, { style: [styles.container, style, containerAnimatedStyle], children: _jsx(Animated.View, { style: animatedStyle, children: children }) }));
|
|
260
281
|
}
|
|
261
282
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-puff-pop",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "A React Native animation library for revealing children components with beautiful puff and pop effects",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
package/src/index.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
useRef,
|
|
4
4
|
useState,
|
|
5
5
|
useCallback,
|
|
6
|
+
useMemo,
|
|
6
7
|
type ReactNode,
|
|
7
8
|
type ReactElement,
|
|
8
9
|
} from 'react';
|
|
@@ -168,6 +169,30 @@ export function PuffPop({
|
|
|
168
169
|
const animatedHeight = useRef(new Animated.Value(0)).current;
|
|
169
170
|
const hasAnimated = useRef(false);
|
|
170
171
|
const loopAnimationRef = useRef<Animated.CompositeAnimation | null>(null);
|
|
172
|
+
const loopTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
173
|
+
|
|
174
|
+
// Memoize effect type checks to avoid repeated includes() calls
|
|
175
|
+
const effectFlags = useMemo(() => ({
|
|
176
|
+
hasScale: ['scale', 'bounce', 'zoom', 'rotateScale', 'flip'].includes(effect),
|
|
177
|
+
hasRotate: ['rotate', 'rotateScale'].includes(effect),
|
|
178
|
+
hasFlip: effect === 'flip',
|
|
179
|
+
hasTranslateX: ['slideLeft', 'slideRight'].includes(effect),
|
|
180
|
+
hasTranslateY: ['slideUp', 'slideDown', 'bounce'].includes(effect),
|
|
181
|
+
hasRotateEffect: ['rotate', 'rotateScale', 'flip'].includes(effect),
|
|
182
|
+
}), [effect]);
|
|
183
|
+
|
|
184
|
+
// Memoize interpolations to avoid recreating on every render
|
|
185
|
+
const rotateInterpolation = useMemo(() =>
|
|
186
|
+
rotate.interpolate({
|
|
187
|
+
inputRange: [-360, 0, 360],
|
|
188
|
+
outputRange: ['-360deg', '0deg', '360deg'],
|
|
189
|
+
}), [rotate]);
|
|
190
|
+
|
|
191
|
+
const flipInterpolation = useMemo(() =>
|
|
192
|
+
rotate.interpolate({
|
|
193
|
+
inputRange: [-180, 0],
|
|
194
|
+
outputRange: ['-180deg', '0deg'],
|
|
195
|
+
}), [rotate]);
|
|
171
196
|
|
|
172
197
|
// Handle layout measurement for non-skeleton mode
|
|
173
198
|
const onLayout = useCallback(
|
|
@@ -204,7 +229,7 @@ export function PuffPop({
|
|
|
204
229
|
);
|
|
205
230
|
|
|
206
231
|
// Scale animation
|
|
207
|
-
if (
|
|
232
|
+
if (effectFlags.hasScale) {
|
|
208
233
|
const targetScale = toVisible ? 1 : getInitialScale(effect);
|
|
209
234
|
animations.push(
|
|
210
235
|
Animated.timing(scale, {
|
|
@@ -216,7 +241,7 @@ export function PuffPop({
|
|
|
216
241
|
}
|
|
217
242
|
|
|
218
243
|
// Rotate animation
|
|
219
|
-
if (
|
|
244
|
+
if (effectFlags.hasRotate || effectFlags.hasFlip) {
|
|
220
245
|
const targetRotate = toVisible ? 0 : getInitialRotate(effect);
|
|
221
246
|
animations.push(
|
|
222
247
|
Animated.timing(rotate, {
|
|
@@ -227,7 +252,7 @@ export function PuffPop({
|
|
|
227
252
|
}
|
|
228
253
|
|
|
229
254
|
// TranslateX animation
|
|
230
|
-
if (
|
|
255
|
+
if (effectFlags.hasTranslateX) {
|
|
231
256
|
const targetX = toVisible ? 0 : getInitialTranslateX(effect);
|
|
232
257
|
animations.push(
|
|
233
258
|
Animated.timing(translateX, {
|
|
@@ -238,7 +263,7 @@ export function PuffPop({
|
|
|
238
263
|
}
|
|
239
264
|
|
|
240
265
|
// TranslateY animation
|
|
241
|
-
if (
|
|
266
|
+
if (effectFlags.hasTranslateY) {
|
|
242
267
|
const targetY = toVisible ? 0 : getInitialTranslateY(effect);
|
|
243
268
|
animations.push(
|
|
244
269
|
Animated.timing(translateY, {
|
|
@@ -306,7 +331,11 @@ export function PuffPop({
|
|
|
306
331
|
if (loopCount === -1 || currentIteration < loopCount) {
|
|
307
332
|
// Add delay between loops if specified
|
|
308
333
|
if (loopDelay > 0) {
|
|
309
|
-
|
|
334
|
+
// Clear any existing timeout before setting a new one
|
|
335
|
+
if (loopTimeoutRef.current) {
|
|
336
|
+
clearTimeout(loopTimeoutRef.current);
|
|
337
|
+
}
|
|
338
|
+
loopTimeoutRef.current = setTimeout(runLoop, loopDelay);
|
|
310
339
|
} else {
|
|
311
340
|
runLoop();
|
|
312
341
|
}
|
|
@@ -338,6 +367,7 @@ export function PuffPop({
|
|
|
338
367
|
duration,
|
|
339
368
|
easing,
|
|
340
369
|
effect,
|
|
370
|
+
effectFlags,
|
|
341
371
|
measuredHeight,
|
|
342
372
|
onAnimationComplete,
|
|
343
373
|
opacity,
|
|
@@ -367,32 +397,22 @@ export function PuffPop({
|
|
|
367
397
|
}
|
|
368
398
|
}, [visible, animate]);
|
|
369
399
|
|
|
370
|
-
// Cleanup loop animation on unmount
|
|
400
|
+
// Cleanup loop animation and timeout on unmount
|
|
371
401
|
useEffect(() => {
|
|
372
402
|
return () => {
|
|
373
403
|
if (loopAnimationRef.current) {
|
|
374
404
|
loopAnimationRef.current.stop();
|
|
375
405
|
}
|
|
406
|
+
if (loopTimeoutRef.current) {
|
|
407
|
+
clearTimeout(loopTimeoutRef.current);
|
|
408
|
+
}
|
|
376
409
|
};
|
|
377
410
|
}, []);
|
|
378
411
|
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
<View style={styles.hidden}>{children}</View>
|
|
384
|
-
</View>
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Build transform based on effect
|
|
389
|
-
const getTransform = () => {
|
|
390
|
-
const hasScale = ['scale', 'bounce', 'zoom', 'rotateScale', 'flip'].includes(effect);
|
|
391
|
-
const hasRotate = ['rotate', 'rotateScale'].includes(effect);
|
|
392
|
-
const hasFlip = effect === 'flip';
|
|
393
|
-
const hasTranslateX = ['slideLeft', 'slideRight'].includes(effect);
|
|
394
|
-
const hasTranslateY = ['slideUp', 'slideDown', 'bounce'].includes(effect);
|
|
395
|
-
|
|
412
|
+
// Memoize transform array to avoid recreating on every render
|
|
413
|
+
// IMPORTANT: All hooks must be called before any conditional returns
|
|
414
|
+
const transform = useMemo(() => {
|
|
415
|
+
const { hasScale, hasRotate, hasFlip, hasTranslateX, hasTranslateY } = effectFlags;
|
|
396
416
|
const transforms = [];
|
|
397
417
|
|
|
398
418
|
if (hasScale) {
|
|
@@ -400,21 +420,11 @@ export function PuffPop({
|
|
|
400
420
|
}
|
|
401
421
|
|
|
402
422
|
if (hasRotate) {
|
|
403
|
-
transforms.push({
|
|
404
|
-
rotate: rotate.interpolate({
|
|
405
|
-
inputRange: [-360, 0, 360],
|
|
406
|
-
outputRange: ['-360deg', '0deg', '360deg'],
|
|
407
|
-
}),
|
|
408
|
-
});
|
|
423
|
+
transforms.push({ rotate: rotateInterpolation });
|
|
409
424
|
}
|
|
410
425
|
|
|
411
426
|
if (hasFlip) {
|
|
412
|
-
transforms.push({
|
|
413
|
-
rotateY: rotate.interpolate({
|
|
414
|
-
inputRange: [-180, 0],
|
|
415
|
-
outputRange: ['-180deg', '0deg'],
|
|
416
|
-
}),
|
|
417
|
-
});
|
|
427
|
+
transforms.push({ rotateY: flipInterpolation });
|
|
418
428
|
}
|
|
419
429
|
|
|
420
430
|
if (hasTranslateX) {
|
|
@@ -426,17 +436,33 @@ export function PuffPop({
|
|
|
426
436
|
}
|
|
427
437
|
|
|
428
438
|
return transforms.length > 0 ? transforms : undefined;
|
|
429
|
-
};
|
|
439
|
+
}, [effectFlags, scale, rotateInterpolation, flipInterpolation, translateX, translateY]);
|
|
430
440
|
|
|
431
|
-
|
|
441
|
+
// Memoize animated style
|
|
442
|
+
const animatedStyle = useMemo(() => ({
|
|
432
443
|
opacity,
|
|
433
|
-
transform
|
|
434
|
-
};
|
|
444
|
+
transform,
|
|
445
|
+
}), [opacity, transform]);
|
|
446
|
+
|
|
447
|
+
// Memoize container style for non-skeleton mode
|
|
448
|
+
const containerAnimatedStyle = useMemo(() => {
|
|
449
|
+
if (!skeleton && measuredHeight !== null) {
|
|
450
|
+
return {
|
|
451
|
+
height: animatedHeight,
|
|
452
|
+
overflow: effectFlags.hasRotateEffect ? 'visible' as const : 'hidden' as const
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
return {};
|
|
456
|
+
}, [skeleton, measuredHeight, animatedHeight, effectFlags.hasRotateEffect]);
|
|
435
457
|
|
|
436
|
-
//
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
458
|
+
// For non-skeleton mode, measure first (after all hooks)
|
|
459
|
+
if (!skeleton && measuredHeight === null) {
|
|
460
|
+
return (
|
|
461
|
+
<View style={styles.measureContainer} onLayout={onLayout}>
|
|
462
|
+
<View style={styles.hidden}>{children}</View>
|
|
463
|
+
</View>
|
|
464
|
+
);
|
|
465
|
+
}
|
|
440
466
|
|
|
441
467
|
return (
|
|
442
468
|
<Animated.View style={[styles.container, style, containerAnimatedStyle]}>
|