@momo-kits/carousel 0.79.6 → 0.80.1-beta.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/index.tsx ADDED
@@ -0,0 +1,823 @@
1
+ import React from 'react';
2
+ import {
3
+ Animated,
4
+ Dimensions,
5
+ GestureResponderEvent,
6
+ LayoutChangeEvent,
7
+ NativeScrollEvent,
8
+ NativeSyntheticEvent,
9
+ Platform,
10
+ View,
11
+ } from 'react-native';
12
+ import {defaultAnimatedStyles, defaultScrollInterpolator} from './animation';
13
+ import {CarouselProps, CarouselRef, CarouselState, Position} from './types';
14
+ import {Spacing} from '@momo-kits/foundation';
15
+
16
+ const IS_ANDROID = Platform.OS === 'android';
17
+ const IS_IOS = Platform.OS === 'ios';
18
+ const screenWidth = Dimensions.get('window').width;
19
+
20
+ export default class Carousel extends React.PureComponent<
21
+ CarouselProps,
22
+ CarouselState
23
+ > {
24
+ static defaultProps = {
25
+ activeSlideAlignment: 'start',
26
+ activeSlideOffset: 20,
27
+ apparitionDelay: 0,
28
+ autoplay: false,
29
+ autoplayDelay: 1000,
30
+ autoplayInterval: 3000,
31
+ callbackOffsetMargin: 5,
32
+ containerCustomStyle: {},
33
+ contentContainerCustomStyle: {},
34
+ enableSnap: true,
35
+ firstItem: 0,
36
+ hasParallaxImages: false,
37
+ loop: false,
38
+ loopClonesPerSide: 3,
39
+ scrollEnabled: true,
40
+ slideStyle: {},
41
+ shouldOptimizeUpdates: true,
42
+ vertical: false,
43
+ isCustomScrollWidth: false,
44
+ disableIntervalMomentum: IS_ANDROID,
45
+ useExperimentalSnap: IS_ANDROID,
46
+ visibleItem: 1,
47
+ };
48
+
49
+ _activeItem;
50
+ _onScrollActiveItem;
51
+ _previousFirstItem;
52
+ _previousItemsLength;
53
+ _mounted;
54
+ _positions: Position[];
55
+ _currentScrollOffset;
56
+ _scrollEnabled;
57
+ _initTimeout?: NodeJS.Timeout;
58
+ _apparitionTimeout?: NodeJS.Timeout;
59
+ _enableAutoplayTimeout?: NodeJS.Timeout;
60
+ _autoplayTimeout?: NodeJS.Timeout;
61
+ _snapNoMomentumTimeout?: NodeJS.Timeout;
62
+ _androidRepositioningTimeout?: NodeJS.Timeout;
63
+ _scrollPos?: Animated.Value;
64
+ _onScrollHandler?: (...args: any[]) => void;
65
+ _autoplay?: boolean;
66
+ _autoplaying?: boolean;
67
+ _autoplayInterval?: NodeJS.Timeout;
68
+ _carouselRef: any;
69
+ _onLayoutInitDone?: boolean;
70
+ constructor(props: CarouselProps) {
71
+ super(props);
72
+
73
+ this.state = {
74
+ hideCarousel: !!props.apparitionDelay,
75
+ interpolators: [],
76
+ containerWidth: screenWidth,
77
+ itemWidth: screenWidth - Spacing.L * 2,
78
+ };
79
+
80
+ const initialActiveItem = this._getFirstItem(props.firstItem);
81
+ this._activeItem = initialActiveItem;
82
+ this._onScrollActiveItem = initialActiveItem;
83
+ this._previousFirstItem = initialActiveItem;
84
+ this._previousItemsLength = initialActiveItem;
85
+
86
+ this._mounted = false;
87
+ this._positions = [];
88
+ this._currentScrollOffset = 0;
89
+ this._scrollEnabled = props.scrollEnabled;
90
+
91
+ this._getItemLayout = this._getItemLayout.bind(this);
92
+ this._getKeyExtractor = this._getKeyExtractor.bind(this);
93
+ this._onLayout = this._onLayout.bind(this);
94
+ this._onScroll = this._onScroll.bind(this);
95
+ this._onMomentumScrollEnd = this._onMomentumScrollEnd.bind(this);
96
+ this._onTouchStart = this._onTouchStart.bind(this);
97
+ this._onTouchEnd = this._onTouchEnd.bind(this);
98
+ this._renderItem = this._renderItem.bind(this);
99
+ this._setScrollHandler(props);
100
+ }
101
+
102
+ componentDidMount() {
103
+ const {apparitionDelay, autoplay} = this.props;
104
+
105
+ this._mounted = true;
106
+ this._initPositionsAndInterpolators();
107
+
108
+ // Without 'requestAnimationFrame' or a `0` timeout, images will randomly not be rendered on Android...
109
+ this._initTimeout = setTimeout(() => {
110
+ if (!this._mounted) {
111
+ return;
112
+ }
113
+
114
+ const apparitionCallback = () => {
115
+ if (apparitionDelay) {
116
+ this.setState({hideCarousel: false});
117
+ }
118
+ if (autoplay) {
119
+ this.startAutoplay();
120
+ }
121
+ };
122
+
123
+ if (apparitionDelay) {
124
+ this._apparitionTimeout = setTimeout(() => {
125
+ apparitionCallback();
126
+ }, apparitionDelay);
127
+ } else {
128
+ apparitionCallback();
129
+ }
130
+ }, 1);
131
+ }
132
+
133
+ componentDidUpdate(prevProps: CarouselProps) {
134
+ const {interpolators} = this.state;
135
+ const {firstItem, scrollEnabled} = this.props;
136
+ const itemsLength = this._getCustomDataLength(this.props);
137
+
138
+ if (!itemsLength) {
139
+ return;
140
+ }
141
+
142
+ const nextFirstItem = this._getFirstItem(firstItem, this.props);
143
+ let nextActiveItem =
144
+ typeof this._activeItem !== 'undefined'
145
+ ? this._activeItem
146
+ : nextFirstItem;
147
+
148
+ if (nextActiveItem > itemsLength - 1) {
149
+ nextActiveItem = itemsLength - 1;
150
+ }
151
+
152
+ if (scrollEnabled !== prevProps.scrollEnabled) {
153
+ this._setScrollEnabled(scrollEnabled);
154
+ }
155
+
156
+ if (interpolators.length !== itemsLength) {
157
+ this._activeItem = nextActiveItem;
158
+ this._previousItemsLength = itemsLength;
159
+
160
+ this._initPositionsAndInterpolators(this.props);
161
+ } else if (
162
+ nextFirstItem !== this._previousFirstItem &&
163
+ nextFirstItem !== this._activeItem
164
+ ) {
165
+ this._activeItem = nextFirstItem;
166
+ this._previousFirstItem = nextFirstItem;
167
+ this._snapToItem(nextFirstItem, false, true, true);
168
+ }
169
+
170
+ if (this.props.onScroll !== prevProps.onScroll) {
171
+ this._setScrollHandler(this.props);
172
+ }
173
+ }
174
+
175
+ componentWillUnmount() {
176
+ this._mounted = false;
177
+ this.stopAutoplay();
178
+ clearTimeout(this._initTimeout);
179
+ clearTimeout(this._apparitionTimeout);
180
+ clearTimeout(this._enableAutoplayTimeout);
181
+ clearTimeout(this._autoplayTimeout);
182
+ clearTimeout(this._snapNoMomentumTimeout);
183
+ clearTimeout(this._androidRepositioningTimeout);
184
+ }
185
+
186
+ _setScrollHandler(props: CarouselProps) {
187
+ const scrollEventConfig = {
188
+ listener: this._onScroll,
189
+ useNativeDriver: true,
190
+ };
191
+ this._scrollPos = new Animated.Value(0);
192
+ const argMapping = [{nativeEvent: {contentOffset: {x: this._scrollPos}}}];
193
+
194
+ if (props.onScroll && Array.isArray(props.onScroll._argMapping)) {
195
+ argMapping.pop();
196
+ const [argMap] = props.onScroll._argMapping;
197
+ if (argMap && argMap.nativeEvent && argMap.nativeEvent.contentOffset) {
198
+ this._scrollPos =
199
+ argMap.nativeEvent.contentOffset.x ||
200
+ argMap.nativeEvent.contentOffset.y ||
201
+ this._scrollPos;
202
+ }
203
+ argMapping.push(...props.onScroll._argMapping);
204
+ }
205
+ this._onScrollHandler = Animated.event(argMapping, scrollEventConfig);
206
+ }
207
+
208
+ _enableLoop() {
209
+ const {data, enableSnap, loop} = this.props;
210
+ return enableSnap && loop && data && data.length && data.length > 1;
211
+ }
212
+
213
+ _shouldAnimateSlides(props = this.props) {
214
+ const {inactiveSlideOpacity, inactiveSlideScale} = props;
215
+ return inactiveSlideOpacity < 1 || inactiveSlideScale < 1;
216
+ }
217
+
218
+ _shouldRepositionScroll(index: number) {
219
+ const {data, enableSnap, loopClonesPerSide} = this.props;
220
+ const dataLength = data && data.length;
221
+ return !(
222
+ !enableSnap ||
223
+ !dataLength ||
224
+ !this._enableLoop() ||
225
+ (index >= loopClonesPerSide && index < dataLength + loopClonesPerSide)
226
+ );
227
+ }
228
+
229
+ _isMultiple(x: number, y: number) {
230
+ return Math.round(Math.round(x / y) / (1 / y)) === Math.round(x);
231
+ }
232
+
233
+ _getCustomData(props = this.props) {
234
+ const {data, loopClonesPerSide} = props;
235
+ const dataLength = data && data.length;
236
+
237
+ if (!dataLength) {
238
+ return [];
239
+ }
240
+
241
+ if (!this._enableLoop()) {
242
+ return data;
243
+ }
244
+
245
+ let previousItems = [];
246
+ let nextItems = [];
247
+
248
+ if (loopClonesPerSide > dataLength) {
249
+ const dataMultiplier = Math.floor(loopClonesPerSide / dataLength);
250
+ const remainder = loopClonesPerSide % dataLength;
251
+
252
+ for (let i = 0; i < dataMultiplier; i++) {
253
+ previousItems.push(...data);
254
+ nextItems.push(...data);
255
+ }
256
+
257
+ previousItems.unshift(...data.slice(-remainder));
258
+ nextItems.push(...data.slice(0, remainder));
259
+ } else {
260
+ previousItems = data.slice(-loopClonesPerSide);
261
+ nextItems = data.slice(0, loopClonesPerSide);
262
+ }
263
+
264
+ return previousItems.concat(data, nextItems);
265
+ }
266
+
267
+ _getCustomDataLength(props = this.props) {
268
+ const {data, loopClonesPerSide} = props;
269
+ const dataLength = data && data.length;
270
+
271
+ if (!dataLength) {
272
+ return 0;
273
+ }
274
+
275
+ return this._enableLoop() ? dataLength + 2 * loopClonesPerSide : dataLength;
276
+ }
277
+
278
+ _getCustomIndex(index: number, props = this.props) {
279
+ const itemsLength = this._getCustomDataLength(props);
280
+
281
+ if (!itemsLength || typeof index === 'undefined') {
282
+ return 0;
283
+ }
284
+
285
+ return index;
286
+ }
287
+
288
+ _getDataIndex(index: number) {
289
+ const {data, loopClonesPerSide} = this.props;
290
+ const dataLength = data && data.length;
291
+ if (!this._enableLoop() || !dataLength) {
292
+ return index;
293
+ }
294
+
295
+ if (index >= dataLength + loopClonesPerSide) {
296
+ return loopClonesPerSide > dataLength
297
+ ? (index - loopClonesPerSide) % dataLength
298
+ : index - dataLength - loopClonesPerSide;
299
+ } else if (index < loopClonesPerSide) {
300
+ if (loopClonesPerSide > dataLength) {
301
+ const baseDataIndexes = [];
302
+ const dataIndexes = [];
303
+ const dataMultiplier = Math.floor(loopClonesPerSide / dataLength);
304
+ const remainder = loopClonesPerSide % dataLength;
305
+
306
+ for (let i = 0; i < dataLength; i++) {
307
+ baseDataIndexes.push(i);
308
+ }
309
+
310
+ for (let j = 0; j < dataMultiplier; j++) {
311
+ dataIndexes.push(...baseDataIndexes);
312
+ }
313
+
314
+ dataIndexes.unshift(...baseDataIndexes.slice(-remainder));
315
+ return dataIndexes[index];
316
+ } else {
317
+ return index + dataLength - loopClonesPerSide;
318
+ }
319
+ } else {
320
+ return index - loopClonesPerSide;
321
+ }
322
+ }
323
+
324
+ _getFirstItem(index: number, props = this.props) {
325
+ const {loopClonesPerSide} = props;
326
+ const itemsLength = this._getCustomDataLength(props);
327
+
328
+ if (!itemsLength || index > itemsLength - 1 || index < 0) {
329
+ return 0;
330
+ }
331
+
332
+ return this._enableLoop() ? index + loopClonesPerSide : index;
333
+ }
334
+
335
+ _getWrappedRef() {
336
+ return this._carouselRef;
337
+ }
338
+
339
+ _getScrollEnabled() {
340
+ return this._scrollEnabled;
341
+ }
342
+
343
+ _setScrollEnabled(scrollEnabled = true) {
344
+ this._scrollEnabled = scrollEnabled;
345
+ }
346
+
347
+ _getItemMainDimension() {
348
+ const {itemWidth} = this.state;
349
+ return itemWidth + Spacing.S;
350
+ }
351
+
352
+ _getItemScrollOffset(index: number) {
353
+ return (
354
+ this._positions && this._positions[index] && this._positions[index].start
355
+ );
356
+ }
357
+
358
+ _getItemLayout(_: any, index: number) {
359
+ const itemMainDimension = this._getItemMainDimension();
360
+ return {
361
+ index,
362
+ length: itemMainDimension,
363
+ offset: itemMainDimension * index,
364
+ };
365
+ }
366
+
367
+ _getKeyExtractor(_: any, index: any) {
368
+ return `flatlist-item-${index}`;
369
+ }
370
+
371
+ _getScrollOffset(event: NativeSyntheticEvent<NativeScrollEvent>) {
372
+ return event.nativeEvent.contentOffset.x;
373
+ }
374
+
375
+ _getActiveSlideOffset() {
376
+ const {activeSlideOffset} = this.props;
377
+ const itemMainDimension = this._getItemMainDimension();
378
+ const minOffset = 10;
379
+ return itemMainDimension / 2 - activeSlideOffset >= minOffset
380
+ ? activeSlideOffset
381
+ : minOffset;
382
+ }
383
+
384
+ _getActiveItem(offset: number) {
385
+ const itemMainDimension = this._getItemMainDimension();
386
+ const center = offset + itemMainDimension / 2;
387
+ const activeSlideOffset = this._getActiveSlideOffset();
388
+ const lastIndex = this._positions.length - 1;
389
+ let itemIndex;
390
+
391
+ if (offset <= 0) {
392
+ return 0;
393
+ }
394
+
395
+ if (
396
+ this._positions[lastIndex] &&
397
+ offset >= this._positions[lastIndex].start
398
+ ) {
399
+ return lastIndex;
400
+ }
401
+
402
+ for (let i = 0; i < this._positions.length; i++) {
403
+ const {start, end} = this._positions[i];
404
+ if (
405
+ center + activeSlideOffset >= start &&
406
+ center - activeSlideOffset <= end
407
+ ) {
408
+ itemIndex = i;
409
+ break;
410
+ }
411
+ }
412
+
413
+ return itemIndex || 0;
414
+ }
415
+
416
+ _getSlideInterpolatedStyle(index: number, animatedValue: Animated.Value) {
417
+ return defaultAnimatedStyles(animatedValue, this.props);
418
+ }
419
+
420
+ _initPositionsAndInterpolators(props = this.props) {
421
+ const {data} = props;
422
+ const itemMainDimension = this._getItemMainDimension();
423
+
424
+ if (!data || !data.length) {
425
+ return;
426
+ }
427
+
428
+ const interpolators: any[] = [];
429
+ this._positions = [];
430
+
431
+ this._getCustomData(props).forEach((_itemData, index) => {
432
+ const _index = this._getCustomIndex(index, props);
433
+ let animatedValue;
434
+
435
+ this._positions[index] = {
436
+ start: index * itemMainDimension,
437
+ end: index * itemMainDimension + itemMainDimension,
438
+ };
439
+
440
+ if (!this._shouldAnimateSlides(props) || !this._scrollPos) {
441
+ animatedValue = new Animated.Value(1);
442
+ } else {
443
+ let interpolator = defaultScrollInterpolator(
444
+ _index,
445
+ this.state.itemWidth,
446
+ );
447
+
448
+ animatedValue = this._scrollPos.interpolate({
449
+ ...interpolator,
450
+ extrapolate: 'clamp',
451
+ });
452
+ }
453
+
454
+ interpolators.push(animatedValue);
455
+ });
456
+
457
+ this.setState({interpolators});
458
+ }
459
+
460
+ _repositionScroll(index: number, animated = false) {
461
+ const {data, loopClonesPerSide} = this.props;
462
+ const dataLength = data && data.length;
463
+
464
+ if (typeof index === 'undefined' || !this._shouldRepositionScroll(index)) {
465
+ return;
466
+ }
467
+
468
+ let repositionTo = index;
469
+
470
+ if (index >= dataLength + loopClonesPerSide) {
471
+ repositionTo = index - dataLength;
472
+ } else if (index < loopClonesPerSide) {
473
+ repositionTo = index + dataLength;
474
+ }
475
+
476
+ this._snapToItem(repositionTo, animated, false);
477
+ }
478
+
479
+ _onTouchStart(event: any) {
480
+ const {onTouchStart} = this.props;
481
+
482
+ if (this._getScrollEnabled() !== false && this._autoplaying) {
483
+ this.pauseAutoPlay();
484
+ }
485
+
486
+ onTouchStart && onTouchStart(event);
487
+ }
488
+
489
+ _onTouchEnd(event: GestureResponderEvent) {
490
+ const {onTouchEnd} = this.props;
491
+
492
+ if (
493
+ this._getScrollEnabled() !== false &&
494
+ this._autoplay &&
495
+ !this._autoplaying
496
+ ) {
497
+ this.startAutoplay();
498
+ }
499
+
500
+ onTouchEnd && onTouchEnd(event);
501
+ }
502
+
503
+ _onScroll(event: NativeSyntheticEvent<NativeScrollEvent>) {
504
+ const {onScroll, onScrollIndexChanged, onSnapToItem} = this.props;
505
+ const scrollOffset = event
506
+ ? this._getScrollOffset(event)
507
+ : this._currentScrollOffset;
508
+ const nextActiveItem = this._getActiveItem(scrollOffset);
509
+ const dataLength = this._getCustomDataLength();
510
+ const lastItemScrollOffset = this._getItemScrollOffset(dataLength - 1);
511
+
512
+ this._currentScrollOffset = scrollOffset;
513
+
514
+ if (nextActiveItem !== this._onScrollActiveItem) {
515
+ this._onScrollActiveItem = nextActiveItem;
516
+ onScrollIndexChanged &&
517
+ onScrollIndexChanged(this._getDataIndex(nextActiveItem));
518
+
519
+ onSnapToItem && onSnapToItem(this._getDataIndex(nextActiveItem));
520
+ }
521
+
522
+ if (
523
+ (IS_IOS && scrollOffset > lastItemScrollOffset) ||
524
+ (IS_ANDROID &&
525
+ Math.floor(scrollOffset) > Math.floor(lastItemScrollOffset))
526
+ ) {
527
+ this._activeItem = nextActiveItem;
528
+ this._repositionScroll(nextActiveItem);
529
+ }
530
+
531
+ if (typeof onScroll === 'function' && event) {
532
+ onScroll(event);
533
+ }
534
+ }
535
+
536
+ _onMomentumScrollEnd(event: NativeSyntheticEvent<NativeScrollEvent>) {
537
+ const {autoplayDelay, onMomentumScrollEnd, onSnapToItem} = this.props;
538
+ const {itemWidth} = this.state;
539
+ const scrollOffset = event
540
+ ? this._getScrollOffset(event)
541
+ : this._currentScrollOffset;
542
+ const nextActiveItem = this._getActiveItem(scrollOffset);
543
+ const hasSnapped = this._isMultiple(scrollOffset, itemWidth);
544
+
545
+ if (nextActiveItem !== this._activeItem) {
546
+ this._activeItem = nextActiveItem;
547
+ onSnapToItem && onSnapToItem(this._getDataIndex(nextActiveItem));
548
+
549
+ if (hasSnapped && IS_ANDROID) {
550
+ this._repositionScroll(nextActiveItem);
551
+ } else if (IS_IOS) {
552
+ this._repositionScroll(nextActiveItem);
553
+ }
554
+ }
555
+
556
+ onMomentumScrollEnd && onMomentumScrollEnd(event);
557
+
558
+ if (IS_ANDROID && this._autoplay && !this._autoplaying) {
559
+ clearTimeout(this._enableAutoplayTimeout);
560
+ this._enableAutoplayTimeout = setTimeout(() => {
561
+ this.startAutoplay();
562
+ }, autoplayDelay);
563
+ }
564
+ }
565
+
566
+ _onLayout(event: LayoutChangeEvent) {
567
+ const {onLayout, visibleItem} = this.props;
568
+
569
+ if (this._onLayoutInitDone) {
570
+ this._initPositionsAndInterpolators();
571
+ this._snapToItem(this._activeItem, false, false, true);
572
+ } else {
573
+ this._onLayoutInitDone = true;
574
+ }
575
+ const containerWidth = event.nativeEvent.layout.width;
576
+ const itemWidth =
577
+ this.props.itemWidth ||
578
+ (this.props.visibleItem === 1
579
+ ? screenWidth - Spacing.M * 2
580
+ : Math.ceil(
581
+ (containerWidth * 0.9 - visibleItem * Spacing.S) / visibleItem,
582
+ ));
583
+
584
+ this.setState({containerWidth, itemWidth});
585
+
586
+ onLayout && onLayout(event);
587
+ }
588
+
589
+ _getPositionIndex(index: number) {
590
+ const {loop, loopClonesPerSide} = this.props;
591
+ return loop ? index + loopClonesPerSide : index;
592
+ }
593
+
594
+ _snapToItem(
595
+ index: number,
596
+ animated = true,
597
+ fireCallback = true,
598
+ forceScrollTo = false,
599
+ ) {
600
+ const {onSnapToItem} = this.props;
601
+ const itemsLength = this._getCustomDataLength();
602
+ const wrappedRef = this._getWrappedRef();
603
+ if (!itemsLength || !wrappedRef) {
604
+ return;
605
+ }
606
+
607
+ if (!index || index < 0) {
608
+ index = 0;
609
+ } else if (itemsLength > 0 && index >= itemsLength) {
610
+ index = itemsLength - 1;
611
+ }
612
+
613
+ if (index === this._activeItem && !forceScrollTo) {
614
+ return;
615
+ }
616
+
617
+ this._carouselRef.scrollToIndex({
618
+ index,
619
+ animated: true,
620
+ });
621
+
622
+ const requiresManualTrigger = !animated || IS_ANDROID;
623
+ if (requiresManualTrigger) {
624
+ this._activeItem = index;
625
+
626
+ if (fireCallback) {
627
+ onSnapToItem && onSnapToItem(this._getDataIndex(index));
628
+ }
629
+
630
+ if (IS_ANDROID && this._shouldRepositionScroll(index)) {
631
+ if (animated) {
632
+ this._androidRepositioningTimeout = setTimeout(() => {
633
+ this._repositionScroll(index, false);
634
+ }, 400);
635
+ } else {
636
+ this._repositionScroll(index);
637
+ }
638
+ }
639
+ }
640
+ }
641
+
642
+ _renderItem(info: {item: any; index: number}) {
643
+ const {item, index} = info;
644
+ const {interpolators, itemWidth} = this.state;
645
+ const {slideStyle} = this.props;
646
+ const animatedValue = interpolators && interpolators[index];
647
+
648
+ if (typeof animatedValue === 'undefined') {
649
+ return null;
650
+ }
651
+
652
+ const animate = this._shouldAnimateSlides();
653
+ const Component = animate ? Animated.View : View;
654
+ // const animatedStyle = animate
655
+ // ? this._getSlideInterpolatedStyle(index, animatedValue)
656
+ // : {};
657
+
658
+ const mainDimension = {width: itemWidth};
659
+
660
+ return (
661
+ <Component
662
+ style={[
663
+ mainDimension,
664
+ slideStyle,
665
+ // animatedStyle,
666
+ {overflow: 'hidden'},
667
+ this.props.loop
668
+ ? {marginLeft: Spacing.S}
669
+ : {
670
+ marginLeft: index === 0 ? Spacing.M : 0,
671
+ marginRight:
672
+ index === this._getCustomDataLength() - 1
673
+ ? Spacing.M
674
+ : Spacing.S,
675
+ },
676
+ ]}
677
+ pointerEvents="box-none">
678
+ {this.props.renderItem({
679
+ item,
680
+ index,
681
+ })}
682
+ </Component>
683
+ );
684
+ }
685
+
686
+ startAutoplay() {
687
+ const {autoplayInterval, autoplayDelay} = this.props;
688
+ this._autoplay = true;
689
+
690
+ if (this._autoplaying) {
691
+ return;
692
+ }
693
+
694
+ clearTimeout(this._autoplayTimeout);
695
+ this._autoplayTimeout = setTimeout(() => {
696
+ this._autoplaying = true;
697
+ this._autoplayInterval = setInterval(() => {
698
+ if (this._autoplaying) {
699
+ this.snapToNext();
700
+ }
701
+ }, autoplayInterval);
702
+ }, autoplayDelay);
703
+ }
704
+
705
+ pauseAutoPlay() {
706
+ this._autoplaying = false;
707
+ clearTimeout(this._autoplayTimeout);
708
+ clearTimeout(this._enableAutoplayTimeout);
709
+ clearInterval(this._autoplayInterval);
710
+ }
711
+
712
+ stopAutoplay() {
713
+ this._autoplay = false;
714
+ this.pauseAutoPlay();
715
+ }
716
+
717
+ snapToItem(index: number, animated = true, fireCallback = true) {
718
+ if (!index || index < 0) {
719
+ index = 0;
720
+ }
721
+
722
+ const positionIndex = this._getPositionIndex(index);
723
+
724
+ if (positionIndex === this._activeItem) {
725
+ return;
726
+ }
727
+
728
+ this._snapToItem(positionIndex, animated, fireCallback);
729
+ }
730
+
731
+ snapToNext(animated = true, fireCallback = true) {
732
+ const itemsLength = this._getCustomDataLength();
733
+
734
+ let newIndex = this._activeItem + 1;
735
+ if (newIndex > itemsLength - 1) {
736
+ newIndex = 0;
737
+ }
738
+ this._snapToItem(newIndex, animated, fireCallback);
739
+ }
740
+
741
+ snapToPrev(animated = true, fireCallback = true) {
742
+ const itemsLength = this._getCustomDataLength();
743
+
744
+ let newIndex = this._activeItem - 1;
745
+ if (newIndex < 0) {
746
+ newIndex = itemsLength - 1;
747
+ }
748
+ this._snapToItem(newIndex, animated, fireCallback);
749
+ }
750
+
751
+ render() {
752
+ const {
753
+ loopClonesPerSide,
754
+ visibleItem,
755
+ firstItem,
756
+ getItemLayout,
757
+ keyExtractor,
758
+ style,
759
+ disableIntervalMomentum,
760
+ enableSnap,
761
+ } = this.props;
762
+ const {hideCarousel, containerWidth} = this.state;
763
+
764
+ const initialNumPerSide = this._enableLoop() ? loopClonesPerSide : 2;
765
+ const initialNumToRender =
766
+ visibleItem > 2
767
+ ? visibleItem + initialNumPerSide * 2
768
+ : initialNumPerSide * 2;
769
+ const maxToRenderPerBatch = initialNumToRender;
770
+ const windowSize = maxToRenderPerBatch;
771
+
772
+ const snapToInterval = enableSnap
773
+ ? this._getItemMainDimension()
774
+ : undefined;
775
+
776
+ const specificProps = {
777
+ getItemLayout: getItemLayout || this._getItemLayout,
778
+ initialScrollIndex: this._getFirstItem(firstItem),
779
+ keyExtractor: keyExtractor || this._getKeyExtractor,
780
+ renderItem: this._renderItem,
781
+ };
782
+
783
+ return (
784
+ <Animated.FlatList
785
+ {...this.props}
786
+ {...specificProps}
787
+ ref={c => (this._carouselRef = c)}
788
+ overScrollMode={'never'}
789
+ snapToInterval={snapToInterval}
790
+ disableIntervalMomentum={disableIntervalMomentum}
791
+ pointerEvents={hideCarousel ? 'none' : 'auto'}
792
+ decelerationRate={'fast'}
793
+ numColumns={1}
794
+ style={[
795
+ style,
796
+ {width: '100%', flexDirection: 'row'},
797
+ hideCarousel ? {opacity: 0} : {},
798
+ ]}
799
+ automaticallyAdjustContentInsets={false}
800
+ directionalLockEnabled
801
+ disableScrollViewPanResponder={false}
802
+ pinchGestureEnabled={false}
803
+ scrollsToTop={false}
804
+ showsHorizontalScrollIndicator={false}
805
+ showsVerticalScrollIndicator={false}
806
+ initialNumToRender={initialNumToRender}
807
+ maxToRenderPerBatch={maxToRenderPerBatch}
808
+ windowSize={windowSize}
809
+ pagingEnabled={enableSnap}
810
+ data={this._getCustomData()}
811
+ horizontal
812
+ scrollEventThrottle={1}
813
+ onLayout={this._onLayout}
814
+ onMomentumScrollEnd={this._onMomentumScrollEnd}
815
+ onScroll={this._onScrollHandler}
816
+ onTouchStart={this._onTouchStart}
817
+ onTouchEnd={this._onTouchEnd}
818
+ />
819
+ );
820
+ }
821
+ }
822
+
823
+ export type {CarouselProps, CarouselRef};