@momo-kits/carousel 0.73.3-beta.5 → 0.74.2-react-native.2

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