react-native-puff-pop 1.0.6 → 1.0.7

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/README.md CHANGED
@@ -16,6 +16,10 @@ Works with both **React Native CLI** and **Expo** projects - no native dependenc
16
16
  ## Features
17
17
 
18
18
  - 🎎 **11 Animation Effects**: scale, rotate, fade, slideUp, slideDown, slideLeft, slideRight, bounce, flip, zoom, rotateScale
19
+ - ðŸŽŊ **Exit Animations**: Different effects for enter and exit animations
20
+ - 🔄 **Reverse Mode**: Reverse animation direction with a single prop
21
+ - 🎛ïļ **Custom Initial Values**: Fine-tune starting opacity, scale, rotation, and position
22
+ - 📊 **Animation Intensity**: Control how dramatic your animations are (0-1)
19
23
  - ðŸĶī **Skeleton Mode**: Reserve space before animation or expand from zero height
20
24
  - ⚡ **Native Driver Support**: Smooth 60fps animations
21
25
  - ðŸŽŊ **Easy to Use**: Just wrap your components with `<PuffPop>`
@@ -181,6 +185,173 @@ function App() {
181
185
  </PuffPop>
182
186
  ```
183
187
 
188
+ ### Exit Animation
189
+
190
+ Use different effects for enter and exit animations:
191
+
192
+ ```tsx
193
+ // Scale in, fade out
194
+ <PuffPop
195
+ effect="scale"
196
+ exitEffect="fade"
197
+ visible={isVisible}
198
+ >
199
+ <Modal />
200
+ </PuffPop>
201
+
202
+ // Slide up to enter, slide down to exit
203
+ <PuffPop
204
+ effect="slideUp"
205
+ exitEffect="slideDown"
206
+ exitDuration={200}
207
+ visible={isVisible}
208
+ >
209
+ <Toast />
210
+ </PuffPop>
211
+
212
+ // Different timing for enter and exit
213
+ <PuffPop
214
+ effect="zoom"
215
+ duration={400}
216
+ easing="spring"
217
+ exitEffect="fade"
218
+ exitDuration={150}
219
+ exitEasing="easeIn"
220
+ exitDelay={50}
221
+ visible={isVisible}
222
+ >
223
+ <Notification />
224
+ </PuffPop>
225
+ ```
226
+
227
+ With `PuffPopGroup`:
228
+
229
+ ```tsx
230
+ <PuffPopGroup
231
+ effect="slideUp"
232
+ exitEffect="fade"
233
+ exitDuration={150}
234
+ staggerDelay={50}
235
+ visible={isVisible}
236
+ >
237
+ <ListItem />
238
+ <ListItem />
239
+ <ListItem />
240
+ </PuffPopGroup>
241
+ ```
242
+
243
+ ### Custom Initial Values
244
+
245
+ Fine-tune the starting values of your animations:
246
+
247
+ ```tsx
248
+ // Start from 50% opacity instead of 0
249
+ <PuffPop initialOpacity={0.5}>
250
+ <Card />
251
+ </PuffPop>
252
+
253
+ // Start from a larger scale
254
+ <PuffPop effect="scale" initialScale={0.5}>
255
+ <Avatar />
256
+ </PuffPop>
257
+
258
+ // Custom slide distance
259
+ <PuffPop effect="slideUp" initialTranslateY={200}>
260
+ <Modal />
261
+ </PuffPop>
262
+
263
+ // Combine multiple custom values
264
+ <PuffPop
265
+ effect="rotateScale"
266
+ initialOpacity={0.3}
267
+ initialScale={0.2}
268
+ initialRotate={-90}
269
+ >
270
+ <Icon />
271
+ </PuffPop>
272
+ ```
273
+
274
+ ### Reverse Mode
275
+
276
+ Reverse the animation direction:
277
+
278
+ ```tsx
279
+ // slideUp now slides from top instead of bottom
280
+ <PuffPop effect="slideUp" reverse>
281
+ <Toast />
282
+ </PuffPop>
283
+
284
+ // slideLeft now slides from left instead of right
285
+ <PuffPop effect="slideLeft" reverse>
286
+ <Drawer />
287
+ </PuffPop>
288
+
289
+ // rotate spins clockwise instead of counter-clockwise
290
+ <PuffPop effect="rotate" reverse>
291
+ <Spinner />
292
+ </PuffPop>
293
+ ```
294
+
295
+ With `PuffPopGroup`:
296
+
297
+ ```tsx
298
+ <PuffPopGroup effect="slideRight" reverse staggerDelay={50}>
299
+ <MenuItem />
300
+ <MenuItem />
301
+ <MenuItem />
302
+ </PuffPopGroup>
303
+ ```
304
+
305
+ ### Animation Intensity
306
+
307
+ Control how dramatic your animations are with the `intensity` prop (0-1):
308
+
309
+ ```tsx
310
+ // Full animation (default)
311
+ <PuffPop effect="slideUp" intensity={1}>
312
+ <Card />
313
+ </PuffPop>
314
+
315
+ // Half the movement distance (25px instead of 50px)
316
+ <PuffPop effect="slideUp" intensity={0.5}>
317
+ <Card />
318
+ </PuffPop>
319
+
320
+ // Subtle animation (10px slide)
321
+ <PuffPop effect="slideUp" intensity={0.2}>
322
+ <Tooltip />
323
+ </PuffPop>
324
+
325
+ // No movement, just fade (intensity 0)
326
+ <PuffPop effect="slideUp" intensity={0}>
327
+ <Content />
328
+ </PuffPop>
329
+ ```
330
+
331
+ Works with all effects:
332
+
333
+ ```tsx
334
+ // Smaller scale range (starts at 0.5 instead of 0)
335
+ <PuffPop effect="scale" intensity={0.5}>
336
+ <Avatar />
337
+ </PuffPop>
338
+
339
+ // Less rotation (180deg instead of 360deg)
340
+ <PuffPop effect="rotate" intensity={0.5}>
341
+ <Icon />
342
+ </PuffPop>
343
+ ```
344
+
345
+ With `PuffPopGroup`:
346
+
347
+ ```tsx
348
+ <PuffPopGroup effect="slideLeft" intensity={0.3} staggerDelay={50}>
349
+ <ListItem />
350
+ <ListItem />
351
+ <ListItem />
352
+ </PuffPopGroup>
353
+ ```
354
+
184
355
  ## Props
185
356
 
186
357
  | Prop | Type | Default | Description |
@@ -200,6 +371,17 @@ function App() {
200
371
  | `loopDelay` | `number` | `0` | Delay between loop iterations in ms |
201
372
  | `respectReduceMotion` | `boolean` | `true` | Respect system reduce motion setting |
202
373
  | `testID` | `string` | - | Test ID for testing purposes |
374
+ | `exitEffect` | `PuffPopEffect` | - | Animation effect for exit (defaults to enter effect) |
375
+ | `exitDuration` | `number` | - | Duration for exit animation (defaults to duration) |
376
+ | `exitEasing` | `PuffPopEasing` | - | Easing for exit animation (defaults to easing) |
377
+ | `exitDelay` | `number` | `0` | Delay before exit animation starts in ms |
378
+ | `initialOpacity` | `number` | - | Custom initial opacity (0-1) |
379
+ | `initialScale` | `number` | - | Custom initial scale value |
380
+ | `initialRotate` | `number` | - | Custom initial rotation in degrees |
381
+ | `initialTranslateX` | `number` | - | Custom initial X translation in pixels |
382
+ | `initialTranslateY` | `number` | - | Custom initial Y translation in pixels |
383
+ | `reverse` | `boolean` | `false` | Reverse animation direction |
384
+ | `intensity` | `number` | `1` | Animation intensity multiplier (0-1) |
203
385
 
204
386
  ### PuffPopGroup Props
205
387
 
@@ -222,6 +404,17 @@ function App() {
222
404
  | `gap` | `number` | - | Gap between children |
223
405
  | `respectReduceMotion` | `boolean` | `true` | Respect system reduce motion setting |
224
406
  | `testID` | `string` | - | Test ID for testing purposes |
407
+ | `exitEffect` | `PuffPopEffect` | - | Exit animation effect for all children |
408
+ | `exitDuration` | `number` | - | Exit duration for all children |
409
+ | `exitEasing` | `PuffPopEasing` | - | Exit easing for all children |
410
+ | `exitDelay` | `number` | `0` | Exit delay for all children |
411
+ | `initialOpacity` | `number` | - | Custom initial opacity for all children |
412
+ | `initialScale` | `number` | - | Custom initial scale for all children |
413
+ | `initialRotate` | `number` | - | Custom initial rotation for all children |
414
+ | `initialTranslateX` | `number` | - | Custom initial X translation for all children |
415
+ | `initialTranslateY` | `number` | - | Custom initial Y translation for all children |
416
+ | `reverse` | `boolean` | `false` | Reverse animation direction for all children |
417
+ | `intensity` | `number` | `1` | Animation intensity multiplier for all children |
225
418
 
226
419
  ### Animation Effects (`PuffPopEffect`)
227
420
 
@@ -25,13 +25,47 @@ 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, onAnimationStart, style, animateOnMount = true, loop = false, loopDelay = 0, respectReduceMotion = true, testID, }) {
28
+ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0, easing = 'easeOut', skeleton = true, visible = true, onAnimationComplete, onAnimationStart, style, animateOnMount = true, loop = false, loopDelay = 0, respectReduceMotion = true, testID,
29
+ // Exit animation settings
30
+ exitEffect, exitDuration, exitEasing, exitDelay = 0,
31
+ // Custom initial values
32
+ initialOpacity, initialScale, initialRotate, initialTranslateX, initialTranslateY,
33
+ // Reverse mode
34
+ reverse = false,
35
+ // Animation intensity
36
+ intensity = 1, }) {
37
+ // Clamp intensity between 0 and 1
38
+ const clampedIntensity = Math.max(0, Math.min(1, intensity));
39
+ // Helper to get initial value with custom override, reverse, and intensity support
40
+ const getInitialOpacityValue = () => initialOpacity ?? 0;
41
+ const getInitialScaleValue = (eff) => {
42
+ if (initialScale !== undefined)
43
+ return initialScale;
44
+ const baseScale = getInitialScale(eff, reverse);
45
+ // Scale goes from baseScale to 1, so we interpolate: 1 - (1 - baseScale) * intensity
46
+ return 1 - (1 - baseScale) * clampedIntensity;
47
+ };
48
+ const getInitialRotateValue = (eff) => {
49
+ if (initialRotate !== undefined)
50
+ return initialRotate;
51
+ return getInitialRotate(eff, reverse) * clampedIntensity;
52
+ };
53
+ const getInitialTranslateXValue = (eff) => {
54
+ if (initialTranslateX !== undefined)
55
+ return initialTranslateX;
56
+ return getInitialTranslateX(eff, reverse) * clampedIntensity;
57
+ };
58
+ const getInitialTranslateYValue = (eff) => {
59
+ if (initialTranslateY !== undefined)
60
+ return initialTranslateY;
61
+ return getInitialTranslateY(eff, reverse) * clampedIntensity;
62
+ };
29
63
  // Animation values
30
- const opacity = useRef(new Animated.Value(animateOnMount ? 0 : 1)).current;
31
- const scale = useRef(new Animated.Value(animateOnMount ? getInitialScale(effect) : 1)).current;
32
- const rotate = useRef(new Animated.Value(animateOnMount ? getInitialRotate(effect) : 0)).current;
33
- const translateX = useRef(new Animated.Value(animateOnMount ? getInitialTranslateX(effect) : 0)).current;
34
- const translateY = useRef(new Animated.Value(animateOnMount ? getInitialTranslateY(effect) : 0)).current;
64
+ const opacity = useRef(new Animated.Value(animateOnMount ? getInitialOpacityValue() : 1)).current;
65
+ const scale = useRef(new Animated.Value(animateOnMount ? getInitialScaleValue(effect) : 1)).current;
66
+ const rotate = useRef(new Animated.Value(animateOnMount ? getInitialRotateValue(effect) : 0)).current;
67
+ const translateX = useRef(new Animated.Value(animateOnMount ? getInitialTranslateXValue(effect) : 0)).current;
68
+ const translateY = useRef(new Animated.Value(animateOnMount ? getInitialTranslateYValue(effect) : 0)).current;
35
69
  // For non-skeleton mode
36
70
  const [measuredHeight, setMeasuredHeight] = useState(null);
37
71
  const animatedHeight = useRef(new Animated.Value(0)).current;
@@ -55,15 +89,20 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
55
89
  }, [respectReduceMotion]);
56
90
  // Effective duration (0 if reduce motion is enabled)
57
91
  const effectiveDuration = respectReduceMotion && isReduceMotionEnabled ? 0 : duration;
92
+ const effectiveExitDuration = respectReduceMotion && isReduceMotionEnabled ? 0 : (exitDuration ?? duration);
93
+ // Helper to get effect flags for any effect type
94
+ const getEffectFlags = useCallback((eff) => ({
95
+ hasScale: ['scale', 'bounce', 'zoom', 'rotateScale', 'flip'].includes(eff),
96
+ hasRotate: ['rotate', 'rotateScale'].includes(eff),
97
+ hasFlip: eff === 'flip',
98
+ hasTranslateX: ['slideLeft', 'slideRight'].includes(eff),
99
+ hasTranslateY: ['slideUp', 'slideDown', 'bounce'].includes(eff),
100
+ hasRotateEffect: ['rotate', 'rotateScale', 'flip'].includes(eff),
101
+ }), []);
58
102
  // Memoize effect type checks to avoid repeated includes() calls
59
- const effectFlags = useMemo(() => ({
60
- hasScale: ['scale', 'bounce', 'zoom', 'rotateScale', 'flip'].includes(effect),
61
- hasRotate: ['rotate', 'rotateScale'].includes(effect),
62
- hasFlip: effect === 'flip',
63
- hasTranslateX: ['slideLeft', 'slideRight'].includes(effect),
64
- hasTranslateY: ['slideUp', 'slideDown', 'bounce'].includes(effect),
65
- hasRotateEffect: ['rotate', 'rotateScale', 'flip'].includes(effect),
66
- }), [effect]);
103
+ const effectFlags = useMemo(() => getEffectFlags(effect), [effect, getEffectFlags]);
104
+ // Exit effect flags (use exitEffect if specified, otherwise same as enter effect)
105
+ const exitEffectFlags = useMemo(() => exitEffect ? getEffectFlags(exitEffect) : effectFlags, [exitEffect, getEffectFlags, effectFlags]);
67
106
  // Memoize interpolations to avoid recreating on every render
68
107
  const rotateInterpolation = useMemo(() => rotate.interpolate({
69
108
  inputRange: [-360, 0, 360],
@@ -86,12 +125,18 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
86
125
  if (toVisible && onAnimationStart) {
87
126
  onAnimationStart();
88
127
  }
89
- const easingFn = getEasing(easing);
128
+ // Determine which effect settings to use based on direction
129
+ const currentEffect = toVisible ? effect : (exitEffect ?? effect);
130
+ const currentDuration = toVisible ? effectiveDuration : effectiveExitDuration;
131
+ const currentEasing = toVisible ? easing : (exitEasing ?? easing);
132
+ const currentDelay = toVisible ? delay : exitDelay;
133
+ const currentFlags = toVisible ? effectFlags : exitEffectFlags;
134
+ const easingFn = getEasing(currentEasing);
90
135
  // When skeleton is false, we animate height which doesn't support native driver
91
136
  // So we must use JS driver for all animations in that case
92
137
  const useNative = skeleton;
93
138
  const config = {
94
- duration: effectiveDuration,
139
+ duration: currentDuration,
95
140
  easing: easingFn,
96
141
  useNativeDriver: useNative,
97
142
  };
@@ -102,33 +147,33 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
102
147
  ...config,
103
148
  }));
104
149
  // Scale animation
105
- if (effectFlags.hasScale) {
106
- const targetScale = toVisible ? 1 : getInitialScale(effect);
150
+ if (currentFlags.hasScale) {
151
+ const targetScale = toVisible ? 1 : getInitialScaleValue(currentEffect);
107
152
  animations.push(Animated.timing(scale, {
108
153
  toValue: targetScale,
109
154
  ...config,
110
- easing: effect === 'bounce' ? Easing.bounce : easingFn,
155
+ easing: currentEffect === 'bounce' ? Easing.bounce : easingFn,
111
156
  }));
112
157
  }
113
158
  // Rotate animation
114
- if (effectFlags.hasRotate || effectFlags.hasFlip) {
115
- const targetRotate = toVisible ? 0 : getInitialRotate(effect);
159
+ if (currentFlags.hasRotate || currentFlags.hasFlip) {
160
+ const targetRotate = toVisible ? 0 : getInitialRotateValue(currentEffect);
116
161
  animations.push(Animated.timing(rotate, {
117
162
  toValue: targetRotate,
118
163
  ...config,
119
164
  }));
120
165
  }
121
166
  // TranslateX animation
122
- if (effectFlags.hasTranslateX) {
123
- const targetX = toVisible ? 0 : getInitialTranslateX(effect);
167
+ if (currentFlags.hasTranslateX) {
168
+ const targetX = toVisible ? 0 : getInitialTranslateXValue(currentEffect);
124
169
  animations.push(Animated.timing(translateX, {
125
170
  toValue: targetX,
126
171
  ...config,
127
172
  }));
128
173
  }
129
174
  // TranslateY animation
130
- if (effectFlags.hasTranslateY) {
131
- const targetY = toVisible ? 0 : getInitialTranslateY(effect);
175
+ if (currentFlags.hasTranslateY) {
176
+ const targetY = toVisible ? 0 : getInitialTranslateYValue(currentEffect);
132
177
  animations.push(Animated.timing(translateY, {
133
178
  toValue: targetY,
134
179
  ...config,
@@ -139,7 +184,7 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
139
184
  const targetHeight = toVisible ? measuredHeight : 0;
140
185
  animations.push(Animated.timing(animatedHeight, {
141
186
  toValue: targetHeight,
142
- duration: effectiveDuration,
187
+ duration: currentDuration,
143
188
  easing: easingFn,
144
189
  useNativeDriver: false,
145
190
  }));
@@ -148,20 +193,20 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
148
193
  const parallelAnimation = Animated.parallel(animations);
149
194
  // Reset values function for looping
150
195
  const resetValues = () => {
151
- opacity.setValue(0);
152
- scale.setValue(getInitialScale(effect));
153
- rotate.setValue(getInitialRotate(effect));
154
- translateX.setValue(getInitialTranslateX(effect));
155
- translateY.setValue(getInitialTranslateY(effect));
196
+ opacity.setValue(getInitialOpacityValue());
197
+ scale.setValue(getInitialScaleValue(effect));
198
+ rotate.setValue(getInitialRotateValue(effect));
199
+ translateX.setValue(getInitialTranslateXValue(effect));
200
+ translateY.setValue(getInitialTranslateYValue(effect));
156
201
  if (!skeleton && measuredHeight !== null) {
157
202
  animatedHeight.setValue(0);
158
203
  }
159
204
  };
160
205
  // Build the animation sequence
161
206
  let animation;
162
- if (delay > 0) {
207
+ if (currentDelay > 0) {
163
208
  animation = Animated.sequence([
164
- Animated.delay(delay),
209
+ Animated.delay(currentDelay),
165
210
  parallelAnimation,
166
211
  ]);
167
212
  }
@@ -230,9 +275,14 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
230
275
  }, [
231
276
  delay,
232
277
  effectiveDuration,
278
+ effectiveExitDuration,
233
279
  easing,
234
280
  effect,
235
281
  effectFlags,
282
+ exitEffect,
283
+ exitEffectFlags,
284
+ exitEasing,
285
+ exitDelay,
236
286
  measuredHeight,
237
287
  onAnimationComplete,
238
288
  onAnimationStart,
@@ -316,7 +366,8 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
316
366
  /**
317
367
  * Get initial scale value based on effect
318
368
  */
319
- function getInitialScale(effect) {
369
+ function getInitialScale(effect, _reverse = false) {
370
+ // Scale doesn't change with reverse (parameter kept for consistent API)
320
371
  switch (effect) {
321
372
  case 'scale':
322
373
  case 'rotateScale':
@@ -334,14 +385,15 @@ function getInitialScale(effect) {
334
385
  /**
335
386
  * Get initial rotate value based on effect
336
387
  */
337
- function getInitialRotate(effect) {
388
+ function getInitialRotate(effect, reverse = false) {
389
+ const multiplier = reverse ? -1 : 1;
338
390
  switch (effect) {
339
391
  case 'rotate':
340
- return -360;
392
+ return -360 * multiplier;
341
393
  case 'rotateScale':
342
- return -180;
394
+ return -180 * multiplier;
343
395
  case 'flip':
344
- return -180;
396
+ return -180 * multiplier;
345
397
  default:
346
398
  return 0;
347
399
  }
@@ -349,12 +401,13 @@ function getInitialRotate(effect) {
349
401
  /**
350
402
  * Get initial translateX value based on effect
351
403
  */
352
- function getInitialTranslateX(effect) {
404
+ function getInitialTranslateX(effect, reverse = false) {
405
+ const multiplier = reverse ? -1 : 1;
353
406
  switch (effect) {
354
407
  case 'slideLeft':
355
- return 100;
408
+ return 100 * multiplier;
356
409
  case 'slideRight':
357
- return -100;
410
+ return -100 * multiplier;
358
411
  default:
359
412
  return 0;
360
413
  }
@@ -362,14 +415,15 @@ function getInitialTranslateX(effect) {
362
415
  /**
363
416
  * Get initial translateY value based on effect
364
417
  */
365
- function getInitialTranslateY(effect) {
418
+ function getInitialTranslateY(effect, reverse = false) {
419
+ const multiplier = reverse ? -1 : 1;
366
420
  switch (effect) {
367
421
  case 'slideUp':
368
- return 50;
422
+ return 50 * multiplier;
369
423
  case 'slideDown':
370
- return -50;
424
+ return -50 * multiplier;
371
425
  case 'bounce':
372
- return 30;
426
+ return 30 * multiplier;
373
427
  default:
374
428
  return 0;
375
429
  }
@@ -398,7 +452,15 @@ const styles = StyleSheet.create({
398
452
  * </PuffPopGroup>
399
453
  * ```
400
454
  */
401
- export function PuffPopGroup({ children, effect = 'scale', duration = 400, staggerDelay = 100, initialDelay = 0, easing = 'easeOut', skeleton = true, visible = true, animateOnMount = true, onAnimationComplete, onAnimationStart, style, respectReduceMotion = true, testID, staggerDirection = 'forward', horizontal = false, gap, }) {
455
+ export function PuffPopGroup({ children, effect = 'scale', duration = 400, staggerDelay = 100, initialDelay = 0, easing = 'easeOut', skeleton = true, visible = true, animateOnMount = true, onAnimationComplete, onAnimationStart, style, respectReduceMotion = true, testID, staggerDirection = 'forward', horizontal = false, gap,
456
+ // Custom initial values
457
+ initialOpacity, initialScale, initialRotate, initialTranslateX, initialTranslateY,
458
+ // Reverse mode
459
+ reverse,
460
+ // Animation intensity
461
+ intensity,
462
+ // Exit animation settings
463
+ exitEffect, exitDuration, exitEasing, exitDelay, }) {
402
464
  const childArray = Children.toArray(children);
403
465
  const childCount = childArray.length;
404
466
  const completedCount = useRef(0);
@@ -457,6 +519,6 @@ export function PuffPopGroup({ children, effect = 'scale', duration = 400, stagg
457
519
  }
458
520
  return baseStyle;
459
521
  }, [horizontal, gap]);
460
- return (_jsx(View, { style: [styles.groupContainer, containerStyle, style], testID: testID, children: childArray.map((child, index) => (_jsx(PuffPop, { effect: effect, duration: duration, delay: getChildDelay(index), easing: easing, skeleton: skeleton, visible: visible, animateOnMount: animateOnMount, onAnimationComplete: handleChildComplete, onAnimationStart: index === 0 ? handleChildStart : undefined, respectReduceMotion: respectReduceMotion, children: child }, index))) }));
522
+ return (_jsx(View, { style: [styles.groupContainer, containerStyle, style], testID: testID, children: childArray.map((child, index) => (_jsx(PuffPop, { effect: effect, duration: duration, delay: getChildDelay(index), easing: easing, skeleton: skeleton, visible: visible, animateOnMount: animateOnMount, onAnimationComplete: handleChildComplete, onAnimationStart: index === 0 ? handleChildStart : undefined, respectReduceMotion: respectReduceMotion, initialOpacity: initialOpacity, initialScale: initialScale, initialRotate: initialRotate, initialTranslateX: initialTranslateX, initialTranslateY: initialTranslateY, reverse: reverse, intensity: intensity, exitEffect: exitEffect, exitDuration: exitDuration, exitEasing: exitEasing, exitDelay: exitDelay, children: child }, index))) }));
461
523
  }
462
524
  export default PuffPop;
@@ -83,11 +83,73 @@ export interface PuffPopProps {
83
83
  * Test ID for testing purposes
84
84
  */
85
85
  testID?: string;
86
+ /**
87
+ * Animation effect type when hiding (exit animation)
88
+ * If not specified, uses the reverse of the enter effect
89
+ */
90
+ exitEffect?: PuffPopEffect;
91
+ /**
92
+ * Animation duration for exit animation in milliseconds
93
+ * If not specified, uses the same duration as enter animation
94
+ */
95
+ exitDuration?: number;
96
+ /**
97
+ * Easing function for exit animation
98
+ * If not specified, uses the same easing as enter animation
99
+ */
100
+ exitEasing?: PuffPopEasing;
101
+ /**
102
+ * Delay before exit animation starts in milliseconds
103
+ * @default 0
104
+ */
105
+ exitDelay?: number;
106
+ /**
107
+ * Custom initial opacity value (0-1)
108
+ * Overrides the default initial opacity for the effect
109
+ */
110
+ initialOpacity?: number;
111
+ /**
112
+ * Custom initial scale value
113
+ * Overrides the default initial scale for effects like 'scale', 'zoom', 'bounce'
114
+ */
115
+ initialScale?: number;
116
+ /**
117
+ * Custom initial rotation value in degrees
118
+ * Overrides the default initial rotation for effects like 'rotate', 'rotateScale'
119
+ */
120
+ initialRotate?: number;
121
+ /**
122
+ * Custom initial translateX value in pixels
123
+ * Overrides the default initial translateX for effects like 'slideLeft', 'slideRight'
124
+ */
125
+ initialTranslateX?: number;
126
+ /**
127
+ * Custom initial translateY value in pixels
128
+ * Overrides the default initial translateY for effects like 'slideUp', 'slideDown'
129
+ */
130
+ initialTranslateY?: number;
131
+ /**
132
+ * If true, reverses the animation direction
133
+ * - slideUp becomes slide from top
134
+ * - slideLeft becomes slide from left
135
+ * - rotate spins clockwise instead of counter-clockwise
136
+ * @default false
137
+ */
138
+ reverse?: boolean;
139
+ /**
140
+ * Animation intensity multiplier (0-1)
141
+ * Controls how far/much elements move during animation
142
+ * - 1 = full animation (default)
143
+ * - 0.5 = half the movement distance
144
+ * - 0 = no movement (instant appear)
145
+ * @default 1
146
+ */
147
+ intensity?: number;
86
148
  }
87
149
  /**
88
150
  * PuffPop - Animate children with beautiful entrance effects
89
151
  */
90
- export declare function PuffPop({ children, effect, duration, delay, easing, skeleton, visible, onAnimationComplete, onAnimationStart, style, animateOnMount, loop, loopDelay, respectReduceMotion, testID, }: PuffPopProps): ReactElement;
152
+ export declare function PuffPop({ children, effect, duration, delay, easing, skeleton, visible, onAnimationComplete, onAnimationStart, style, animateOnMount, loop, loopDelay, respectReduceMotion, testID, exitEffect, exitDuration, exitEasing, exitDelay, initialOpacity, initialScale, initialRotate, initialTranslateX, initialTranslateY, reverse, intensity, }: PuffPopProps): ReactElement;
91
153
  /**
92
154
  * Props for PuffPopGroup component
93
155
  */
@@ -175,6 +237,57 @@ export interface PuffPopGroupProps {
175
237
  * Gap between children (uses flexbox gap)
176
238
  */
177
239
  gap?: number;
240
+ /**
241
+ * Custom initial opacity value (0-1) for all children
242
+ */
243
+ initialOpacity?: number;
244
+ /**
245
+ * Custom initial scale value for all children
246
+ */
247
+ initialScale?: number;
248
+ /**
249
+ * Custom initial rotation value in degrees for all children
250
+ */
251
+ initialRotate?: number;
252
+ /**
253
+ * Custom initial translateX value in pixels for all children
254
+ */
255
+ initialTranslateX?: number;
256
+ /**
257
+ * Custom initial translateY value in pixels for all children
258
+ */
259
+ initialTranslateY?: number;
260
+ /**
261
+ * If true, reverses the animation direction for all children
262
+ * @default false
263
+ */
264
+ reverse?: boolean;
265
+ /**
266
+ * Animation intensity multiplier (0-1) for all children
267
+ * Controls how far/much elements move during animation
268
+ * @default 1
269
+ */
270
+ intensity?: number;
271
+ /**
272
+ * Animation effect type when hiding (exit animation) for all children
273
+ * If not specified, uses the reverse of the enter effect
274
+ */
275
+ exitEffect?: PuffPopEffect;
276
+ /**
277
+ * Animation duration for exit animation in milliseconds for all children
278
+ * If not specified, uses the same duration as enter animation
279
+ */
280
+ exitDuration?: number;
281
+ /**
282
+ * Easing function for exit animation for all children
283
+ * If not specified, uses the same easing as enter animation
284
+ */
285
+ exitEasing?: PuffPopEasing;
286
+ /**
287
+ * Delay before exit animation starts in milliseconds
288
+ * @default 0
289
+ */
290
+ exitDelay?: number;
178
291
  }
179
292
  /**
180
293
  * PuffPopGroup - Animate multiple children with staggered entrance effects
@@ -188,5 +301,5 @@ export interface PuffPopGroupProps {
188
301
  * </PuffPopGroup>
189
302
  * ```
190
303
  */
191
- export declare function PuffPopGroup({ children, effect, duration, staggerDelay, initialDelay, easing, skeleton, visible, animateOnMount, onAnimationComplete, onAnimationStart, style, respectReduceMotion, testID, staggerDirection, horizontal, gap, }: PuffPopGroupProps): ReactElement;
304
+ export declare function PuffPopGroup({ children, effect, duration, staggerDelay, initialDelay, easing, skeleton, visible, animateOnMount, onAnimationComplete, onAnimationStart, style, respectReduceMotion, testID, staggerDirection, horizontal, gap, initialOpacity, initialScale, initialRotate, initialTranslateX, initialTranslateY, reverse, intensity, exitEffect, exitDuration, exitEasing, exitDelay, }: PuffPopGroupProps): ReactElement;
192
305
  export default PuffPop;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-puff-pop",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
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
@@ -135,6 +135,87 @@ export interface PuffPopProps {
135
135
  * Test ID for testing purposes
136
136
  */
137
137
  testID?: string;
138
+
139
+ // ============ Exit Animation Settings ============
140
+
141
+ /**
142
+ * Animation effect type when hiding (exit animation)
143
+ * If not specified, uses the reverse of the enter effect
144
+ */
145
+ exitEffect?: PuffPopEffect;
146
+
147
+ /**
148
+ * Animation duration for exit animation in milliseconds
149
+ * If not specified, uses the same duration as enter animation
150
+ */
151
+ exitDuration?: number;
152
+
153
+ /**
154
+ * Easing function for exit animation
155
+ * If not specified, uses the same easing as enter animation
156
+ */
157
+ exitEasing?: PuffPopEasing;
158
+
159
+ /**
160
+ * Delay before exit animation starts in milliseconds
161
+ * @default 0
162
+ */
163
+ exitDelay?: number;
164
+
165
+ // ============ Custom Initial Values ============
166
+
167
+ /**
168
+ * Custom initial opacity value (0-1)
169
+ * Overrides the default initial opacity for the effect
170
+ */
171
+ initialOpacity?: number;
172
+
173
+ /**
174
+ * Custom initial scale value
175
+ * Overrides the default initial scale for effects like 'scale', 'zoom', 'bounce'
176
+ */
177
+ initialScale?: number;
178
+
179
+ /**
180
+ * Custom initial rotation value in degrees
181
+ * Overrides the default initial rotation for effects like 'rotate', 'rotateScale'
182
+ */
183
+ initialRotate?: number;
184
+
185
+ /**
186
+ * Custom initial translateX value in pixels
187
+ * Overrides the default initial translateX for effects like 'slideLeft', 'slideRight'
188
+ */
189
+ initialTranslateX?: number;
190
+
191
+ /**
192
+ * Custom initial translateY value in pixels
193
+ * Overrides the default initial translateY for effects like 'slideUp', 'slideDown'
194
+ */
195
+ initialTranslateY?: number;
196
+
197
+ // ============ Reverse Mode ============
198
+
199
+ /**
200
+ * If true, reverses the animation direction
201
+ * - slideUp becomes slide from top
202
+ * - slideLeft becomes slide from left
203
+ * - rotate spins clockwise instead of counter-clockwise
204
+ * @default false
205
+ */
206
+ reverse?: boolean;
207
+
208
+ // ============ Animation Intensity ============
209
+
210
+ /**
211
+ * Animation intensity multiplier (0-1)
212
+ * Controls how far/much elements move during animation
213
+ * - 1 = full animation (default)
214
+ * - 0.5 = half the movement distance
215
+ * - 0 = no movement (instant appear)
216
+ * @default 1
217
+ */
218
+ intensity?: number;
138
219
  }
139
220
 
140
221
  /**
@@ -178,13 +259,52 @@ export function PuffPop({
178
259
  loopDelay = 0,
179
260
  respectReduceMotion = true,
180
261
  testID,
262
+ // Exit animation settings
263
+ exitEffect,
264
+ exitDuration,
265
+ exitEasing,
266
+ exitDelay = 0,
267
+ // Custom initial values
268
+ initialOpacity,
269
+ initialScale,
270
+ initialRotate,
271
+ initialTranslateX,
272
+ initialTranslateY,
273
+ // Reverse mode
274
+ reverse = false,
275
+ // Animation intensity
276
+ intensity = 1,
181
277
  }: PuffPopProps): ReactElement {
278
+ // Clamp intensity between 0 and 1
279
+ const clampedIntensity = Math.max(0, Math.min(1, intensity));
280
+
281
+ // Helper to get initial value with custom override, reverse, and intensity support
282
+ const getInitialOpacityValue = () => initialOpacity ?? 0;
283
+ const getInitialScaleValue = (eff: PuffPopEffect) => {
284
+ if (initialScale !== undefined) return initialScale;
285
+ const baseScale = getInitialScale(eff, reverse);
286
+ // Scale goes from baseScale to 1, so we interpolate: 1 - (1 - baseScale) * intensity
287
+ return 1 - (1 - baseScale) * clampedIntensity;
288
+ };
289
+ const getInitialRotateValue = (eff: PuffPopEffect) => {
290
+ if (initialRotate !== undefined) return initialRotate;
291
+ return getInitialRotate(eff, reverse) * clampedIntensity;
292
+ };
293
+ const getInitialTranslateXValue = (eff: PuffPopEffect) => {
294
+ if (initialTranslateX !== undefined) return initialTranslateX;
295
+ return getInitialTranslateX(eff, reverse) * clampedIntensity;
296
+ };
297
+ const getInitialTranslateYValue = (eff: PuffPopEffect) => {
298
+ if (initialTranslateY !== undefined) return initialTranslateY;
299
+ return getInitialTranslateY(eff, reverse) * clampedIntensity;
300
+ };
301
+
182
302
  // Animation values
183
- const opacity = useRef(new Animated.Value(animateOnMount ? 0 : 1)).current;
184
- const scale = useRef(new Animated.Value(animateOnMount ? getInitialScale(effect) : 1)).current;
185
- const rotate = useRef(new Animated.Value(animateOnMount ? getInitialRotate(effect) : 0)).current;
186
- const translateX = useRef(new Animated.Value(animateOnMount ? getInitialTranslateX(effect) : 0)).current;
187
- const translateY = useRef(new Animated.Value(animateOnMount ? getInitialTranslateY(effect) : 0)).current;
303
+ const opacity = useRef(new Animated.Value(animateOnMount ? getInitialOpacityValue() : 1)).current;
304
+ const scale = useRef(new Animated.Value(animateOnMount ? getInitialScaleValue(effect) : 1)).current;
305
+ const rotate = useRef(new Animated.Value(animateOnMount ? getInitialRotateValue(effect) : 0)).current;
306
+ const translateX = useRef(new Animated.Value(animateOnMount ? getInitialTranslateXValue(effect) : 0)).current;
307
+ const translateY = useRef(new Animated.Value(animateOnMount ? getInitialTranslateYValue(effect) : 0)).current;
188
308
 
189
309
  // For non-skeleton mode
190
310
  const [measuredHeight, setMeasuredHeight] = useState<number | null>(null);
@@ -218,16 +338,25 @@ export function PuffPop({
218
338
 
219
339
  // Effective duration (0 if reduce motion is enabled)
220
340
  const effectiveDuration = respectReduceMotion && isReduceMotionEnabled ? 0 : duration;
341
+ const effectiveExitDuration = respectReduceMotion && isReduceMotionEnabled ? 0 : (exitDuration ?? duration);
342
+
343
+ // Helper to get effect flags for any effect type
344
+ const getEffectFlags = useCallback((eff: PuffPopEffect) => ({
345
+ hasScale: ['scale', 'bounce', 'zoom', 'rotateScale', 'flip'].includes(eff),
346
+ hasRotate: ['rotate', 'rotateScale'].includes(eff),
347
+ hasFlip: eff === 'flip',
348
+ hasTranslateX: ['slideLeft', 'slideRight'].includes(eff),
349
+ hasTranslateY: ['slideUp', 'slideDown', 'bounce'].includes(eff),
350
+ hasRotateEffect: ['rotate', 'rotateScale', 'flip'].includes(eff),
351
+ }), []);
221
352
 
222
353
  // Memoize effect type checks to avoid repeated includes() calls
223
- const effectFlags = useMemo(() => ({
224
- hasScale: ['scale', 'bounce', 'zoom', 'rotateScale', 'flip'].includes(effect),
225
- hasRotate: ['rotate', 'rotateScale'].includes(effect),
226
- hasFlip: effect === 'flip',
227
- hasTranslateX: ['slideLeft', 'slideRight'].includes(effect),
228
- hasTranslateY: ['slideUp', 'slideDown', 'bounce'].includes(effect),
229
- hasRotateEffect: ['rotate', 'rotateScale', 'flip'].includes(effect),
230
- }), [effect]);
354
+ const effectFlags = useMemo(() => getEffectFlags(effect), [effect, getEffectFlags]);
355
+
356
+ // Exit effect flags (use exitEffect if specified, otherwise same as enter effect)
357
+ const exitEffectFlags = useMemo(() =>
358
+ exitEffect ? getEffectFlags(exitEffect) : effectFlags
359
+ , [exitEffect, getEffectFlags, effectFlags]);
231
360
 
232
361
  // Memoize interpolations to avoid recreating on every render
233
362
  const rotateInterpolation = useMemo(() =>
@@ -261,12 +390,19 @@ export function PuffPop({
261
390
  onAnimationStart();
262
391
  }
263
392
 
264
- const easingFn = getEasing(easing);
393
+ // Determine which effect settings to use based on direction
394
+ const currentEffect = toVisible ? effect : (exitEffect ?? effect);
395
+ const currentDuration = toVisible ? effectiveDuration : effectiveExitDuration;
396
+ const currentEasing = toVisible ? easing : (exitEasing ?? easing);
397
+ const currentDelay = toVisible ? delay : exitDelay;
398
+ const currentFlags = toVisible ? effectFlags : exitEffectFlags;
399
+
400
+ const easingFn = getEasing(currentEasing);
265
401
  // When skeleton is false, we animate height which doesn't support native driver
266
402
  // So we must use JS driver for all animations in that case
267
403
  const useNative = skeleton;
268
404
  const config = {
269
- duration: effectiveDuration,
405
+ duration: currentDuration,
270
406
  easing: easingFn,
271
407
  useNativeDriver: useNative,
272
408
  };
@@ -282,20 +418,20 @@ export function PuffPop({
282
418
  );
283
419
 
284
420
  // Scale animation
285
- if (effectFlags.hasScale) {
286
- const targetScale = toVisible ? 1 : getInitialScale(effect);
421
+ if (currentFlags.hasScale) {
422
+ const targetScale = toVisible ? 1 : getInitialScaleValue(currentEffect);
287
423
  animations.push(
288
424
  Animated.timing(scale, {
289
425
  toValue: targetScale,
290
426
  ...config,
291
- easing: effect === 'bounce' ? Easing.bounce : easingFn,
427
+ easing: currentEffect === 'bounce' ? Easing.bounce : easingFn,
292
428
  })
293
429
  );
294
430
  }
295
431
 
296
432
  // Rotate animation
297
- if (effectFlags.hasRotate || effectFlags.hasFlip) {
298
- const targetRotate = toVisible ? 0 : getInitialRotate(effect);
433
+ if (currentFlags.hasRotate || currentFlags.hasFlip) {
434
+ const targetRotate = toVisible ? 0 : getInitialRotateValue(currentEffect);
299
435
  animations.push(
300
436
  Animated.timing(rotate, {
301
437
  toValue: targetRotate,
@@ -305,8 +441,8 @@ export function PuffPop({
305
441
  }
306
442
 
307
443
  // TranslateX animation
308
- if (effectFlags.hasTranslateX) {
309
- const targetX = toVisible ? 0 : getInitialTranslateX(effect);
444
+ if (currentFlags.hasTranslateX) {
445
+ const targetX = toVisible ? 0 : getInitialTranslateXValue(currentEffect);
310
446
  animations.push(
311
447
  Animated.timing(translateX, {
312
448
  toValue: targetX,
@@ -316,8 +452,8 @@ export function PuffPop({
316
452
  }
317
453
 
318
454
  // TranslateY animation
319
- if (effectFlags.hasTranslateY) {
320
- const targetY = toVisible ? 0 : getInitialTranslateY(effect);
455
+ if (currentFlags.hasTranslateY) {
456
+ const targetY = toVisible ? 0 : getInitialTranslateYValue(currentEffect);
321
457
  animations.push(
322
458
  Animated.timing(translateY, {
323
459
  toValue: targetY,
@@ -332,7 +468,7 @@ export function PuffPop({
332
468
  animations.push(
333
469
  Animated.timing(animatedHeight, {
334
470
  toValue: targetHeight,
335
- duration: effectiveDuration,
471
+ duration: currentDuration,
336
472
  easing: easingFn,
337
473
  useNativeDriver: false,
338
474
  })
@@ -344,11 +480,11 @@ export function PuffPop({
344
480
 
345
481
  // Reset values function for looping
346
482
  const resetValues = () => {
347
- opacity.setValue(0);
348
- scale.setValue(getInitialScale(effect));
349
- rotate.setValue(getInitialRotate(effect));
350
- translateX.setValue(getInitialTranslateX(effect));
351
- translateY.setValue(getInitialTranslateY(effect));
483
+ opacity.setValue(getInitialOpacityValue());
484
+ scale.setValue(getInitialScaleValue(effect));
485
+ rotate.setValue(getInitialRotateValue(effect));
486
+ translateX.setValue(getInitialTranslateXValue(effect));
487
+ translateY.setValue(getInitialTranslateYValue(effect));
352
488
  if (!skeleton && measuredHeight !== null) {
353
489
  animatedHeight.setValue(0);
354
490
  }
@@ -357,9 +493,9 @@ export function PuffPop({
357
493
  // Build the animation sequence
358
494
  let animation: Animated.CompositeAnimation;
359
495
 
360
- if (delay > 0) {
496
+ if (currentDelay > 0) {
361
497
  animation = Animated.sequence([
362
- Animated.delay(delay),
498
+ Animated.delay(currentDelay),
363
499
  parallelAnimation,
364
500
  ]);
365
501
  } else {
@@ -430,9 +566,14 @@ export function PuffPop({
430
566
  [
431
567
  delay,
432
568
  effectiveDuration,
569
+ effectiveExitDuration,
433
570
  easing,
434
571
  effect,
435
572
  effectFlags,
573
+ exitEffect,
574
+ exitEffectFlags,
575
+ exitEasing,
576
+ exitDelay,
436
577
  measuredHeight,
437
578
  onAnimationComplete,
438
579
  onAnimationStart,
@@ -543,7 +684,8 @@ export function PuffPop({
543
684
  /**
544
685
  * Get initial scale value based on effect
545
686
  */
546
- function getInitialScale(effect: PuffPopEffect): number {
687
+ function getInitialScale(effect: PuffPopEffect, _reverse = false): number {
688
+ // Scale doesn't change with reverse (parameter kept for consistent API)
547
689
  switch (effect) {
548
690
  case 'scale':
549
691
  case 'rotateScale':
@@ -562,14 +704,15 @@ function getInitialScale(effect: PuffPopEffect): number {
562
704
  /**
563
705
  * Get initial rotate value based on effect
564
706
  */
565
- function getInitialRotate(effect: PuffPopEffect): number {
707
+ function getInitialRotate(effect: PuffPopEffect, reverse = false): number {
708
+ const multiplier = reverse ? -1 : 1;
566
709
  switch (effect) {
567
710
  case 'rotate':
568
- return -360;
711
+ return -360 * multiplier;
569
712
  case 'rotateScale':
570
- return -180;
713
+ return -180 * multiplier;
571
714
  case 'flip':
572
- return -180;
715
+ return -180 * multiplier;
573
716
  default:
574
717
  return 0;
575
718
  }
@@ -578,12 +721,13 @@ function getInitialRotate(effect: PuffPopEffect): number {
578
721
  /**
579
722
  * Get initial translateX value based on effect
580
723
  */
581
- function getInitialTranslateX(effect: PuffPopEffect): number {
724
+ function getInitialTranslateX(effect: PuffPopEffect, reverse = false): number {
725
+ const multiplier = reverse ? -1 : 1;
582
726
  switch (effect) {
583
727
  case 'slideLeft':
584
- return 100;
728
+ return 100 * multiplier;
585
729
  case 'slideRight':
586
- return -100;
730
+ return -100 * multiplier;
587
731
  default:
588
732
  return 0;
589
733
  }
@@ -592,14 +736,15 @@ function getInitialTranslateX(effect: PuffPopEffect): number {
592
736
  /**
593
737
  * Get initial translateY value based on effect
594
738
  */
595
- function getInitialTranslateY(effect: PuffPopEffect): number {
739
+ function getInitialTranslateY(effect: PuffPopEffect, reverse = false): number {
740
+ const multiplier = reverse ? -1 : 1;
596
741
  switch (effect) {
597
742
  case 'slideUp':
598
- return 50;
743
+ return 50 * multiplier;
599
744
  case 'slideDown':
600
- return -50;
745
+ return -50 * multiplier;
601
746
  case 'bounce':
602
- return 30;
747
+ return 30 * multiplier;
603
748
  default:
604
749
  return 0;
605
750
  }
@@ -721,6 +866,76 @@ export interface PuffPopGroupProps {
721
866
  * Gap between children (uses flexbox gap)
722
867
  */
723
868
  gap?: number;
869
+
870
+ // ============ Custom Initial Values ============
871
+
872
+ /**
873
+ * Custom initial opacity value (0-1) for all children
874
+ */
875
+ initialOpacity?: number;
876
+
877
+ /**
878
+ * Custom initial scale value for all children
879
+ */
880
+ initialScale?: number;
881
+
882
+ /**
883
+ * Custom initial rotation value in degrees for all children
884
+ */
885
+ initialRotate?: number;
886
+
887
+ /**
888
+ * Custom initial translateX value in pixels for all children
889
+ */
890
+ initialTranslateX?: number;
891
+
892
+ /**
893
+ * Custom initial translateY value in pixels for all children
894
+ */
895
+ initialTranslateY?: number;
896
+
897
+ // ============ Reverse Mode ============
898
+
899
+ /**
900
+ * If true, reverses the animation direction for all children
901
+ * @default false
902
+ */
903
+ reverse?: boolean;
904
+
905
+ // ============ Animation Intensity ============
906
+
907
+ /**
908
+ * Animation intensity multiplier (0-1) for all children
909
+ * Controls how far/much elements move during animation
910
+ * @default 1
911
+ */
912
+ intensity?: number;
913
+
914
+ // ============ Exit Animation Settings ============
915
+
916
+ /**
917
+ * Animation effect type when hiding (exit animation) for all children
918
+ * If not specified, uses the reverse of the enter effect
919
+ */
920
+ exitEffect?: PuffPopEffect;
921
+
922
+ /**
923
+ * Animation duration for exit animation in milliseconds for all children
924
+ * If not specified, uses the same duration as enter animation
925
+ */
926
+ exitDuration?: number;
927
+
928
+ /**
929
+ * Easing function for exit animation for all children
930
+ * If not specified, uses the same easing as enter animation
931
+ */
932
+ exitEasing?: PuffPopEasing;
933
+
934
+ /**
935
+ * Delay before exit animation starts in milliseconds
936
+ * @default 0
937
+ */
938
+ exitDelay?: number;
724
939
  }
725
940
 
726
941
  /**
@@ -753,6 +968,21 @@ export function PuffPopGroup({
753
968
  staggerDirection = 'forward',
754
969
  horizontal = false,
755
970
  gap,
971
+ // Custom initial values
972
+ initialOpacity,
973
+ initialScale,
974
+ initialRotate,
975
+ initialTranslateX,
976
+ initialTranslateY,
977
+ // Reverse mode
978
+ reverse,
979
+ // Animation intensity
980
+ intensity,
981
+ // Exit animation settings
982
+ exitEffect,
983
+ exitDuration,
984
+ exitEasing,
985
+ exitDelay,
756
986
  }: PuffPopGroupProps): ReactElement {
757
987
  const childArray = Children.toArray(children);
758
988
  const childCount = childArray.length;
@@ -838,6 +1068,17 @@ export function PuffPopGroup({
838
1068
  onAnimationComplete={handleChildComplete}
839
1069
  onAnimationStart={index === 0 ? handleChildStart : undefined}
840
1070
  respectReduceMotion={respectReduceMotion}
1071
+ initialOpacity={initialOpacity}
1072
+ initialScale={initialScale}
1073
+ initialRotate={initialRotate}
1074
+ initialTranslateX={initialTranslateX}
1075
+ initialTranslateY={initialTranslateY}
1076
+ reverse={reverse}
1077
+ intensity={intensity}
1078
+ exitEffect={exitEffect}
1079
+ exitDuration={exitDuration}
1080
+ exitEasing={exitEasing}
1081
+ exitDelay={exitDelay}
841
1082
  >
842
1083
  {child}
843
1084
  </PuffPop>