@telus-uds/components-base 1.83.0 → 1.84.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.
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useState, useRef, useEffect, useCallback, useMemo, forwardRef } from 'react';
2
2
  import View from "react-native-web/dist/exports/View";
3
3
  import Animated from "react-native-web/dist/exports/Animated";
4
4
  import PanResponder from "react-native-web/dist/exports/PanResponder";
@@ -22,6 +22,11 @@ import CarouselTabsPanelItem from './CarouselTabs/CarouselTabsPanelItem';
22
22
  import dictionary from './dictionary';
23
23
  import { jsx as _jsx } from "react/jsx-runtime";
24
24
  import { jsxs as _jsxs } from "react/jsx-runtime";
25
+ const TRANSITION_MODES = {
26
+ MANUAL: 'manual',
27
+ AUTOMATIC: 'automatic',
28
+ SWIPE: 'swipe'
29
+ };
25
30
  const staticStyles = StyleSheet.create({
26
31
  root: {
27
32
  backgroundColor: 'transparent',
@@ -30,6 +35,12 @@ const staticStyles = StyleSheet.create({
30
35
  position: 'relative',
31
36
  top: 0,
32
37
  left: 0
38
+ },
39
+ animationControlButton: {
40
+ position: 'absolute',
41
+ zIndex: 1,
42
+ right: Platform.OS === 'web' ? undefined : 40,
43
+ top: 40
33
44
  }
34
45
  });
35
46
  const selectContainerStyles = width => ({
@@ -42,12 +53,29 @@ const selectSwipeAreaStyles = (count, width) => ({
42
53
  justifyContent: 'space-between',
43
54
  flexDirection: 'row'
44
55
  });
56
+ const getDynamicPositionProperty = areStylesAppliedOnPreviousButton => areStylesAppliedOnPreviousButton ? 'left' : 'right';
57
+ const selectControlButtonPositionStyles = _ref => {
58
+ let {
59
+ positionVariant,
60
+ buttonWidth,
61
+ positionProperty = getDynamicPositionProperty(),
62
+ spaceBetweenSlideAndButton
63
+ } = _ref;
64
+ const styles = {};
65
+ if (positionVariant === 'edge') {
66
+ styles[positionProperty] = -1 * (buttonWidth / 2);
67
+ } else if (positionVariant === 'inside') {
68
+ styles[positionProperty] = 0;
69
+ } else if (positionVariant === 'outside') {
70
+ styles[positionProperty] = -1 * (spaceBetweenSlideAndButton + buttonWidth);
71
+ }
72
+ return styles;
73
+ };
45
74
  const selectPreviousNextNavigationButtonStyles = (previousNextNavigationButtonWidth, previousNextNavigationPosition, spaceBetweenSlideAndPreviousNextNavigation, isFirstSlide, isLastSlide, areStylesAppliedOnPreviousButton) => {
46
75
  const styles = {
47
76
  zIndex: 1,
48
77
  position: 'absolute'
49
78
  };
50
- const dynamicPositionProperty = areStylesAppliedOnPreviousButton ? 'left' : 'right';
51
79
  if (isFirstSlide) {
52
80
  styles.visibility = areStylesAppliedOnPreviousButton ? 'hidden' : 'visible';
53
81
  } else if (isLastSlide) {
@@ -55,19 +83,20 @@ const selectPreviousNextNavigationButtonStyles = (previousNextNavigationButtonWi
55
83
  } else {
56
84
  styles.visibility = 'visible';
57
85
  }
58
- if (previousNextNavigationPosition === 'edge') {
59
- styles[dynamicPositionProperty] = -1 * (previousNextNavigationButtonWidth / 2);
60
- } else if (previousNextNavigationPosition === 'inside') {
61
- styles[dynamicPositionProperty] = 0;
62
- } else if (previousNextNavigationPosition === 'outside') {
63
- styles[dynamicPositionProperty] = -1 * (spaceBetweenSlideAndPreviousNextNavigation + previousNextNavigationButtonWidth);
64
- }
65
- return styles;
86
+ return {
87
+ ...styles,
88
+ ...selectControlButtonPositionStyles({
89
+ positionVariant: previousNextNavigationPosition,
90
+ buttonWidth: previousNextNavigationButtonWidth,
91
+ positionProperty: getDynamicPositionProperty(areStylesAppliedOnPreviousButton),
92
+ spaceBetweenSlideAndButton: spaceBetweenSlideAndPreviousNextNavigation
93
+ })
94
+ };
66
95
  };
67
- const selectIconStyles = _ref => {
96
+ const selectIconStyles = _ref2 => {
68
97
  let {
69
98
  iconBackgroundColor
70
- } = _ref;
99
+ } = _ref2;
71
100
  return {
72
101
  backgroundColor: iconBackgroundColor
73
102
  };
@@ -128,7 +157,7 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, vie
128
157
  - `spaceBetweenSlideAndPreviousNextNavigation` - Horizontal space between slide and previous/next navigational buttons
129
158
  - `spaceBetweenSlideAndPanelNavigation` - Vertical space between slide area and panel navigation area
130
159
  */
131
- const Carousel = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
160
+ const Carousel = /*#__PURE__*/forwardRef((_ref3, ref) => {
132
161
  let {
133
162
  tokens,
134
163
  variant,
@@ -158,8 +187,21 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
158
187
  accessibilityLabel,
159
188
  accessibilityLiveRegion = 'polite',
160
189
  copy,
190
+ slideDuration = 0,
191
+ transitionDuration = 0,
192
+ autoPlay = false,
161
193
  ...rest
162
- } = _ref2;
194
+ } = _ref3;
195
+ let childrenArray = unpackFragment(children);
196
+ const autoPlayFeatureEnabled = autoPlay && slideDuration > 0 && transitionDuration > 0 && childrenArray.length > 1;
197
+ // if `Carousel` only has one `Carousel.Item`, convert this to a single-item array
198
+ if (!Array.isArray(childrenArray)) {
199
+ childrenArray = [childrenArray];
200
+ }
201
+ const getCopy = useCopy({
202
+ dictionary,
203
+ copy
204
+ });
163
205
  const viewport = useViewport();
164
206
  const themeTokens = useThemeTokens('Carousel', tokens, variant, {
165
207
  viewport
@@ -167,84 +209,53 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
167
209
  const {
168
210
  previousIcon,
169
211
  nextIcon,
212
+ playIcon,
213
+ pauseIcon,
170
214
  showPreviousNextNavigation,
171
215
  showPanelNavigation,
172
216
  showPanelTabs,
173
217
  spaceBetweenSlideAndPreviousNextNavigation
174
218
  } = themeTokens;
175
- const [activeIndex, setActiveIndex] = React.useState(0);
176
- const [isAnimating, setIsAnimating] = React.useState(false);
177
- const handleAnimationStart = React.useCallback(function () {
178
- if (typeof onAnimationStart === 'function') onAnimationStart(...arguments);
179
- setIsAnimating(true);
180
- }, [onAnimationStart]);
181
- const handleAnimationEnd = React.useCallback(function () {
182
- if (typeof onAnimationEnd === 'function') onAnimationEnd(...arguments);
183
- setIsAnimating(false);
184
- }, [onAnimationEnd]);
185
- const getCopy = useCopy({
186
- dictionary,
187
- copy
188
- });
189
- let childrenArray = unpackFragment(children);
190
- // if `Carousel` only has one `Carousel.Item`, convert this to a single-item array
191
- if (!Array.isArray(childrenArray)) {
192
- childrenArray = [childrenArray];
193
- }
194
- const systemProps = selectProps({
195
- ...rest,
196
- accessibilityRole,
197
- accessibilityLabel,
198
- accessibilityValue: {
199
- min: 1,
200
- max: childrenArray.length,
201
- now: activeIndex + 1
202
- }
203
- });
219
+ const [activeIndex, setActiveIndex] = useState(0);
220
+ const activeIndexRef = useRef(activeIndex);
204
221
  const {
205
222
  reduceMotionEnabled
206
223
  } = useA11yInfo();
224
+ const reduceMotionRef = useRef(reduceMotionEnabled);
207
225
  const [containerLayout, setContainerLayout] = React.useState({
208
226
  x: 0,
209
227
  y: 0,
210
228
  width: 0
211
229
  });
212
- const [previousNextNavigationButtonWidth, setPreviousNextNavigationButtonWidth] = React.useState(0);
213
- const firstFocusRef = React.useRef(null);
214
- const pan = React.useRef(new Animated.ValueXY()).current;
215
- const animatedX = React.useRef(0);
216
- const animatedY = React.useRef(0);
230
+ const containerLayoutRef = useRef(containerLayout);
231
+ const [previousNextNavigationButtonWidth, setPreviousNextNavigationButtonWidth] = useState(0);
232
+ const firstFocusRef = useRef(null);
233
+ const pan = useRef(new Animated.ValueXY()).current;
234
+ const animatedX = useRef(0);
235
+ const animatedY = useRef(0);
236
+ const [isAnimating, setIsAnimating] = useState(false);
237
+ /**
238
+ * While having the same starting point, `isAutoPlayEnabled` and `isCarouselPlaying` are different states
239
+ *
240
+ * `isAutoPlayEnabled` is a state to determine if the autoplay feature is enabled or disabled
241
+ * `isCarouselPlaying` is a state to determine if the carousel is currently playing or paused
242
+ */
243
+ const [isAutoPlayEnabled, setIsAutoPlayEnabled] = useState(autoPlayFeatureEnabled);
244
+ const [isCarouselPlaying, setisCarouselPlaying] = useState(autoPlayFeatureEnabled);
245
+ const isSwiping = useRef(false);
246
+ const autoPlayRef = useRef(null);
217
247
  const isFirstSlide = !activeIndex;
218
- const isLastSlide = activeIndex + 1 >= children.length;
219
- const onContainerLayout = _ref3 => {
220
- let {
221
- nativeEvent: {
222
- layout: {
223
- x,
224
- y,
225
- width
226
- }
227
- }
228
- } = _ref3;
229
- return setContainerLayout(prevState => ({
230
- ...prevState,
231
- x,
232
- y,
233
- width
234
- }));
235
- };
236
- const onPreviousNextNavigationButtonLayout = _ref4 => {
237
- let {
238
- nativeEvent: {
239
- layout: {
240
- width
241
- }
242
- }
243
- } = _ref4;
244
- return setPreviousNextNavigationButtonWidth(width);
245
- };
246
- const updateOffset = React.useCallback(() => {
247
- animatedX.current = containerLayout.width * activeIndex * -1;
248
+ const isLastSlide = activeIndex + 1 >= childrenArray.length;
249
+ const handleAnimationStart = useCallback(function () {
250
+ if (typeof onAnimationStart === 'function') onAnimationStart(...arguments);
251
+ setIsAnimating(true);
252
+ }, [onAnimationStart]);
253
+ const handleAnimationEnd = useCallback(function () {
254
+ if (typeof onAnimationEnd === 'function') onAnimationEnd(...arguments);
255
+ setIsAnimating(false);
256
+ }, [onAnimationEnd]);
257
+ const updateOffset = useCallback(() => {
258
+ animatedX.current = containerLayoutRef.current.width * activeIndexRef.current * -1;
248
259
  animatedY.current = 0;
249
260
  pan.setOffset({
250
261
  x: animatedX.current,
@@ -254,20 +265,27 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
254
265
  x: 0,
255
266
  y: 0
256
267
  });
257
- }, [activeIndex, containerLayout.width, pan, animatedX]);
258
- const animate = React.useCallback((toValue, toIndex) => {
268
+ }, [pan, animatedX]);
269
+ const animate = useCallback((toValue, toIndex) => {
259
270
  const handleAnimationEndToIndex = function () {
260
271
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
261
272
  args[_key] = arguments[_key];
262
273
  }
263
274
  return handleAnimationEnd(toIndex, ...args);
264
275
  };
265
- if (reduceMotionEnabled) {
276
+ if (reduceMotionRef.current || isSwiping.current) {
266
277
  Animated.timing(pan, {
267
278
  toValue,
268
279
  duration: 1,
269
280
  useNativeDriver: false
270
281
  }).start(handleAnimationEndToIndex);
282
+ } else if (isAutoPlayEnabled) {
283
+ Animated.timing(pan, {
284
+ ...springConfig,
285
+ toValue,
286
+ useNativeDriver: false,
287
+ duration: transitionDuration * 1000
288
+ }).start(handleAnimationEndToIndex);
271
289
  } else {
272
290
  Animated.spring(pan, {
273
291
  ...springConfig,
@@ -275,45 +293,157 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
275
293
  useNativeDriver: false
276
294
  }).start(handleAnimationEndToIndex);
277
295
  }
278
- }, [pan, springConfig, reduceMotionEnabled, handleAnimationEnd]);
279
- const updateIndex = React.useCallback(function () {
296
+ }, [pan, springConfig, handleAnimationEnd, transitionDuration, isAutoPlayEnabled]);
297
+ const stopAutoplay = useCallback(() => {
298
+ if (autoPlayRef !== null && autoPlayRef !== void 0 && autoPlayRef.current) {
299
+ clearTimeout(autoPlayRef === null || autoPlayRef === void 0 ? void 0 : autoPlayRef.current);
300
+ }
301
+ }, []);
302
+ const updateIndex = useCallback(function () {
280
303
  let delta = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
304
+ let transitionMode = arguments.length > 1 ? arguments[1] : undefined;
281
305
  const toValue = {
282
306
  x: 0,
283
307
  y: 0
284
308
  };
285
309
  let skipChanges = !delta;
286
310
  let calcDelta = delta;
287
- if (activeIndex <= 0 && delta < 0) {
288
- skipChanges = true;
289
- calcDelta = children.length + delta;
290
- } else if (activeIndex + 1 >= children.length && delta > 0) {
291
- skipChanges = true;
292
- calcDelta = -1 * activeIndex + delta - 1;
311
+ if (activeIndexRef.current <= 0 && delta < 0) {
312
+ skipChanges = transitionMode !== TRANSITION_MODES.AUTOMATIC;
313
+ calcDelta = childrenArray.length + delta;
314
+ } else if (activeIndexRef.current + 1 >= childrenArray.length && delta > 0) {
315
+ skipChanges = transitionMode !== TRANSITION_MODES.AUTOMATIC;
316
+ calcDelta = -1 * activeIndexRef.current + delta - 1;
293
317
  }
294
- const index = activeIndex + calcDelta;
318
+ const index = activeIndexRef.current + calcDelta;
295
319
  if (skipChanges) {
296
320
  animate(toValue, index);
297
321
  return calcDelta;
298
322
  }
323
+ stopAutoplay();
299
324
  setActiveIndex(index);
300
- toValue.x = containerLayout.width * -1 * calcDelta;
325
+ toValue.x = containerLayoutRef.current.width * -1 * calcDelta;
301
326
  animate(toValue, index);
327
+ if (isCarouselPlaying) {
328
+ stopAutoplay();
329
+ if (index === 0 && activeIndexRef.current + 1 === childrenArray.length && transitionMode === TRANSITION_MODES.AUTOMATIC) {
330
+ setisCarouselPlaying(false);
331
+ } else if (isAutoPlayEnabled) {
332
+ autoPlayRef.current = setTimeout(() => {
333
+ var _firstFocusRef$curren;
334
+ updateOffset();
335
+ handleAnimationStart(activeIndexRef.current);
336
+ updateIndex(slideDuration < 0 ? -1 : 1, TRANSITION_MODES.AUTOMATIC);
337
+ if (refocus) (_firstFocusRef$curren = firstFocusRef.current) === null || _firstFocusRef$curren === void 0 ? void 0 : _firstFocusRef$curren.focus();
338
+ }, Math.abs(slideDuration) * 1000);
339
+ }
340
+ }
302
341
  if (onIndexChanged) onIndexChanged(calcDelta, index);
303
342
  return calcDelta;
304
- }, [containerLayout.width, activeIndex, animate, children.length, onIndexChanged]);
305
- const fixOffsetAndGo = React.useCallback(delta => {
306
- var _firstFocusRef$curren;
343
+ }, [handleAnimationStart, refocus, slideDuration, updateOffset, animate, childrenArray.length, onIndexChanged, isCarouselPlaying, stopAutoplay, isAutoPlayEnabled]);
344
+ const startAutoplay = useCallback(() => {
345
+ stopAutoplay();
346
+ if (isAutoPlayEnabled) {
347
+ autoPlayRef.current = setTimeout(() => {
348
+ var _firstFocusRef$curren2;
349
+ updateOffset();
350
+ handleAnimationStart(activeIndexRef.current);
351
+ updateIndex(slideDuration < 0 ? -1 : 1, TRANSITION_MODES.AUTOMATIC);
352
+ if (refocus && Platform.OS === 'web') (_firstFocusRef$curren2 = firstFocusRef.current) === null || _firstFocusRef$curren2 === void 0 ? void 0 : _firstFocusRef$curren2.focus();
353
+ }, Math.abs(slideDuration) * 1000);
354
+ }
355
+ }, [handleAnimationStart, refocus, updateIndex, updateOffset, slideDuration, stopAutoplay, isAutoPlayEnabled]);
356
+ const fixOffsetAndGo = useCallback((delta, transitionMode) => {
357
+ var _firstFocusRef$curren3;
307
358
  updateOffset();
308
- handleAnimationStart(activeIndex);
309
- updateIndex(delta);
310
- if (refocus) (_firstFocusRef$curren = firstFocusRef.current) === null || _firstFocusRef$curren === void 0 ? void 0 : _firstFocusRef$curren.focus();
311
- }, [updateIndex, updateOffset, activeIndex, handleAnimationStart, refocus]);
312
- const goToNeighboring = React.useCallback(function () {
359
+ handleAnimationStart(activeIndexRef.current);
360
+ updateIndex(delta, transitionMode);
361
+ if (refocus && Platform.OS === 'web') (_firstFocusRef$curren3 = firstFocusRef.current) === null || _firstFocusRef$curren3 === void 0 ? void 0 : _firstFocusRef$curren3.focus();
362
+ }, [updateIndex, updateOffset, handleAnimationStart, refocus]);
363
+ const goToNeighboring = useCallback(function () {
313
364
  let toPrev = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
314
- fixOffsetAndGo(toPrev ? -1 : 1);
365
+ let transitionMode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : TRANSITION_MODES.MANUAL;
366
+ fixOffsetAndGo(toPrev ? -1 : 1, transitionMode);
315
367
  }, [fixOffsetAndGo]);
316
- const isSwipeAllowed = React.useCallback(() => {
368
+ useEffect(() => {
369
+ activeIndexRef.current = activeIndex;
370
+ }, [activeIndex]);
371
+ useEffect(() => {
372
+ reduceMotionRef.current = reduceMotionEnabled;
373
+ }, [reduceMotionEnabled]);
374
+ useEffect(() => {
375
+ containerLayoutRef.current = containerLayout;
376
+ }, [containerLayout]);
377
+ useEffect(() => {
378
+ pan.x.addListener(_ref4 => {
379
+ let {
380
+ value
381
+ } = _ref4;
382
+ animatedX.current = value;
383
+ });
384
+ pan.y.addListener(_ref5 => {
385
+ let {
386
+ value
387
+ } = _ref5;
388
+ animatedY.current = value;
389
+ });
390
+ if (isCarouselPlaying) {
391
+ startAutoplay();
392
+ }
393
+ return () => {
394
+ stopAutoplay();
395
+ pan.x.removeAllListeners();
396
+ pan.y.removeAllListeners();
397
+ };
398
+ }, [pan.x, pan.y, startAutoplay, stopAutoplay, isCarouselPlaying]);
399
+ useEffect(() => {
400
+ const subscription = Dimensions.addEventListener('change', () => {
401
+ updateOffset();
402
+ });
403
+ return () => {
404
+ if (subscription.remove) {
405
+ subscription.remove();
406
+ } else {
407
+ Dimensions.removeEventListener('change', updateOffset);
408
+ }
409
+ };
410
+ }, [updateOffset]);
411
+ useEffect(() => {
412
+ setIsAutoPlayEnabled(autoPlay && slideDuration > 0 && transitionDuration > 0 && childrenArray.length > 1);
413
+ }, [autoPlay, slideDuration, transitionDuration, childrenArray.length]);
414
+ useEffect(() => {
415
+ return () => {
416
+ stopAutoplay();
417
+ };
418
+ }, [stopAutoplay]);
419
+ const onContainerLayout = _ref6 => {
420
+ let {
421
+ nativeEvent: {
422
+ layout: {
423
+ x,
424
+ y,
425
+ width
426
+ }
427
+ }
428
+ } = _ref6;
429
+ return setContainerLayout(prevState => ({
430
+ ...prevState,
431
+ x,
432
+ y,
433
+ width
434
+ }));
435
+ };
436
+ const onPreviousNextNavigationButtonLayout = _ref7 => {
437
+ let {
438
+ nativeEvent: {
439
+ layout: {
440
+ width
441
+ }
442
+ }
443
+ } = _ref7;
444
+ return setPreviousNextNavigationButtonWidth(width);
445
+ };
446
+ const isSwipeAllowed = useCallback(() => {
317
447
  if (childrenArray.length === 1) {
318
448
  return false;
319
449
  }
@@ -322,72 +452,59 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
322
452
  }
323
453
  return true;
324
454
  }, [viewport, childrenArray.length]);
325
- const panResponder = React.useMemo(() => PanResponder.create({
455
+ const panResponder = useMemo(() => PanResponder.create({
326
456
  onPanResponderTerminationRequest: () => false,
327
457
  onMoveShouldSetResponderCapture: () => true,
328
458
  onMoveShouldSetPanResponderCapture: (_, gestureState) => {
329
459
  if (!isSwipeAllowed()) {
330
460
  return false;
331
461
  }
332
- handleAnimationStart(activeIndex);
333
- return Math.abs(gestureState.dx) > minDistanceToCapture;
462
+ handleAnimationStart(activeIndexRef.current);
463
+ const allow = Math.abs(gestureState.dx) > minDistanceToCapture;
464
+ if (allow) {
465
+ isSwiping.current = true;
466
+ stopAutoplay();
467
+ }
468
+ return allow;
469
+ },
470
+ onPanResponderGrant: () => {
471
+ updateOffset();
334
472
  },
335
- onPanResponderGrant: () => updateOffset(),
336
473
  onPanResponderMove: Animated.event([null, {
337
474
  dx: pan.x
338
475
  }], {
339
476
  useNativeDriver: false
340
477
  }),
341
478
  onPanResponderRelease: (_, gesture) => {
479
+ if (isCarouselPlaying) {
480
+ startAutoplay();
481
+ }
342
482
  const correction = gesture.moveX - gesture.x0;
343
- if (Math.abs(correction) < containerLayout.width * minDistanceForAction) {
483
+ if (Math.abs(correction) < containerLayoutRef.current.width * minDistanceForAction) {
344
484
  animate({
345
485
  x: 0,
346
486
  y: 0
347
487
  }, 0);
348
488
  } else {
349
489
  const delta = correction > 0 ? -1 : 1;
350
- updateIndex(delta);
490
+ updateIndex(delta, TRANSITION_MODES.SWIPE);
351
491
  }
492
+ isSwiping.current = false;
352
493
  }
353
- }), [containerLayout.width, updateIndex, updateOffset, animate, isSwipeAllowed, activeIndex, minDistanceForAction, handleAnimationStart, minDistanceToCapture, pan.x]);
354
- React.useEffect(() => {
355
- pan.x.addListener(_ref5 => {
356
- let {
357
- value
358
- } = _ref5;
359
- animatedX.current = value;
360
- });
361
- pan.y.addListener(_ref6 => {
362
- let {
363
- value
364
- } = _ref6;
365
- animatedY.current = value;
366
- });
367
- return () => {
368
- pan.x.removeAllListeners();
369
- pan.y.removeAllListeners();
370
- };
371
- }, [pan.x, pan.y]);
372
- React.useEffect(() => {
373
- const subscription = Dimensions.addEventListener('change', () => {
374
- updateOffset();
375
- });
376
- return () => subscription === null || subscription === void 0 ? void 0 : subscription.remove();
377
- });
378
- const goToNext = React.useCallback(() => {
494
+ }), [updateIndex, updateOffset, animate, isSwipeAllowed, minDistanceForAction, handleAnimationStart, minDistanceToCapture, pan.x, startAutoplay, stopAutoplay, isCarouselPlaying]);
495
+ const goToNext = useCallback(() => {
379
496
  goToNeighboring();
380
497
  }, [goToNeighboring]);
381
- const goToPrev = React.useCallback(() => {
498
+ const goToPrev = useCallback(() => {
382
499
  goToNeighboring(true);
383
500
  }, [goToNeighboring]);
384
- const goTo = React.useCallback(function () {
501
+ const goTo = useCallback(function () {
385
502
  let index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
386
- const delta = index - activeIndex;
503
+ const delta = index - activeIndexRef.current;
387
504
  if (delta) {
388
- fixOffsetAndGo(delta);
505
+ fixOffsetAndGo(delta, TRANSITION_MODES.MANUAL);
389
506
  }
390
- }, [fixOffsetAndGo, activeIndex]);
507
+ }, [fixOffsetAndGo]);
391
508
 
392
509
  // @TODO: - these are Allium-theme variants and won't have any effect in themes that don't implement them.
393
510
  // Normally we avoid setting variants of subcomponents, however this could be re-considered.
@@ -397,7 +514,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
397
514
  raised: !(variant !== null && variant !== void 0 && variant.inverse) && true,
398
515
  inverse: variant === null || variant === void 0 ? void 0 : variant.inverse
399
516
  };
400
- const getCopyWithPlaceholders = React.useCallback(copyKey => {
517
+ const getCopyWithPlaceholders = useCallback(copyKey => {
401
518
  const copyText = getCopy(copyKey).replace(/%\{title\}/g, title).replace(/%\{itemLabel\}/g, itemLabel).replace(/%\{stepNumber\}/g, activeIndex + 1).replace(/%\{stepCount\}/g, childrenArray.length);
402
519
 
403
520
  // First word might be a lowercase placeholder: capitalize the first letter
@@ -420,6 +537,17 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
420
537
  }
421
538
  }
422
539
  };
540
+ const systemProps = selectProps({
541
+ ...rest,
542
+ accessibilityRole,
543
+ accessibilityLabel,
544
+ accessibilityValue: {
545
+ min: 1,
546
+ max: childrenArray.length,
547
+ now: activeIndex + 1
548
+ }
549
+ });
550
+
423
551
  // If container isn't used for focus, give it a label of title if none is passed in,
424
552
  // otherwise read the current position on focus
425
553
  const containerAccessibilityLabel = systemProps.accessibilityLabel ?? isFirstFocusContainer ? `${title ? `${title} ` : ''}${getCopyWithPlaceholders('stepTrackerLabel')}` : title;
@@ -431,6 +559,14 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
431
559
  focusable: true
432
560
  })
433
561
  };
562
+ const onAnimationControlButtonPress = useCallback(() => {
563
+ if (isCarouselPlaying) {
564
+ stopAutoplay();
565
+ } else {
566
+ startAutoplay();
567
+ }
568
+ setisCarouselPlaying(prevState => !prevState);
569
+ }, [isCarouselPlaying, stopAutoplay, startAutoplay]);
434
570
  return /*#__PURE__*/_jsxs(CarouselProvider, {
435
571
  activeIndex: activeIndex,
436
572
  goTo: goTo,
@@ -447,7 +583,19 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
447
583
  ref: ref,
448
584
  ...systemProps,
449
585
  ...containerProps,
450
- children: [showPreviousNextNavigation && childrenArray.length > 1 ? /*#__PURE__*/_jsx(View, {
586
+ children: [isAutoPlayEnabled ? /*#__PURE__*/_jsx(View, {
587
+ style: [staticStyles.animationControlButton, selectControlButtonPositionStyles({
588
+ positionVariant: previousNextNavigationPosition,
589
+ buttonWidth: previousNextNavigationButtonWidth,
590
+ positionProperty: getDynamicPositionProperty(),
591
+ spaceBetweenSlideAndButton: spaceBetweenSlideAndPreviousNextNavigation
592
+ })],
593
+ children: /*#__PURE__*/_jsx(IconButton, {
594
+ icon: isCarouselPlaying ? pauseIcon : playIcon,
595
+ variant: previousNextIconButtonVariants,
596
+ onPress: onAnimationControlButtonPress
597
+ })
598
+ }) : null, showPreviousNextNavigation && childrenArray.length > 1 ? /*#__PURE__*/_jsx(View, {
451
599
  style: selectPreviousNextNavigationButtonStyles(previousNextNavigationButtonWidth, previousNextNavigationPosition, spaceBetweenSlideAndPreviousNextNavigation, isFirstSlide, isLastSlide, true),
452
600
  testID: "previous-button-container",
453
601
  children: /*#__PURE__*/_jsx(IconButton, {
@@ -471,7 +619,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
471
619
  }), /*#__PURE__*/_jsx(View, {
472
620
  style: selectContainerStyles(containerLayout.width),
473
621
  children: /*#__PURE__*/_jsx(Animated.View, {
474
- style: StyleSheet.flatten([selectSwipeAreaStyles(children.length, containerLayout.width), {
622
+ style: StyleSheet.flatten([selectSwipeAreaStyles(childrenArray.length, containerLayout.width), {
475
623
  transform: [{
476
624
  translateX: pan.x
477
625
  }, {
@@ -564,7 +712,7 @@ Carousel.propTypes = {
564
712
  * This function is also provided with a parameter indicating changed index (either 1, or -1)
565
713
  * Use it as follows:
566
714
  * ```js
567
- * const onIndexChangedCallback = React.useCallback((changedIndex, currentActiveIndex) => {
715
+ * const onIndexChangedCallback = useCallback((changedIndex, currentActiveIndex) => {
568
716
  * console.log(changedIndex)
569
717
  * }, []) // pass local dependencies as per your component
570
718
  * <Carousel
@@ -615,7 +763,7 @@ Carousel.propTypes = {
615
763
  * This function is also provided with a parameter indicating the current slide index before animation starts
616
764
  * Use it as follows:
617
765
  * ```js
618
- * const onAnimationStartCallback = React.useCallback((currentIndex) => {
766
+ * const onAnimationStartCallback = useCallback((currentIndex) => {
619
767
  * console.log(currentIndex)
620
768
  * }, []) // pass local dependencies as per your component
621
769
  * <Carousel
@@ -632,7 +780,7 @@ Carousel.propTypes = {
632
780
  * This function is also provided with a parameter indicating the updated slide index after animation ends
633
781
  * Use it as follows:
634
782
  * ```js
635
- * const onAnimationEndCallback = React.useCallback((changedIndex) => {
783
+ * const onAnimationEndCallback = useCallback((changedIndex) => {
636
784
  * console.log(changedIndex)
637
785
  * }, []) // pass local dependencies as per your component
638
786
  * <Carousel
@@ -670,7 +818,26 @@ Carousel.propTypes = {
670
818
  * Note that if the immediate Carousel children do not all render as `'li'` elements,
671
819
  * this should be changed (e.g. pass tag="div") because only 'li' is a valid child of 'ul'.
672
820
  */
673
- tag: PropTypes.oneOf(layoutTags)
821
+ tag: PropTypes.oneOf(layoutTags),
822
+ /**
823
+ * If set to `true`, the Carousel will automatically transition between slides
824
+ * and show the play/pause button
825
+ * - Default value is `false`
826
+ * - `slideDuration` and `transitionDuration` are required to be set for this to work
827
+ */
828
+ autoPlay: PropTypes.bool,
829
+ /**
830
+ * Duration of the time in seconds spent on each slide
831
+ * - Default value is `0`
832
+ * - `autoPlay` and `transitionDuration` are required to be set for this to work
833
+ */
834
+ slideDuration: PropTypes.number,
835
+ /**
836
+ * Duration of the time in seconds between each slide transition
837
+ * - Default value is `0`
838
+ * - `autoPlay` and `slideDuration` are required to be set for this to work
839
+ */
840
+ transitionDuration: PropTypes.number
674
841
  };
675
842
  Carousel.Item = CarouselItem;
676
843
  Carousel.displayName = 'Carousel';