@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.
- package/CarouselV2.js +1221 -1208
- package/package.json +15 -15
- 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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
(
|
|
155
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
this._setScrollEnabled(scrollEnabled);
|
|
205
|
+
get realIndex() {
|
|
206
|
+
return this._activeItem;
|
|
165
207
|
}
|
|
166
208
|
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
193
|
-
|
|
213
|
+
get currentScrollPosition() {
|
|
214
|
+
return this._currentScrollOffset;
|
|
194
215
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
388
|
-
|
|
350
|
+
_shouldUseStackLayout() {
|
|
351
|
+
return this.props.layout === 'stack';
|
|
389
352
|
}
|
|
390
353
|
|
|
391
|
-
|
|
392
|
-
|
|
354
|
+
_shouldUseTinderLayout() {
|
|
355
|
+
return this.props.layout === 'tinder';
|
|
356
|
+
}
|
|
393
357
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
411
|
-
|
|
385
|
+
_getCustomData(props = this.props) {
|
|
386
|
+
const { data, loopClonesPerSide } = props;
|
|
387
|
+
const dataLength = data && data.length;
|
|
412
388
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
389
|
+
if (!dataLength) {
|
|
390
|
+
return [];
|
|
391
|
+
}
|
|
416
392
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
393
|
+
if (!this._enableLoop()) {
|
|
394
|
+
return data;
|
|
395
|
+
}
|
|
420
396
|
|
|
421
|
-
|
|
422
|
-
|
|
397
|
+
let previousItems = [];
|
|
398
|
+
let nextItems = [];
|
|
423
399
|
|
|
424
|
-
|
|
425
|
-
|
|
400
|
+
if (loopClonesPerSide > dataLength) {
|
|
401
|
+
const dataMultiplier = Math.floor(loopClonesPerSide / dataLength);
|
|
402
|
+
const remainder = loopClonesPerSide % dataLength;
|
|
426
403
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
404
|
+
for (let i = 0; i < dataMultiplier; i++) {
|
|
405
|
+
previousItems.push(...data);
|
|
406
|
+
nextItems.push(...data);
|
|
407
|
+
}
|
|
430
408
|
|
|
431
|
-
|
|
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
|
-
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
-
|
|
458
|
-
|
|
423
|
+
if (!dataLength) {
|
|
424
|
+
return 0;
|
|
459
425
|
}
|
|
460
426
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
-
|
|
506
|
-
//
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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
|
-
|
|
635
|
-
|
|
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
|
-
|
|
646
|
-
|
|
530
|
+
_setScrollEnabled(scrollEnabled = true) {
|
|
531
|
+
const wrappedRef = this._getWrappedRef();
|
|
647
532
|
|
|
648
|
-
|
|
649
|
-
|
|
533
|
+
if (!wrappedRef || !wrappedRef.setNativeProps) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
650
536
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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
|
-
|
|
675
|
-
|
|
676
|
-
|
|
543
|
+
_getItemMainDimension() {
|
|
544
|
+
return this.props.vertical
|
|
545
|
+
? this.props.itemHeight
|
|
546
|
+
: this.props.itemWidth;
|
|
547
|
+
}
|
|
677
548
|
|
|
678
|
-
|
|
679
|
-
|
|
549
|
+
_getItemScrollOffset(index) {
|
|
550
|
+
return (
|
|
551
|
+
this._positions &&
|
|
552
|
+
this._positions[index] &&
|
|
553
|
+
this._positions[index].start
|
|
554
|
+
);
|
|
680
555
|
}
|
|
681
556
|
|
|
682
|
-
|
|
683
|
-
|
|
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
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
581
|
+
_getKeyExtractor(_, index) {
|
|
582
|
+
return this._needsScrollView()
|
|
583
|
+
? `scrollview-item-${index}`
|
|
584
|
+
: `flatlist-item-${index}`;
|
|
585
|
+
}
|
|
693
586
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|
-
|
|
700
|
-
|
|
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
|
-
|
|
709
|
-
|
|
710
|
-
!interpolator.outputRange
|
|
602
|
+
(activeSlideAlignment === 'start' && !opposite) ||
|
|
603
|
+
(activeSlideAlignment === 'end' && opposite)
|
|
711
604
|
) {
|
|
712
|
-
|
|
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
|
-
|
|
716
|
-
|
|
717
|
-
|
|
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
|
-
|
|
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
|
-
|
|
725
|
-
|
|
637
|
+
if (offset <= 0) {
|
|
638
|
+
return 0;
|
|
639
|
+
}
|
|
726
640
|
|
|
727
|
-
|
|
728
|
-
|
|
641
|
+
if (
|
|
642
|
+
this._positions[lastIndex] &&
|
|
643
|
+
offset >= this._positions[lastIndex].start
|
|
644
|
+
) {
|
|
645
|
+
return lastIndex;
|
|
646
|
+
}
|
|
729
647
|
|
|
730
|
-
|
|
731
|
-
|
|
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
|
-
|
|
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
|
-
|
|
662
|
+
_getSlideInterpolatedStyle(index, animatedValue) {
|
|
663
|
+
const { layoutCardOffset, slideInterpolatedStyle } = this.props;
|
|
761
664
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
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
|
-
|
|
769
|
-
|
|
688
|
+
_initPositionsAndInterpolators(props = this.props) {
|
|
689
|
+
const { data, scrollInterpolator } = props;
|
|
690
|
+
const itemMainDimension = this._getItemMainDimension();
|
|
770
691
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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
|
-
|
|
790
|
-
return;
|
|
738
|
+
this.setState({ interpolators });
|
|
791
739
|
}
|
|
792
740
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|
-
|
|
805
|
-
|
|
806
|
-
} else {
|
|
807
|
-
wrappedRef.scrollToOffset(options);
|
|
808
|
-
}
|
|
809
|
-
}
|
|
752
|
+
const multiplier = this._currentScrollOffset === 0 ? 1 : -1;
|
|
753
|
+
const scrollDelta = scrollValue * multiplier;
|
|
810
754
|
|
|
811
|
-
|
|
812
|
-
const {onTouchStart} = this.props;
|
|
755
|
+
this._scrollTo({ offset: offset + scrollDelta, animated: false });
|
|
813
756
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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
|
-
|
|
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
|
-
|
|
823
|
-
|
|
777
|
+
if (index >= dataLength + loopClonesPerSide) {
|
|
778
|
+
repositionTo = index - dataLength;
|
|
779
|
+
} else if (index < loopClonesPerSide) {
|
|
780
|
+
repositionTo = index + dataLength;
|
|
781
|
+
}
|
|
824
782
|
|
|
825
|
-
|
|
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
|
-
|
|
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
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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
|
-
|
|
804
|
+
if (typeof scrollToOffset === 'undefined') {
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
847
807
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
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
|
-
|
|
855
|
-
|
|
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
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
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
|
-
|
|
837
|
+
_onTouchEnd(event) {
|
|
838
|
+
const { onTouchEnd } = this.props;
|
|
894
839
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
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
|
-
|
|
907
|
-
|
|
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
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
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
|
-
|
|
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
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
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
|
-
|
|
940
|
-
|
|
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
|
-
|
|
908
|
+
onMomentumScrollEnd && onMomentumScrollEnd(event);
|
|
944
909
|
|
|
945
|
-
|
|
946
|
-
|
|
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
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
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
|
-
|
|
929
|
+
this._onLayoutInitDone = true;
|
|
975
930
|
}
|
|
976
|
-
|
|
931
|
+
|
|
932
|
+
onLayout && onLayout(event);
|
|
977
933
|
}
|
|
978
|
-
}
|
|
979
934
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
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
|
-
|
|
985
|
-
|
|
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
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
this._autoplayInterval = setInterval(() => {
|
|
992
|
+
startAutoplay() {
|
|
993
|
+
const { autoplayInterval, autoplayDelay } = this.props;
|
|
994
|
+
this._autoplay = true;
|
|
995
|
+
|
|
993
996
|
if (this._autoplaying) {
|
|
994
|
-
|
|
997
|
+
return;
|
|
995
998
|
}
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1023
|
-
|
|
1022
|
+
stopAutoplay() {
|
|
1023
|
+
this._autoplay = false;
|
|
1024
|
+
this.pauseAutoPlay();
|
|
1024
1025
|
}
|
|
1025
1026
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1027
|
+
snapToItem(index, animated = true, fireCallback = true) {
|
|
1028
|
+
if (!index || index < 0) {
|
|
1029
|
+
index = 0;
|
|
1030
|
+
}
|
|
1028
1031
|
|
|
1029
|
-
|
|
1030
|
-
|
|
1032
|
+
const positionIndex = this._getPositionIndex(index);
|
|
1033
|
+
|
|
1034
|
+
if (positionIndex === this._activeItem) {
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1031
1037
|
|
|
1032
|
-
|
|
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
|
-
|
|
1040
|
-
|
|
1041
|
+
snapToNext(animated = true, fireCallback = true) {
|
|
1042
|
+
const itemsLength = this._getCustomDataLength();
|
|
1041
1043
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
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
|
-
|
|
1050
|
-
|
|
1051
|
-
this._hackActiveSlideAnimation(this._activeItem, offset);
|
|
1052
|
-
}
|
|
1051
|
+
snapToPrev(animated = true, fireCallback = true) {
|
|
1052
|
+
const itemsLength = this._getCustomDataLength();
|
|
1053
1053
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
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
|
-
|
|
1060
|
-
|
|
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
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
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
|
-
|
|
1205
|
-
|
|
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
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
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
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
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
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
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
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
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
|
};
|