react-native-puff-pop 1.0.1 → 1.0.2

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/LICENSE CHANGED
@@ -20,3 +20,4 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
22
 
23
+
package/README.md CHANGED
@@ -133,6 +133,25 @@ function App() {
133
133
  }
134
134
  ```
135
135
 
136
+ ### Loop Animation
137
+
138
+ ```tsx
139
+ // Loop infinitely
140
+ <PuffPop effect="rotate" loop={true}>
141
+ <LoadingSpinner />
142
+ </PuffPop>
143
+
144
+ // Loop 3 times
145
+ <PuffPop effect="bounce" loop={3}>
146
+ <NotificationBadge />
147
+ </PuffPop>
148
+
149
+ // Loop with delay between iterations
150
+ <PuffPop effect="scale" loop={true} loopDelay={500}>
151
+ <PulsingDot />
152
+ </PuffPop>
153
+ ```
154
+
136
155
  ## Props
137
156
 
138
157
  | Prop | Type | Default | Description |
@@ -147,6 +166,8 @@ function App() {
147
166
  | `animateOnMount` | `boolean` | `true` | Animate when component mounts |
148
167
  | `onAnimationComplete` | `() => void` | - | Callback when animation completes |
149
168
  | `style` | `ViewStyle` | - | Custom container style |
169
+ | `loop` | `boolean \| number` | `false` | Loop animation (true=infinite, number=times) |
170
+ | `loopDelay` | `number` | `0` | Delay between loop iterations in ms |
150
171
 
151
172
  ### Animation Effects (`PuffPopEffect`)
152
173
 
@@ -25,7 +25,7 @@ function getEasing(type) {
25
25
  /**
26
26
  * PuffPop - Animate children with beautiful entrance effects
27
27
  */
28
- export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0, easing = 'easeOut', skeleton = true, visible = true, onAnimationComplete, style, animateOnMount = true, }) {
28
+ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0, easing = 'easeOut', skeleton = true, visible = true, onAnimationComplete, style, animateOnMount = true, loop = false, loopDelay = 0, }) {
29
29
  // Animation values
30
30
  const opacity = useRef(new Animated.Value(animateOnMount ? 0 : 1)).current;
31
31
  const scale = useRef(new Animated.Value(animateOnMount ? getInitialScale(effect) : 1)).current;
@@ -36,6 +36,7 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
36
36
  const [measuredHeight, setMeasuredHeight] = useState(null);
37
37
  const animatedHeight = useRef(new Animated.Value(0)).current;
38
38
  const hasAnimated = useRef(false);
39
+ const loopAnimationRef = useRef(null);
39
40
  // Handle layout measurement for non-skeleton mode
40
41
  const onLayout = useCallback((event) => {
41
42
  if (!skeleton && measuredHeight === null) {
@@ -105,18 +106,66 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
105
106
  }
106
107
  // Run animations with delay
107
108
  const parallelAnimation = Animated.parallel(animations);
109
+ // Reset values function for looping
110
+ const resetValues = () => {
111
+ opacity.setValue(0);
112
+ scale.setValue(getInitialScale(effect));
113
+ rotate.setValue(getInitialRotate(effect));
114
+ translateX.setValue(getInitialTranslateX(effect));
115
+ translateY.setValue(getInitialTranslateY(effect));
116
+ if (!skeleton && measuredHeight !== null) {
117
+ animatedHeight.setValue(0);
118
+ }
119
+ };
120
+ // Build the animation sequence
121
+ let animation;
108
122
  if (delay > 0) {
109
- Animated.sequence([
123
+ animation = Animated.sequence([
110
124
  Animated.delay(delay),
111
125
  parallelAnimation,
112
- ]).start(() => {
113
- if (toVisible && onAnimationComplete) {
114
- onAnimationComplete();
115
- }
116
- });
126
+ ]);
117
127
  }
118
128
  else {
119
- parallelAnimation.start(() => {
129
+ animation = parallelAnimation;
130
+ }
131
+ // Handle loop option
132
+ if (toVisible && loop) {
133
+ // Stop any existing loop animation
134
+ if (loopAnimationRef.current) {
135
+ loopAnimationRef.current.stop();
136
+ }
137
+ const loopCount = typeof loop === 'number' ? loop : -1;
138
+ let currentIteration = 0;
139
+ const runLoop = () => {
140
+ resetValues();
141
+ animation.start(({ finished }) => {
142
+ if (finished) {
143
+ currentIteration++;
144
+ if (loopCount === -1 || currentIteration < loopCount) {
145
+ // Add delay between loops if specified
146
+ if (loopDelay > 0) {
147
+ setTimeout(runLoop, loopDelay);
148
+ }
149
+ else {
150
+ runLoop();
151
+ }
152
+ }
153
+ else if (onAnimationComplete) {
154
+ onAnimationComplete();
155
+ }
156
+ }
157
+ });
158
+ };
159
+ // Store reference and start
160
+ runLoop();
161
+ }
162
+ else {
163
+ // Stop any existing loop animation
164
+ if (loopAnimationRef.current) {
165
+ loopAnimationRef.current.stop();
166
+ loopAnimationRef.current = null;
167
+ }
168
+ animation.start(() => {
120
169
  if (toVisible && onAnimationComplete) {
121
170
  onAnimationComplete();
122
171
  }
@@ -136,6 +185,8 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
136
185
  translateX,
137
186
  translateY,
138
187
  animatedHeight,
188
+ loop,
189
+ loopDelay,
139
190
  ]);
140
191
  // Handle initial mount animation
141
192
  useEffect(() => {
@@ -150,6 +201,14 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
150
201
  animate(visible);
151
202
  }
152
203
  }, [visible, animate]);
204
+ // Cleanup loop animation on unmount
205
+ useEffect(() => {
206
+ return () => {
207
+ if (loopAnimationRef.current) {
208
+ loopAnimationRef.current.stop();
209
+ }
210
+ };
211
+ }, []);
153
212
  // For non-skeleton mode, measure first
154
213
  if (!skeleton && measuredHeight === null) {
155
214
  return (_jsx(View, { style: styles.measureContainer, onLayout: onLayout, children: _jsx(View, { style: styles.hidden, children: children }) }));
@@ -57,9 +57,21 @@ export interface PuffPopProps {
57
57
  * @default true
58
58
  */
59
59
  animateOnMount?: boolean;
60
+ /**
61
+ * Loop the animation
62
+ * - true: loop infinitely
63
+ * - number: loop specific number of times
64
+ * @default false
65
+ */
66
+ loop?: boolean | number;
67
+ /**
68
+ * Delay between loop iterations in milliseconds
69
+ * @default 0
70
+ */
71
+ loopDelay?: number;
60
72
  }
61
73
  /**
62
74
  * PuffPop - Animate children with beautiful entrance effects
63
75
  */
64
- export declare function PuffPop({ children, effect, duration, delay, easing, skeleton, visible, onAnimationComplete, style, animateOnMount, }: PuffPopProps): ReactElement;
76
+ export declare function PuffPop({ children, effect, duration, delay, easing, skeleton, visible, onAnimationComplete, style, animateOnMount, loop, loopDelay, }: PuffPopProps): ReactElement;
65
77
  export default PuffPop;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-puff-pop",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
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
@@ -101,6 +101,20 @@ export interface PuffPopProps {
101
101
  * @default true
102
102
  */
103
103
  animateOnMount?: boolean;
104
+
105
+ /**
106
+ * Loop the animation
107
+ * - true: loop infinitely
108
+ * - number: loop specific number of times
109
+ * @default false
110
+ */
111
+ loop?: boolean | number;
112
+
113
+ /**
114
+ * Delay between loop iterations in milliseconds
115
+ * @default 0
116
+ */
117
+ loopDelay?: number;
104
118
  }
105
119
 
106
120
  /**
@@ -139,6 +153,8 @@ export function PuffPop({
139
153
  onAnimationComplete,
140
154
  style,
141
155
  animateOnMount = true,
156
+ loop = false,
157
+ loopDelay = 0,
142
158
  }: PuffPopProps): ReactElement {
143
159
  // Animation values
144
160
  const opacity = useRef(new Animated.Value(animateOnMount ? 0 : 1)).current;
@@ -151,6 +167,7 @@ export function PuffPop({
151
167
  const [measuredHeight, setMeasuredHeight] = useState<number | null>(null);
152
168
  const animatedHeight = useRef(new Animated.Value(0)).current;
153
169
  const hasAnimated = useRef(false);
170
+ const loopAnimationRef = useRef<Animated.CompositeAnimation | null>(null);
154
171
 
155
172
  // Handle layout measurement for non-skeleton mode
156
173
  const onLayout = useCallback(
@@ -246,18 +263,70 @@ export function PuffPop({
246
263
 
247
264
  // Run animations with delay
248
265
  const parallelAnimation = Animated.parallel(animations);
266
+
267
+ // Reset values function for looping
268
+ const resetValues = () => {
269
+ opacity.setValue(0);
270
+ scale.setValue(getInitialScale(effect));
271
+ rotate.setValue(getInitialRotate(effect));
272
+ translateX.setValue(getInitialTranslateX(effect));
273
+ translateY.setValue(getInitialTranslateY(effect));
274
+ if (!skeleton && measuredHeight !== null) {
275
+ animatedHeight.setValue(0);
276
+ }
277
+ };
278
+
279
+ // Build the animation sequence
280
+ let animation: Animated.CompositeAnimation;
249
281
 
250
282
  if (delay > 0) {
251
- Animated.sequence([
283
+ animation = Animated.sequence([
252
284
  Animated.delay(delay),
253
285
  parallelAnimation,
254
- ]).start(() => {
255
- if (toVisible && onAnimationComplete) {
256
- onAnimationComplete();
257
- }
258
- });
286
+ ]);
287
+ } else {
288
+ animation = parallelAnimation;
289
+ }
290
+
291
+ // Handle loop option
292
+ if (toVisible && loop) {
293
+ // Stop any existing loop animation
294
+ if (loopAnimationRef.current) {
295
+ loopAnimationRef.current.stop();
296
+ }
297
+
298
+ const loopCount = typeof loop === 'number' ? loop : -1;
299
+ let currentIteration = 0;
300
+
301
+ const runLoop = () => {
302
+ resetValues();
303
+ animation.start(({ finished }) => {
304
+ if (finished) {
305
+ currentIteration++;
306
+ if (loopCount === -1 || currentIteration < loopCount) {
307
+ // Add delay between loops if specified
308
+ if (loopDelay > 0) {
309
+ setTimeout(runLoop, loopDelay);
310
+ } else {
311
+ runLoop();
312
+ }
313
+ } else if (onAnimationComplete) {
314
+ onAnimationComplete();
315
+ }
316
+ }
317
+ });
318
+ };
319
+
320
+ // Store reference and start
321
+ runLoop();
259
322
  } else {
260
- parallelAnimation.start(() => {
323
+ // Stop any existing loop animation
324
+ if (loopAnimationRef.current) {
325
+ loopAnimationRef.current.stop();
326
+ loopAnimationRef.current = null;
327
+ }
328
+
329
+ animation.start(() => {
261
330
  if (toVisible && onAnimationComplete) {
262
331
  onAnimationComplete();
263
332
  }
@@ -278,6 +347,8 @@ export function PuffPop({
278
347
  translateX,
279
348
  translateY,
280
349
  animatedHeight,
350
+ loop,
351
+ loopDelay,
281
352
  ]
282
353
  );
283
354
 
@@ -296,6 +367,15 @@ export function PuffPop({
296
367
  }
297
368
  }, [visible, animate]);
298
369
 
370
+ // Cleanup loop animation on unmount
371
+ useEffect(() => {
372
+ return () => {
373
+ if (loopAnimationRef.current) {
374
+ loopAnimationRef.current.stop();
375
+ }
376
+ };
377
+ }, []);
378
+
299
379
  // For non-skeleton mode, measure first
300
380
  if (!skeleton && measuredHeight === null) {
301
381
  return (