@momo-kits/carousel 0.0.39-beta → 0.0.40-beta.3

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