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