@momo-kits/carousel 0.0.65-alpha.1 → 0.0.65-alpha.8

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