@momo-kits/carousel 0.0.41-beta → 0.0.41-beta.14

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/CarouselV2.js ADDED
@@ -0,0 +1,1368 @@
1
+ import React from 'react';
2
+ import { Animated, I18nManager, Platform, View } from 'react-native';
3
+ // import shallowCompare from 'react-addons-shallow-compare';
4
+ import {
5
+ defaultScrollInterpolator,
6
+ stackScrollInterpolator,
7
+ tinderScrollInterpolator,
8
+ defaultAnimatedStyles,
9
+ shiftAnimatedStyles,
10
+ stackAnimatedStyles,
11
+ tinderAnimatedStyles,
12
+ } from './utils/animationsV2';
13
+ import PropTypes from 'prop-types';
14
+
15
+ // Metro doesn't support dynamic imports - i.e. require() done in the component itself
16
+ // But at the same time the following import will fail on Snack...
17
+ // TODO: find a way to get React Native's version without having to assume the file path
18
+ // import RN_PACKAGE from '../../../react-native/package.json';
19
+
20
+ const IS_ANDROID = Platform.OS === 'android';
21
+
22
+ // React Native automatically handles RTL layouts; unfortunately, it's buggy with horizontal ScrollView
23
+ // See https://github.com/facebook/react-native/issues/11960
24
+ // NOTE: the following variable is not declared in the constructor
25
+ // otherwise it is undefined at init, which messes with custom indexes
26
+ const IS_RTL = I18nManager.isRTL;
27
+
28
+ export default class Carousel extends React.PureComponent {
29
+ constructor(props) {
30
+ super(props);
31
+
32
+ this.state = {
33
+ hideCarousel: !!props.apparitionDelay,
34
+ interpolators: [],
35
+ };
36
+
37
+ // this._RNVersionCode = this._getRNVersionCode();
38
+
39
+ // The following values are not stored in the state because 'setState()' is asynchronous
40
+ // and this results in an absolutely crappy behavior on Android while swiping (see #156)
41
+ const initialActiveItem = this._getFirstItem(props.firstItem);
42
+ this._activeItem = initialActiveItem;
43
+ this._onScrollActiveItem = initialActiveItem;
44
+ this._previousFirstItem = initialActiveItem;
45
+ this._previousItemsLength = initialActiveItem;
46
+
47
+ this._mounted = false;
48
+ this._positions = [];
49
+ this._currentScrollOffset = 0; // Store ScrollView's scroll position
50
+ this._scrollEnabled = props.scrollEnabled !== false;
51
+
52
+ this._getCellRendererComponent =
53
+ this._getCellRendererComponent.bind(this);
54
+ this._getItemLayout = this._getItemLayout.bind(this);
55
+ this._getKeyExtractor = this._getKeyExtractor.bind(this);
56
+ this._onLayout = this._onLayout.bind(this);
57
+ this._onScroll = this._onScroll.bind(this);
58
+ this._onMomentumScrollEnd = this._onMomentumScrollEnd.bind(this);
59
+ this._onTouchStart = this._onTouchStart.bind(this);
60
+ this._onTouchEnd = this._onTouchEnd.bind(this);
61
+ this._renderItem = this._renderItem.bind(this);
62
+
63
+ // WARNING: call this AFTER binding _onScroll
64
+ this._setScrollHandler(props);
65
+
66
+ // Display warnings
67
+ this._displayWarnings(props);
68
+ }
69
+
70
+ componentDidMount() {
71
+ const { apparitionDelay, autoplay, firstItem } = this.props;
72
+
73
+ this._mounted = true;
74
+ this._initPositionsAndInterpolators();
75
+
76
+ // Without 'requestAnimationFrame' or a `0` timeout, images will randomly not be rendered on Android...
77
+ this._initTimeout = setTimeout(() => {
78
+ if (!this._mounted) {
79
+ return;
80
+ }
81
+
82
+ const apparitionCallback = () => {
83
+ if (apparitionDelay) {
84
+ this.setState({ hideCarousel: false });
85
+ }
86
+ if (autoplay) {
87
+ this.startAutoplay();
88
+ }
89
+ };
90
+
91
+ // FlatList will use its own built-in prop `initialScrollIndex`
92
+ if (this._needsScrollView()) {
93
+ const _firstItem = this._getFirstItem(firstItem);
94
+ this._snapToItem(_firstItem, false, false, true);
95
+ // this._hackActiveSlideAnimation(_firstItem);
96
+ }
97
+
98
+ if (apparitionDelay) {
99
+ this._apparitionTimeout = setTimeout(() => {
100
+ apparitionCallback();
101
+ }, apparitionDelay);
102
+ } else {
103
+ apparitionCallback();
104
+ }
105
+ }, 1);
106
+ }
107
+
108
+ // shouldComponentUpdate (
109
+ // nextProps,
110
+ // nextState
111
+ // ) {
112
+ // if (this.props.shouldOptimizeUpdates === false) {
113
+ // return true;
114
+ // } else {
115
+ // return shallowCompare(this, nextProps, nextState);
116
+ // }
117
+ // }
118
+
119
+ componentDidUpdate(prevProps) {
120
+ const { interpolators } = this.state;
121
+ const { firstItem, scrollEnabled } = this.props;
122
+ const itemsLength = this._getCustomDataLength(this.props);
123
+
124
+ if (!itemsLength) {
125
+ return;
126
+ }
127
+
128
+ const nextFirstItem = this._getFirstItem(firstItem, this.props);
129
+ let nextActiveItem =
130
+ typeof this._activeItem !== 'undefined'
131
+ ? this._activeItem
132
+ : nextFirstItem;
133
+
134
+ const hasNewSize =
135
+ this.props.vertical !== prevProps.vertical ||
136
+ (this.props.vertical &&
137
+ prevProps.vertical &&
138
+ (prevProps.itemHeight !== this.props.itemHeight ||
139
+ prevProps.sliderHeight !== this.props.sliderHeight)) ||
140
+ (!this.props.vertical &&
141
+ !prevProps.vertical &&
142
+ (prevProps.itemWidth !== this.props.itemWidth ||
143
+ prevProps.sliderWidth !== this.props.sliderWidth));
144
+
145
+ // Prevent issues with dynamically removed items
146
+ if (nextActiveItem > itemsLength - 1) {
147
+ nextActiveItem = itemsLength - 1;
148
+ }
149
+
150
+ // Handle changing scrollEnabled independent of user -> carousel interaction
151
+ if (scrollEnabled !== prevProps.scrollEnabled) {
152
+ this._setScrollEnabled(scrollEnabled);
153
+ }
154
+
155
+ if (interpolators.length !== itemsLength || hasNewSize) {
156
+ this._activeItem = nextActiveItem;
157
+ this._previousItemsLength = itemsLength;
158
+
159
+ this._initPositionsAndInterpolators(this.props);
160
+
161
+ // Handle scroll issue when dynamically removing items (see #133)
162
+ // This also fixes first item's active state on Android
163
+ // Because 'initialScrollIndex' apparently doesn't trigger scroll
164
+ if (this._previousItemsLength > itemsLength) {
165
+ this._hackActiveSlideAnimation(nextActiveItem);
166
+ }
167
+
168
+ if (hasNewSize) {
169
+ this._snapToItem(nextActiveItem, false, false, true);
170
+ }
171
+ } else if (
172
+ nextFirstItem !== this._previousFirstItem &&
173
+ nextFirstItem !== this._activeItem
174
+ ) {
175
+ this._activeItem = nextFirstItem;
176
+ this._previousFirstItem = nextFirstItem;
177
+ this._snapToItem(nextFirstItem, false, true, true);
178
+ }
179
+
180
+ if (this.props.onScroll !== prevProps.onScroll) {
181
+ this._setScrollHandler(this.props);
182
+ }
183
+ }
184
+
185
+ componentWillUnmount() {
186
+ this._mounted = false;
187
+ this.stopAutoplay();
188
+ // @ts-expect-error setTimeout / clearTiemout is buggy :/
189
+ clearTimeout(this._initTimeout);
190
+ // @ts-expect-error setTimeout / clearTiemout is buggy :/
191
+ clearTimeout(this._apparitionTimeout);
192
+ // @ts-expect-error setTimeout / clearTiemout is buggy :/
193
+ clearTimeout(this._hackSlideAnimationTimeout);
194
+ // @ts-expect-error setTimeout / clearTiemout is buggy :/
195
+ clearTimeout(this._enableAutoplayTimeout);
196
+ // @ts-expect-error setTimeout / clearTiemout is buggy :/
197
+ clearTimeout(this._autoplayTimeout);
198
+ // @ts-expect-error setTimeout / clearTiemout is buggy :/
199
+ clearTimeout(this._snapNoMomentumTimeout);
200
+ // @ts-expect-error setTimeout / clearTiemout is buggy :/
201
+ clearTimeout(this._androidRepositioningTimeout);
202
+ }
203
+
204
+ get realIndex() {
205
+ return this._activeItem;
206
+ }
207
+
208
+ get currentIndex() {
209
+ return this._getDataIndex(this._activeItem);
210
+ }
211
+
212
+ get currentScrollPosition() {
213
+ return this._currentScrollOffset;
214
+ }
215
+
216
+ _setScrollHandler(props) {
217
+ // Native driver for scroll events
218
+ const scrollEventConfig = {
219
+ listener: this._onScroll,
220
+ useNativeDriver: true,
221
+ };
222
+ this._scrollPos = new Animated.Value(0);
223
+ const argMapping = props.vertical
224
+ ? [{ nativeEvent: { contentOffset: { y: this._scrollPos } } }]
225
+ : [{ nativeEvent: { contentOffset: { x: this._scrollPos } } }];
226
+
227
+ // @ts-expect-error Let's ignore for now that trick
228
+ if (props.onScroll && Array.isArray(props.onScroll._argMapping)) {
229
+ // Because of a react-native issue https://github.com/facebook/react-native/issues/13294
230
+ argMapping.pop();
231
+ // @ts-expect-error Let's ignore for now that trick
232
+ const [argMap] = props.onScroll._argMapping;
233
+ if (
234
+ argMap &&
235
+ argMap.nativeEvent &&
236
+ argMap.nativeEvent.contentOffset
237
+ ) {
238
+ // Shares the same animated value passed in props
239
+ this._scrollPos =
240
+ argMap.nativeEvent.contentOffset.x ||
241
+ argMap.nativeEvent.contentOffset.y ||
242
+ this._scrollPos;
243
+ }
244
+ // @ts-expect-error Let's ignore for now that trick
245
+ argMapping.push(...props.onScroll._argMapping);
246
+ }
247
+ this._onScrollHandler = Animated.event(argMapping, scrollEventConfig);
248
+ }
249
+
250
+ // This will return a future-proof version code number compatible with semantic versioning
251
+ // Examples: 0.59.3 -> 5903 / 0.61.4 -> 6104 / 0.62.12 -> 6212 / 1.0.2 -> 10002
252
+ // _getRNVersionCode () {
253
+ // const version = RN_PACKAGE && RN_PACKAGE.version;
254
+ // if (!version) {
255
+ // return null;
256
+ // }
257
+ // const versionSplit = version.split('.');
258
+ // if (!versionSplit || !versionSplit.length) {
259
+ // return null;
260
+ // }
261
+ // return versionSplit[0] * 10000 +
262
+ // (typeof versionSplit[1] !== 'undefined' ? versionSplit[1] * 100 : 0) +
263
+ // (typeof versionSplit[2] !== 'undefined' ? versionSplit[2] * 1 : 0);
264
+ // }
265
+
266
+ _displayWarnings(props = this.props) {
267
+ const pluginName = 'react-native-snap-carousel';
268
+ const removedProps = [
269
+ 'activeAnimationType',
270
+ 'activeAnimationOptions',
271
+ 'enableMomentum',
272
+ 'lockScrollTimeoutDuration',
273
+ 'lockScrollWhileSnapping',
274
+ 'onBeforeSnapToItem',
275
+ 'swipeThreshold',
276
+ ];
277
+
278
+ // if (this._RNVersionCode && this._RNVersionCode < 5800) {
279
+ // console.error(
280
+ // `${pluginName}: Version 4+ of the plugin is based on React Native props that were introduced in version 0.58. ` +
281
+ // 'Please downgrade to version 3.x or update your version of React Native.'
282
+ // );
283
+ // }
284
+ if (!props.vertical && (!props.sliderWidth || !props.itemWidth)) {
285
+ console.error(
286
+ `${pluginName}: You need to specify both 'sliderWidth' and 'itemWidth' for horizontal carousels`,
287
+ );
288
+ }
289
+ if (props.vertical && (!props.sliderHeight || !props.itemHeight)) {
290
+ console.error(
291
+ `${pluginName}: You need to specify both 'sliderHeight' and 'itemHeight' for vertical carousels`,
292
+ );
293
+ }
294
+
295
+ removedProps.forEach((removedProp) => {
296
+ if (removedProp in props) {
297
+ console.warn(
298
+ `${pluginName}: Prop ${removedProp} has been removed in version 4 of the plugin`,
299
+ );
300
+ }
301
+ });
302
+ }
303
+
304
+ _needsScrollView() {
305
+ const { useScrollView } = this.props;
306
+ // Android's cell renderer is buggy and has a stange overflow
307
+ // TODO: a workaround might be to pass the custom animated styles directly to it
308
+ return IS_ANDROID
309
+ ? useScrollView ||
310
+ !Animated.FlatList ||
311
+ this._shouldUseStackLayout() ||
312
+ this._shouldUseTinderLayout()
313
+ : useScrollView || !Animated.FlatList;
314
+ }
315
+
316
+ _needsRTLAdaptations() {
317
+ const { vertical } = this.props;
318
+ return IS_RTL && IS_ANDROID && !vertical;
319
+ }
320
+
321
+ _enableLoop() {
322
+ const { data, enableSnap, loop } = this.props;
323
+ return enableSnap && loop && data && data.length && data.length > 1;
324
+ }
325
+
326
+ _shouldAnimateSlides(props = this.props) {
327
+ const {
328
+ inactiveSlideOpacity,
329
+ inactiveSlideScale,
330
+ scrollInterpolator,
331
+ slideInterpolatedStyle,
332
+ } = props;
333
+ return (
334
+ inactiveSlideOpacity < 1 ||
335
+ inactiveSlideScale < 1 ||
336
+ !!scrollInterpolator ||
337
+ !!slideInterpolatedStyle ||
338
+ this._shouldUseShiftLayout() ||
339
+ this._shouldUseStackLayout() ||
340
+ this._shouldUseTinderLayout()
341
+ );
342
+ }
343
+
344
+ _shouldUseShiftLayout() {
345
+ const { inactiveSlideShift, layout } = this.props;
346
+ return layout === 'default' && inactiveSlideShift !== 0;
347
+ }
348
+
349
+ _shouldUseStackLayout() {
350
+ return this.props.layout === 'stack';
351
+ }
352
+
353
+ _shouldUseTinderLayout() {
354
+ return this.props.layout === 'tinder';
355
+ }
356
+
357
+ _shouldRepositionScroll(index) {
358
+ const { data, enableSnap, loopClonesPerSide } = this.props;
359
+ const dataLength = data && data.length;
360
+ if (
361
+ !enableSnap ||
362
+ !dataLength ||
363
+ !this._enableLoop() ||
364
+ (index >= loopClonesPerSide &&
365
+ index < dataLength + loopClonesPerSide)
366
+ ) {
367
+ return false;
368
+ }
369
+ return true;
370
+ }
371
+
372
+ _roundNumber(num, decimals = 1) {
373
+ // https://stackoverflow.com/a/41716722/
374
+ const rounder = Math.pow(10, decimals);
375
+ return Math.round((num + Number.EPSILON) * rounder) / rounder;
376
+ }
377
+
378
+ _isMultiple(x, y) {
379
+ // This prevents Javascript precision issues: https://stackoverflow.com/a/58440614/
380
+ // Required because Android viewport size can return pretty complicated decimals numbers
381
+ return Math.round(Math.round(x / y) / (1 / y)) === Math.round(x);
382
+ }
383
+
384
+ _getCustomData(props = this.props) {
385
+ const { data, loopClonesPerSide } = props;
386
+ const dataLength = data && data.length;
387
+
388
+ if (!dataLength) {
389
+ return [];
390
+ }
391
+
392
+ if (!this._enableLoop()) {
393
+ return data;
394
+ }
395
+
396
+ let previousItems = [];
397
+ let nextItems = [];
398
+
399
+ if (loopClonesPerSide > dataLength) {
400
+ const dataMultiplier = Math.floor(loopClonesPerSide / dataLength);
401
+ const remainder = loopClonesPerSide % dataLength;
402
+
403
+ for (let i = 0; i < dataMultiplier; i++) {
404
+ previousItems.push(...data);
405
+ nextItems.push(...data);
406
+ }
407
+
408
+ previousItems.unshift(...data.slice(-remainder));
409
+ nextItems.push(...data.slice(0, remainder));
410
+ } else {
411
+ previousItems = data.slice(-loopClonesPerSide);
412
+ nextItems = data.slice(0, loopClonesPerSide);
413
+ }
414
+
415
+ return previousItems.concat(data, nextItems);
416
+ }
417
+
418
+ _getCustomDataLength(props = this.props) {
419
+ const { data, loopClonesPerSide } = props;
420
+ const dataLength = data && data.length;
421
+
422
+ if (!dataLength) {
423
+ return 0;
424
+ }
425
+
426
+ return this._enableLoop()
427
+ ? dataLength + 2 * loopClonesPerSide
428
+ : dataLength;
429
+ }
430
+
431
+ _getCustomIndex(index, props = this.props) {
432
+ const itemsLength = this._getCustomDataLength(props);
433
+
434
+ if (!itemsLength || typeof index === 'undefined') {
435
+ return 0;
436
+ }
437
+
438
+ return this._needsRTLAdaptations() ? itemsLength - index - 1 : index;
439
+ }
440
+
441
+ _getDataIndex(index) {
442
+ const { data, loopClonesPerSide } = this.props;
443
+ const dataLength = data && data.length;
444
+ console.log('Data index: ', index);
445
+ if (!this._enableLoop() || !dataLength) {
446
+ return index;
447
+ }
448
+
449
+ if (index >= dataLength + loopClonesPerSide) {
450
+ return loopClonesPerSide > dataLength
451
+ ? (index - loopClonesPerSide) % dataLength
452
+ : index - dataLength - loopClonesPerSide;
453
+ } else if (index < loopClonesPerSide) {
454
+ // TODO: is there a simpler way of determining the interpolated index?
455
+ if (loopClonesPerSide > dataLength) {
456
+ const baseDataIndexes = [];
457
+ const dataIndexes = [];
458
+ const dataMultiplier = Math.floor(
459
+ loopClonesPerSide / dataLength,
460
+ );
461
+ const remainder = loopClonesPerSide % dataLength;
462
+
463
+ for (let i = 0; i < dataLength; i++) {
464
+ baseDataIndexes.push(i);
465
+ }
466
+
467
+ for (let j = 0; j < dataMultiplier; j++) {
468
+ dataIndexes.push(...baseDataIndexes);
469
+ }
470
+
471
+ dataIndexes.unshift(...baseDataIndexes.slice(-remainder));
472
+ return dataIndexes[index];
473
+ } else {
474
+ return index + dataLength - loopClonesPerSide;
475
+ }
476
+ } else {
477
+ return index - loopClonesPerSide;
478
+ }
479
+ }
480
+
481
+ // Used with `snapToItem()` and 'PaginationDot'
482
+ _getPositionIndex(index) {
483
+ const { loop, loopClonesPerSide } = this.props;
484
+ return loop ? index + loopClonesPerSide : index;
485
+ }
486
+
487
+ _getSnapOffsets(props = this.props) {
488
+ const offset = this._getItemMainDimension();
489
+ return [...Array(this._getCustomDataLength(props))].map((_, i) => {
490
+ return i * offset;
491
+ });
492
+ }
493
+
494
+ _getFirstItem(index, props = this.props) {
495
+ const { loopClonesPerSide } = props;
496
+ const itemsLength = this._getCustomDataLength(props);
497
+
498
+ if (!itemsLength || index > itemsLength - 1 || index < 0) {
499
+ return 0;
500
+ }
501
+
502
+ return this._enableLoop() ? index + loopClonesPerSide : index;
503
+ }
504
+
505
+ _getWrappedRef() {
506
+ // Starting with RN 0.62, we should no longer call `getNode()` on the ref of an Animated component
507
+ if (
508
+ this._carouselRef &&
509
+ ((this._needsScrollView() && this._carouselRef.scrollTo) ||
510
+ (!this._needsScrollView() && this._carouselRef.scrollToOffset))
511
+ ) {
512
+ return this._carouselRef;
513
+ }
514
+ // https://github.com/facebook/react-native/issues/10635
515
+ // https://stackoverflow.com/a/48786374/8412141
516
+ return (
517
+ this._carouselRef &&
518
+ // @ts-expect-error This is for before 0.62
519
+ this._carouselRef.getNode &&
520
+ // @ts-expect-error This is for before 0.62
521
+ this._carouselRef.getNode()
522
+ );
523
+ }
524
+
525
+ _getScrollEnabled() {
526
+ return this._scrollEnabled;
527
+ }
528
+
529
+ _setScrollEnabled(scrollEnabled = true) {
530
+ const wrappedRef = this._getWrappedRef();
531
+
532
+ if (!wrappedRef || !wrappedRef.setNativeProps) {
533
+ return;
534
+ }
535
+
536
+ // 'setNativeProps()' is used instead of 'setState()' because the latter
537
+ // really takes a toll on Android behavior when momentum is disabled
538
+ wrappedRef.setNativeProps({ scrollEnabled });
539
+ this._scrollEnabled = scrollEnabled;
540
+ }
541
+
542
+ _getItemMainDimension() {
543
+ return this.props.vertical
544
+ ? this.props.itemHeight
545
+ : this.props.itemWidth;
546
+ }
547
+
548
+ _getItemScrollOffset(index) {
549
+ return (
550
+ this._positions &&
551
+ this._positions[index] &&
552
+ this._positions[index].start
553
+ );
554
+ }
555
+
556
+ _getItemLayout(_, index) {
557
+ const itemMainDimension = this._getItemMainDimension();
558
+ return {
559
+ index,
560
+ length: itemMainDimension,
561
+ offset: itemMainDimension * index, // + this._getContainerInnerMargin()
562
+ };
563
+ }
564
+
565
+ // This will allow us to have a proper zIndex even with a FlatList
566
+ // https://github.com/facebook/react-native/issues/18616#issuecomment-389444165
567
+ _getCellRendererComponent({ children, index, style, ...props }) {
568
+ const cellStyle = [
569
+ style,
570
+ !IS_ANDROID ? { zIndex: this._getCustomDataLength() - index } : {},
571
+ ];
572
+
573
+ return (
574
+ <View style={cellStyle} key={index} {...props}>
575
+ {children}
576
+ </View>
577
+ );
578
+ }
579
+
580
+ _getKeyExtractor(_, index) {
581
+ return this._needsScrollView()
582
+ ? `scrollview-item-${index}`
583
+ : `flatlist-item-${index}`;
584
+ }
585
+
586
+ _getScrollOffset(event) {
587
+ const { vertical } = this.props;
588
+ return (
589
+ (event &&
590
+ event.nativeEvent &&
591
+ event.nativeEvent.contentOffset &&
592
+ event.nativeEvent.contentOffset[vertical ? 'y' : 'x']) ||
593
+ 0
594
+ );
595
+ }
596
+
597
+ _getContainerInnerMargin(opposite = false) {
598
+ const { activeSlideAlignment } = this.props;
599
+
600
+ if (
601
+ (activeSlideAlignment === 'start' && !opposite) ||
602
+ (activeSlideAlignment === 'end' && opposite)
603
+ ) {
604
+ return 0;
605
+ } else if (
606
+ (activeSlideAlignment === 'end' && !opposite) ||
607
+ (activeSlideAlignment === 'start' && opposite)
608
+ ) {
609
+ return this.props.vertical
610
+ ? this.props.sliderHeight - this.props.itemHeight
611
+ : this.props.sliderWidth - this.props.itemWidth;
612
+ } else {
613
+ return this.props.vertical
614
+ ? (this.props.sliderHeight - this.props.itemHeight) / 2
615
+ : (this.props.sliderWidth - this.props.itemWidth) / 2;
616
+ }
617
+ }
618
+
619
+ _getActiveSlideOffset() {
620
+ const { activeSlideOffset } = this.props;
621
+ const itemMainDimension = this._getItemMainDimension();
622
+ const minOffset = 10;
623
+ // Make sure activeSlideOffset never prevents the active area from being at least 10 px wide
624
+ return itemMainDimension / 2 - activeSlideOffset >= minOffset
625
+ ? activeSlideOffset
626
+ : minOffset;
627
+ }
628
+
629
+ _getActiveItem(offset) {
630
+ const itemMainDimension = this._getItemMainDimension();
631
+ const center = offset + itemMainDimension / 2;
632
+ const activeSlideOffset = this._getActiveSlideOffset();
633
+ const lastIndex = this._positions.length - 1;
634
+ let itemIndex;
635
+
636
+ if (offset <= 0) {
637
+ return 0;
638
+ }
639
+
640
+ if (
641
+ this._positions[lastIndex] &&
642
+ offset >= this._positions[lastIndex].start
643
+ ) {
644
+ return lastIndex;
645
+ }
646
+
647
+ for (let i = 0; i < this._positions.length; i++) {
648
+ const { start, end } = this._positions[i];
649
+ if (
650
+ center + activeSlideOffset >= start &&
651
+ center - activeSlideOffset <= end
652
+ ) {
653
+ itemIndex = i;
654
+ break;
655
+ }
656
+ }
657
+
658
+ return itemIndex || 0;
659
+ }
660
+
661
+ _getSlideInterpolatedStyle(index, animatedValue) {
662
+ const { layoutCardOffset, slideInterpolatedStyle } = this.props;
663
+
664
+ if (slideInterpolatedStyle) {
665
+ return slideInterpolatedStyle(index, animatedValue, this.props);
666
+ } else if (this._shouldUseTinderLayout()) {
667
+ return tinderAnimatedStyles(
668
+ index,
669
+ animatedValue,
670
+ this.props,
671
+ layoutCardOffset,
672
+ );
673
+ } else if (this._shouldUseStackLayout()) {
674
+ return stackAnimatedStyles(
675
+ index,
676
+ animatedValue,
677
+ this.props,
678
+ layoutCardOffset,
679
+ );
680
+ } else if (this._shouldUseShiftLayout()) {
681
+ return shiftAnimatedStyles(index, animatedValue, this.props);
682
+ } else {
683
+ return defaultAnimatedStyles(index, animatedValue, this.props);
684
+ }
685
+ }
686
+
687
+ _initPositionsAndInterpolators(props = this.props) {
688
+ const { data, scrollInterpolator } = props;
689
+ const itemMainDimension = this._getItemMainDimension();
690
+
691
+ if (!data || !data.length) {
692
+ return;
693
+ }
694
+
695
+ const interpolators = [];
696
+ this._positions = [];
697
+
698
+ this._getCustomData(props).forEach((_itemData, index) => {
699
+ const _index = this._getCustomIndex(index, props);
700
+ let animatedValue;
701
+
702
+ this._positions[index] = {
703
+ start: index * itemMainDimension,
704
+ end: index * itemMainDimension + itemMainDimension,
705
+ };
706
+
707
+ if (!this._shouldAnimateSlides(props) || !this._scrollPos) {
708
+ animatedValue = new Animated.Value(1);
709
+ } else {
710
+ let interpolator;
711
+
712
+ if (scrollInterpolator) {
713
+ interpolator = scrollInterpolator(_index, props);
714
+ } else if (this._shouldUseStackLayout()) {
715
+ interpolator = stackScrollInterpolator(_index, props);
716
+ } else if (this._shouldUseTinderLayout()) {
717
+ interpolator = tinderScrollInterpolator(_index, props);
718
+ }
719
+
720
+ if (
721
+ !interpolator ||
722
+ !interpolator.inputRange ||
723
+ !interpolator.outputRange
724
+ ) {
725
+ interpolator = defaultScrollInterpolator(_index, props);
726
+ }
727
+
728
+ animatedValue = this._scrollPos.interpolate({
729
+ ...interpolator,
730
+ extrapolate: 'clamp',
731
+ });
732
+ }
733
+
734
+ interpolators.push(animatedValue);
735
+ });
736
+
737
+ this.setState({ interpolators });
738
+ }
739
+
740
+ _hackActiveSlideAnimation(index, scrollValue = 1) {
741
+ const offset = this._getItemScrollOffset(index);
742
+
743
+ if (
744
+ !this._mounted ||
745
+ !this._carouselRef ||
746
+ typeof offset === 'undefined'
747
+ ) {
748
+ return;
749
+ }
750
+
751
+ const multiplier = this._currentScrollOffset === 0 ? 1 : -1;
752
+ const scrollDelta = scrollValue * multiplier;
753
+
754
+ this._scrollTo({ offset: offset + scrollDelta, animated: false });
755
+
756
+ // @ts-expect-error setTimeout / clearTiemout is buggy :/
757
+ clearTimeout(this._hackSlideAnimationTimeout);
758
+ this._hackSlideAnimationTimeout = setTimeout(() => {
759
+ this._scrollTo({ offset, animated: false });
760
+ }, 1); // works randomly when set to '0'
761
+ }
762
+
763
+ _repositionScroll(index, animated = false) {
764
+ const { data, loopClonesPerSide } = this.props;
765
+ const dataLength = data && data.length;
766
+
767
+ if (
768
+ typeof index === 'undefined' ||
769
+ !this._shouldRepositionScroll(index)
770
+ ) {
771
+ return;
772
+ }
773
+
774
+ let repositionTo = index;
775
+
776
+ if (index >= dataLength + loopClonesPerSide) {
777
+ repositionTo = index - dataLength;
778
+ } else if (index < loopClonesPerSide) {
779
+ repositionTo = index + dataLength;
780
+ }
781
+
782
+ this._snapToItem(repositionTo, animated, false);
783
+ }
784
+
785
+ _scrollTo({ offset, index, animated = true }) {
786
+ const { vertical } = this.props;
787
+ const wrappedRef = this._getWrappedRef();
788
+ if (
789
+ !this._mounted ||
790
+ !wrappedRef ||
791
+ (typeof offset === 'undefined' && typeof index === 'undefined')
792
+ ) {
793
+ return;
794
+ }
795
+
796
+ let scrollToOffset;
797
+ if (typeof index !== 'undefined') {
798
+ scrollToOffset = this._getItemScrollOffset(index);
799
+ } else {
800
+ scrollToOffset = offset;
801
+ }
802
+
803
+ if (typeof scrollToOffset === 'undefined') {
804
+ return;
805
+ }
806
+
807
+ const options = this._needsScrollView()
808
+ ? {
809
+ x: vertical ? 0 : offset,
810
+ y: vertical ? offset : 0,
811
+ animated,
812
+ }
813
+ : {
814
+ offset,
815
+ animated,
816
+ };
817
+
818
+ if (this._needsScrollView()) {
819
+ wrappedRef.scrollTo(options);
820
+ } else {
821
+ wrappedRef.scrollToOffset(options);
822
+ }
823
+ }
824
+
825
+ _onTouchStart(event) {
826
+ const { onTouchStart } = this.props;
827
+
828
+ // `onTouchStart` is fired even when `scrollEnabled` is set to `false`
829
+ if (this._getScrollEnabled() !== false && this._autoplaying) {
830
+ this.pauseAutoPlay();
831
+ }
832
+
833
+ onTouchStart && onTouchStart(event);
834
+ }
835
+
836
+ _onTouchEnd(event) {
837
+ const { onTouchEnd } = this.props;
838
+
839
+ if (
840
+ this._getScrollEnabled() !== false &&
841
+ this._autoplay &&
842
+ !this._autoplaying
843
+ ) {
844
+ // This event is buggy on Android, so a fallback is provided in _onMomentumScrollEnd()
845
+ this.startAutoplay();
846
+ }
847
+
848
+ onTouchEnd && onTouchEnd(event);
849
+ }
850
+
851
+ _onScroll(event) {
852
+ const { onScroll, onScrollIndexChanged } = this.props;
853
+ const scrollOffset = event
854
+ ? this._getScrollOffset(event)
855
+ : this._currentScrollOffset;
856
+ const nextActiveItem = this._getActiveItem(scrollOffset);
857
+
858
+ this._currentScrollOffset = scrollOffset;
859
+
860
+ if (nextActiveItem !== this._onScrollActiveItem) {
861
+ this._onScrollActiveItem = nextActiveItem;
862
+ onScrollIndexChanged &&
863
+ onScrollIndexChanged(this._getDataIndex(nextActiveItem));
864
+ }
865
+
866
+ if (typeof onScroll === 'function' && event) {
867
+ onScroll(event);
868
+ }
869
+ }
870
+
871
+ _onMomentumScrollEnd(event) {
872
+ const { autoplayDelay, onMomentumScrollEnd, onSnapToItem } = this.props;
873
+ const scrollOffset = event
874
+ ? this._getScrollOffset(event)
875
+ : this._currentScrollOffset;
876
+ const nextActiveItem = this._getActiveItem(scrollOffset);
877
+ const hasSnapped = this._isMultiple(
878
+ scrollOffset,
879
+ this.props.vertical ? this.props.itemHeight : this.props.itemWidth,
880
+ );
881
+
882
+ // WARNING: everything in this condition will probably need to be called on _snapToItem as well because:
883
+ // 1. `onMomentumScrollEnd` won't be called if the scroll isn't animated
884
+ // 2. `onMomentumScrollEnd` won't be called at all on Android when scrolling programmatically
885
+ if (nextActiveItem !== this._activeItem) {
886
+ this._activeItem = nextActiveItem;
887
+ onSnapToItem && onSnapToItem(this._getDataIndex(nextActiveItem));
888
+
889
+ if (hasSnapped) {
890
+ this._repositionScroll(nextActiveItem);
891
+ }
892
+ }
893
+
894
+ onMomentumScrollEnd && onMomentumScrollEnd(event);
895
+
896
+ // The touchEnd event is buggy on Android, so this will serve as a fallback whenever needed
897
+ // https://github.com/facebook/react-native/issues/9439
898
+ if (IS_ANDROID && this._autoplay && !this._autoplaying) {
899
+ // @ts-expect-error setTimeout / clearTiemout is buggy :/
900
+ clearTimeout(this._enableAutoplayTimeout);
901
+ this._enableAutoplayTimeout = setTimeout(() => {
902
+ this.startAutoplay();
903
+ }, autoplayDelay);
904
+ }
905
+ }
906
+
907
+ _onLayout(event) {
908
+ const { onLayout } = this.props;
909
+
910
+ // Prevent unneeded actions during the first 'onLayout' (triggered on init)
911
+ if (this._onLayoutInitDone) {
912
+ this._initPositionsAndInterpolators();
913
+ this._snapToItem(this._activeItem, false, false, true);
914
+ } else {
915
+ this._onLayoutInitDone = true;
916
+ }
917
+
918
+ onLayout && onLayout(event);
919
+ }
920
+
921
+ _snapToItem(
922
+ index,
923
+ animated = true,
924
+ fireCallback = true,
925
+ forceScrollTo = false,
926
+ ) {
927
+ const { onSnapToItem } = this.props;
928
+ const itemsLength = this._getCustomDataLength();
929
+ const wrappedRef = this._getWrappedRef();
930
+ if (!itemsLength || !wrappedRef) {
931
+ return;
932
+ }
933
+
934
+ if (!index || index < 0) {
935
+ index = 0;
936
+ } else if (itemsLength > 0 && index >= itemsLength) {
937
+ index = itemsLength - 1;
938
+ }
939
+
940
+ if (index === this._activeItem && !forceScrollTo) {
941
+ return;
942
+ }
943
+
944
+ const offset = this._getItemScrollOffset(index);
945
+
946
+ if (offset === undefined) {
947
+ return;
948
+ }
949
+
950
+ this._scrollTo({ offset, animated });
951
+
952
+ // On both platforms, `onMomentumScrollEnd` won't be triggered if the scroll isn't animated
953
+ // so we need to trigger the callback manually
954
+ // On Android `onMomentumScrollEnd` won't be triggered when scrolling programmatically
955
+ // Therefore everything critical needs to be manually called here as well, even though the timing might be off
956
+ const requiresManualTrigger = !animated || IS_ANDROID;
957
+ if (requiresManualTrigger) {
958
+ this._activeItem = index;
959
+
960
+ if (fireCallback) {
961
+ onSnapToItem && onSnapToItem(this._getDataIndex(index));
962
+ }
963
+
964
+ // Repositioning on Android
965
+ if (IS_ANDROID && this._shouldRepositionScroll(index)) {
966
+ if (animated) {
967
+ this._androidRepositioningTimeout = setTimeout(() => {
968
+ // Without scroll animation, the behavior is completely buggy...
969
+ this._repositionScroll(index, false);
970
+ }, 400); // Approximate scroll duration on Android
971
+ } else {
972
+ this._repositionScroll(index);
973
+ }
974
+ }
975
+ }
976
+ }
977
+
978
+ startAutoplay() {
979
+ const { autoplayInterval, autoplayDelay } = this.props;
980
+ this._autoplay = true;
981
+
982
+ if (this._autoplaying) {
983
+ return;
984
+ }
985
+
986
+ // @ts-expect-error setTimeout / clearTiemout is buggy :/
987
+ clearTimeout(this._autoplayTimeout);
988
+ this._autoplayTimeout = setTimeout(() => {
989
+ this._autoplaying = true;
990
+ this._autoplayInterval = setInterval(() => {
991
+ if (this._autoplaying) {
992
+ this.snapToNext();
993
+ }
994
+ }, autoplayInterval);
995
+ }, autoplayDelay);
996
+ }
997
+
998
+ pauseAutoPlay() {
999
+ this._autoplaying = false;
1000
+ // @ts-expect-error setTimeout / clearTiemout is buggy :/
1001
+ clearTimeout(this._autoplayTimeout);
1002
+ // @ts-expect-error setTimeout / clearTiemout is buggy :/
1003
+ clearTimeout(this._enableAutoplayTimeout);
1004
+ // @ts-expect-error setTimeout / clearTiemout is buggy :/
1005
+ clearInterval(this._autoplayInterval);
1006
+ }
1007
+
1008
+ stopAutoplay() {
1009
+ this._autoplay = false;
1010
+ this.pauseAutoPlay();
1011
+ }
1012
+
1013
+ snapToItem(index, animated = true, fireCallback = true) {
1014
+ if (!index || index < 0) {
1015
+ index = 0;
1016
+ }
1017
+
1018
+ const positionIndex = this._getPositionIndex(index);
1019
+
1020
+ if (positionIndex === this._activeItem) {
1021
+ return;
1022
+ }
1023
+
1024
+ this._snapToItem(positionIndex, animated, fireCallback);
1025
+ }
1026
+
1027
+ snapToNext(animated = true, fireCallback = true) {
1028
+ const itemsLength = this._getCustomDataLength();
1029
+
1030
+ let newIndex = this._activeItem + 1;
1031
+ if (newIndex > itemsLength - 1) {
1032
+ newIndex = 0;
1033
+ }
1034
+ this._snapToItem(newIndex, animated, fireCallback);
1035
+ }
1036
+
1037
+ snapToPrev(animated = true, fireCallback = true) {
1038
+ const itemsLength = this._getCustomDataLength();
1039
+
1040
+ let newIndex = this._activeItem - 1;
1041
+ if (newIndex < 0) {
1042
+ newIndex = itemsLength - 1;
1043
+ }
1044
+ this._snapToItem(newIndex, animated, fireCallback);
1045
+ }
1046
+
1047
+ // https://github.com/facebook/react-native/issues/1831#issuecomment-231069668
1048
+ triggerRenderingHack(offset = 1) {
1049
+ this._hackActiveSlideAnimation(this._activeItem, offset);
1050
+ }
1051
+
1052
+ _renderItem({ item, index }) {
1053
+ const { interpolators } = this.state;
1054
+ const { keyExtractor, slideStyle } = this.props;
1055
+ const animatedValue = interpolators && interpolators[index];
1056
+
1057
+ if (typeof animatedValue === 'undefined') {
1058
+ return null;
1059
+ }
1060
+
1061
+ const animate = this._shouldAnimateSlides();
1062
+ const Component = animate ? Animated.View : View;
1063
+ const animatedStyle = animate
1064
+ ? this._getSlideInterpolatedStyle(index, animatedValue)
1065
+ : {};
1066
+ const dataIndex = this._getDataIndex(index);
1067
+
1068
+ const mainDimension = this.props.vertical
1069
+ ? { height: this.props.itemHeight }
1070
+ : { width: this.props.itemWidth };
1071
+ const specificProps = this._needsScrollView()
1072
+ ? {
1073
+ key: keyExtractor
1074
+ ? keyExtractor(item, index)
1075
+ : this._getKeyExtractor(item, index),
1076
+ }
1077
+ : {};
1078
+
1079
+ return (
1080
+ <Component
1081
+ style={[mainDimension, slideStyle, animatedStyle]}
1082
+ pointerEvents="box-none"
1083
+ {...specificProps}>
1084
+ {this.props.vertical
1085
+ ? this.props.renderItem(
1086
+ {
1087
+ item,
1088
+ index,
1089
+ dataIndex,
1090
+ realIndex: this._getDataIndex(index),
1091
+ activeIndex: this._getDataIndex(this._activeItem),
1092
+ },
1093
+ {
1094
+ scrollPosition: this._scrollPos,
1095
+ carouselRef: this._carouselRef,
1096
+ vertical: this.props.vertical,
1097
+ sliderHeight: this.props.sliderHeight,
1098
+ itemHeight: this.props.itemHeight,
1099
+ },
1100
+ )
1101
+ : this.props.renderItem(
1102
+ {
1103
+ item,
1104
+ index,
1105
+ dataIndex,
1106
+ realIndex: this._getDataIndex(index),
1107
+ activeIndex: this._getDataIndex(this._activeItem),
1108
+ },
1109
+ {
1110
+ scrollPosition: this._scrollPos,
1111
+ carouselRef: this._carouselRef,
1112
+ vertical: !!this.props.vertical,
1113
+ sliderWidth: this.props.sliderWidth,
1114
+ itemWidth: this.props.itemWidth,
1115
+ },
1116
+ )}
1117
+ </Component>
1118
+ );
1119
+ }
1120
+
1121
+ _getComponentOverridableProps() {
1122
+ const { hideCarousel } = this.state;
1123
+ const { loopClonesPerSide } = this.props;
1124
+ const visibleItems =
1125
+ Math.ceil(
1126
+ this.props.vertical
1127
+ ? this.props.sliderHeight / this.props.itemHeight
1128
+ : this.props.sliderWidth / this.props.itemWidth,
1129
+ ) + 1;
1130
+ const initialNumPerSide = this._enableLoop() ? loopClonesPerSide : 2;
1131
+ const initialNumToRender = visibleItems + initialNumPerSide * 2;
1132
+ const maxToRenderPerBatch = initialNumToRender + initialNumPerSide * 2;
1133
+ const windowSize = maxToRenderPerBatch;
1134
+
1135
+ const specificProps = !this._needsScrollView()
1136
+ ? {
1137
+ initialNumToRender,
1138
+ maxToRenderPerBatch,
1139
+ windowSize,
1140
+ // updateCellsBatchingPeriod
1141
+ }
1142
+ : {};
1143
+
1144
+ return {
1145
+ ...specificProps,
1146
+ automaticallyAdjustContentInsets: false,
1147
+ decelerationRate: 'fast',
1148
+ directionalLockEnabled: true,
1149
+ disableScrollViewPanResponder: false, // If set to `true`, touch events will be triggered too easily
1150
+ inverted: this._needsRTLAdaptations(),
1151
+ overScrollMode: 'never',
1152
+ pinchGestureEnabled: false,
1153
+ pointerEvents: hideCarousel ? 'none' : 'auto',
1154
+ // removeClippedSubviews: !this._needsScrollView(),
1155
+ // renderToHardwareTextureAndroid: true,
1156
+ scrollsToTop: false,
1157
+ showsHorizontalScrollIndicator: false,
1158
+ showsVerticalScrollIndicator: false,
1159
+ };
1160
+ }
1161
+
1162
+ _getComponentStaticProps() {
1163
+ const { hideCarousel } = this.state;
1164
+ const {
1165
+ activeSlideAlignment,
1166
+ CellRendererComponent,
1167
+ containerCustomStyle,
1168
+ contentContainerCustomStyle,
1169
+ firstItem,
1170
+ getItemLayout,
1171
+ keyExtractor,
1172
+ style,
1173
+ useExperimentalSnap,
1174
+ disableIntervalMomentum,
1175
+ } = this.props;
1176
+
1177
+ const containerStyle = [
1178
+ // { overflow: 'hidden' },
1179
+ containerCustomStyle || style || {},
1180
+ hideCarousel ? { opacity: 0 } : {},
1181
+ this.props.vertical
1182
+ ? { height: this.props.sliderHeight, flexDirection: 'column' } // LTR hack; see https://github.com/facebook/react-native/issues/11960
1183
+ : // and https://github.com/facebook/react-native/issues/13100#issuecomment-328986423
1184
+ {
1185
+ width: this.props.sliderWidth,
1186
+ flexDirection: this._needsRTLAdaptations()
1187
+ ? 'row-reverse'
1188
+ : 'row',
1189
+ },
1190
+ ];
1191
+
1192
+ const innerMarginStyle = this.props.vertical
1193
+ ? {
1194
+ paddingTop: this._getContainerInnerMargin(),
1195
+ paddingBottom: this._getContainerInnerMargin(true),
1196
+ }
1197
+ : {
1198
+ paddingLeft: this._getContainerInnerMargin(),
1199
+ paddingRight: this._getContainerInnerMargin(true),
1200
+ };
1201
+
1202
+ const contentContainerStyle = [
1203
+ !useExperimentalSnap ? innerMarginStyle : {},
1204
+ contentContainerCustomStyle || {},
1205
+ ];
1206
+
1207
+ // WARNING: `snapToAlignment` won't work as intended because of the following:
1208
+ // https://github.com/facebook/react-native/blob/d0871d0a9a373e1d3ac35da46c85c0d0e793116d/React/Views/ScrollView/RCTScrollView.m#L751-L755
1209
+ // - Snap points will be off
1210
+ // - Slide animations will be off
1211
+ // - Last items won't be set as active (no `onSnapToItem` callback)
1212
+ // Recommended only with large slides and `activeSlideAlignment` set to `start` for the time being
1213
+ const snapProps = useExperimentalSnap
1214
+ ? {
1215
+ disableIntervalMomentum, // Slide ± one item at a time
1216
+ snapToAlignment: activeSlideAlignment,
1217
+ snapToInterval: this._getItemMainDimension(),
1218
+ }
1219
+ : {
1220
+ snapToOffsets: this._getSnapOffsets(),
1221
+ };
1222
+
1223
+ // Flatlist specifics
1224
+ const specificProps = !this._needsScrollView()
1225
+ ? {
1226
+ CellRendererComponent:
1227
+ CellRendererComponent || this._getCellRendererComponent,
1228
+ getItemLayout: getItemLayout || this._getItemLayout,
1229
+ initialScrollIndex: this._getFirstItem(firstItem),
1230
+ keyExtractor: keyExtractor || this._getKeyExtractor,
1231
+ numColumns: 1,
1232
+ renderItem: this._renderItem,
1233
+ }
1234
+ : {};
1235
+
1236
+ return {
1237
+ ...specificProps,
1238
+ ...snapProps,
1239
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1240
+ ref: (c) => {
1241
+ this._carouselRef = c;
1242
+ },
1243
+ contentContainerStyle: contentContainerStyle,
1244
+ data: this._getCustomData(),
1245
+ horizontal: !this.props.vertical,
1246
+ scrollEventThrottle: 1,
1247
+ style: containerStyle,
1248
+ onLayout: this._onLayout,
1249
+ onMomentumScrollEnd: this._onMomentumScrollEnd,
1250
+ onScroll: this._onScrollHandler,
1251
+ onTouchStart: this._onTouchStart,
1252
+ onTouchEnd: this._onTouchEnd,
1253
+ };
1254
+ }
1255
+
1256
+ render() {
1257
+ const { data, renderItem, useScrollView } = this.props;
1258
+
1259
+ if (!data || !renderItem) {
1260
+ return null;
1261
+ }
1262
+
1263
+ const props = {
1264
+ ...this._getComponentOverridableProps(),
1265
+ ...this.props,
1266
+ ...this._getComponentStaticProps(),
1267
+ };
1268
+
1269
+ const ScrollViewComponent =
1270
+ typeof useScrollView === 'function'
1271
+ ? useScrollView
1272
+ : Animated.ScrollView;
1273
+
1274
+ return this._needsScrollView() || !Animated.FlatList ? (
1275
+ <ScrollViewComponent {...props}>
1276
+ {this._getCustomData().map((item, index) => {
1277
+ return this._renderItem({
1278
+ item,
1279
+ index,
1280
+ realIndex: this._getDataIndex(index),
1281
+ activeIndex: this._getDataIndex(this._activeItem),
1282
+ });
1283
+ })}
1284
+ </ScrollViewComponent>
1285
+ ) : (
1286
+ // @ts-expect-error Seems complicated to make TS 100% happy, while sharing that many things between
1287
+ // flatlist && scrollview implementation. I'll prob try to rewrite parts of the logic to overcome that.
1288
+ <Animated.FlatList {...props} />
1289
+ );
1290
+ }
1291
+ }
1292
+
1293
+ Carousel.propTypes = {
1294
+ data: PropTypes.array.isRequired,
1295
+ renderItem: PropTypes.func.isRequired,
1296
+ itemWidth: PropTypes.number, // required for horizontal carousel
1297
+ itemHeight: PropTypes.number, // required for vertical carousel
1298
+ sliderWidth: PropTypes.number, // required for horizontal carousel
1299
+ sliderHeight: PropTypes.number, // required for vertical carousel
1300
+ activeSlideAlignment: PropTypes.oneOf(['center', 'end', 'start']),
1301
+ activeSlideOffset: PropTypes.number,
1302
+ apparitionDelay: PropTypes.number,
1303
+ autoplay: PropTypes.bool,
1304
+ autoplayDelay: PropTypes.number,
1305
+ autoplayInterval: PropTypes.number,
1306
+ callbackOffsetMargin: PropTypes.number,
1307
+ containerCustomStyle: PropTypes.oneOfType([
1308
+ PropTypes.object,
1309
+ PropTypes.array,
1310
+ ]),
1311
+ contentContainerCustomStyle: PropTypes.oneOfType([
1312
+ PropTypes.object,
1313
+ PropTypes.array,
1314
+ ]),
1315
+ enableSnap: PropTypes.bool,
1316
+ firstItem: PropTypes.number,
1317
+ hasParallaxImages: PropTypes.bool,
1318
+ inactiveSlideOpacity: PropTypes.number,
1319
+ inactiveSlideScale: PropTypes.number,
1320
+ inactiveSlideShift: PropTypes.number,
1321
+ layout: PropTypes.oneOf(['default', 'stack', 'tinder']),
1322
+ layoutCardOffset: PropTypes.number,
1323
+ loop: PropTypes.bool,
1324
+ loopClonesPerSide: PropTypes.number,
1325
+ scrollEnabled: PropTypes.bool,
1326
+ scrollInterpolator: PropTypes.func,
1327
+ slideInterpolatedStyle: PropTypes.func,
1328
+ slideStyle: PropTypes.object,
1329
+ shouldOptimizeUpdates: PropTypes.bool,
1330
+ swipeThreshold: PropTypes.number,
1331
+ useScrollView: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
1332
+ vertical: PropTypes.bool,
1333
+ showsPagination: PropTypes.bool,
1334
+ isCustomScrollWidth: PropTypes.bool,
1335
+ disableIntervalMomentum: PropTypes.bool,
1336
+ useExperimentalSnap: PropTypes.bool,
1337
+ onBeforeSnapToItem: PropTypes.func,
1338
+ onSnapToItem: PropTypes.func,
1339
+ };
1340
+
1341
+ Carousel.defaultProps = {
1342
+ activeSlideAlignment: 'center',
1343
+ activeSlideOffset: 20,
1344
+ apparitionDelay: 0,
1345
+ autoplay: false,
1346
+ autoplayDelay: 1000,
1347
+ autoplayInterval: 3000,
1348
+ callbackOffsetMargin: 5,
1349
+ containerCustomStyle: {},
1350
+ contentContainerCustomStyle: {},
1351
+ enableSnap: true,
1352
+ firstItem: 0,
1353
+ hasParallaxImages: false,
1354
+ inactiveSlideOpacity: 0.7,
1355
+ inactiveSlideScale: 0.9,
1356
+ inactiveSlideShift: 0,
1357
+ layout: 'default',
1358
+ loop: false,
1359
+ loopClonesPerSide: 3,
1360
+ scrollEnabled: true,
1361
+ slideStyle: {},
1362
+ shouldOptimizeUpdates: true,
1363
+ useScrollView: !Animated.FlatList,
1364
+ vertical: false,
1365
+ isCustomScrollWidth: false,
1366
+ disableIntervalMomentum: IS_ANDROID,
1367
+ useExperimentalSnap: IS_ANDROID,
1368
+ };