@momo-kits/carousel 0.0.65-alpha.2 → 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 +1223 -1193
- 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,1337 +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
|
+
});
|
|
284
303
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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;
|
|
289
315
|
}
|
|
290
316
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
`${pluginName}: Prop ${removedProp} has been removed in version 4 of the plugin`,
|
|
295
|
-
);
|
|
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;
|
|
317
|
+
_needsRTLAdaptations() {
|
|
318
|
+
const { vertical } = this.props;
|
|
319
|
+
return IS_RTL && IS_ANDROID && !vertical;
|
|
363
320
|
}
|
|
364
|
-
return true;
|
|
365
|
-
}
|
|
366
321
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
322
|
+
_enableLoop() {
|
|
323
|
+
const { data, enableSnap, loop } = this.props;
|
|
324
|
+
return enableSnap && loop && data && data.length && data.length > 1;
|
|
325
|
+
}
|
|
372
326
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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()
|
|
342
|
+
);
|
|
343
|
+
}
|
|
376
344
|
|
|
377
|
-
|
|
378
|
-
|
|
345
|
+
_shouldUseShiftLayout() {
|
|
346
|
+
const { inactiveSlideShift, layout } = this.props;
|
|
347
|
+
return layout === 'default' && inactiveSlideShift !== 0;
|
|
379
348
|
}
|
|
380
349
|
|
|
381
|
-
|
|
382
|
-
|
|
350
|
+
_shouldUseStackLayout() {
|
|
351
|
+
return this.props.layout === 'stack';
|
|
383
352
|
}
|
|
384
353
|
|
|
385
|
-
|
|
386
|
-
|
|
354
|
+
_shouldUseTinderLayout() {
|
|
355
|
+
return this.props.layout === 'tinder';
|
|
356
|
+
}
|
|
387
357
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
+
}
|
|
391
372
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
+
}
|
|
396
378
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
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);
|
|
402
383
|
}
|
|
403
384
|
|
|
404
|
-
|
|
405
|
-
|
|
385
|
+
_getCustomData(props = this.props) {
|
|
386
|
+
const { data, loopClonesPerSide } = props;
|
|
387
|
+
const dataLength = data && data.length;
|
|
388
|
+
|
|
389
|
+
if (!dataLength) {
|
|
390
|
+
return [];
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (!this._enableLoop()) {
|
|
394
|
+
return data;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
let previousItems = [];
|
|
398
|
+
let nextItems = [];
|
|
406
399
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
400
|
+
if (loopClonesPerSide > dataLength) {
|
|
401
|
+
const dataMultiplier = Math.floor(loopClonesPerSide / dataLength);
|
|
402
|
+
const remainder = loopClonesPerSide % dataLength;
|
|
410
403
|
|
|
411
|
-
|
|
412
|
-
|
|
404
|
+
for (let i = 0; i < dataMultiplier; i++) {
|
|
405
|
+
previousItems.push(...data);
|
|
406
|
+
nextItems.push(...data);
|
|
407
|
+
}
|
|
408
|
+
|
|
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
|
+
}
|
|
415
|
+
|
|
416
|
+
return previousItems.concat(data, nextItems);
|
|
413
417
|
}
|
|
414
418
|
|
|
415
|
-
|
|
416
|
-
|
|
419
|
+
_getCustomDataLength(props = this.props) {
|
|
420
|
+
const { data, loopClonesPerSide } = props;
|
|
421
|
+
const dataLength = data && data.length;
|
|
417
422
|
|
|
418
|
-
|
|
419
|
-
|
|
423
|
+
if (!dataLength) {
|
|
424
|
+
return 0;
|
|
425
|
+
}
|
|
420
426
|
|
|
421
|
-
|
|
422
|
-
|
|
427
|
+
return this._enableLoop()
|
|
428
|
+
? dataLength + 2 * loopClonesPerSide
|
|
429
|
+
: dataLength;
|
|
423
430
|
}
|
|
424
431
|
|
|
425
|
-
|
|
426
|
-
|
|
432
|
+
_getCustomIndex(index, props = this.props) {
|
|
433
|
+
const itemsLength = this._getCustomDataLength(props);
|
|
427
434
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
435
|
+
if (!itemsLength || typeof index === 'undefined') {
|
|
436
|
+
return 0;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return this._needsRTLAdaptations() ? itemsLength - index - 1 : index;
|
|
433
440
|
}
|
|
434
441
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
// TODO: is there a simpler way of determining the interpolated index?
|
|
441
|
-
if (loopClonesPerSide > dataLength) {
|
|
442
|
-
const baseDataIndexes = [];
|
|
443
|
-
const dataIndexes = [];
|
|
444
|
-
const dataMultiplier = Math.floor(loopClonesPerSide / dataLength);
|
|
445
|
-
const remainder = loopClonesPerSide % dataLength;
|
|
446
|
-
|
|
447
|
-
for (let i = 0; i < dataLength; i++) {
|
|
448
|
-
baseDataIndexes.push(i);
|
|
442
|
+
_getDataIndex(index) {
|
|
443
|
+
const { data, loopClonesPerSide } = this.props;
|
|
444
|
+
const dataLength = data && data.length;
|
|
445
|
+
if (!this._enableLoop() || !dataLength) {
|
|
446
|
+
return index;
|
|
449
447
|
}
|
|
450
448
|
|
|
451
|
-
|
|
452
|
-
|
|
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;
|
|
453
478
|
}
|
|
479
|
+
}
|
|
454
480
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
return index +
|
|
459
|
-
}
|
|
460
|
-
} else {
|
|
461
|
-
return index - loopClonesPerSide;
|
|
481
|
+
// Used with `snapToItem()` and 'PaginationDot'
|
|
482
|
+
_getPositionIndex(index) {
|
|
483
|
+
const { loop, loopClonesPerSide } = this.props;
|
|
484
|
+
return loop ? index + loopClonesPerSide : index;
|
|
462
485
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
_getSnapOffsets(props = this.props) {
|
|
472
|
-
const offset = this._getItemMainDimension();
|
|
473
|
-
if (!props.enableSnap) return;
|
|
474
|
-
return [...Array(this._getCustomDataLength(props))].map((_, i) => {
|
|
475
|
-
return i * offset;
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
_getFirstItem(index, props = this.props) {
|
|
480
|
-
const {loopClonesPerSide} = props;
|
|
481
|
-
const itemsLength = this._getCustomDataLength(props);
|
|
482
|
-
|
|
483
|
-
if (!itemsLength || index > itemsLength - 1 || index < 0) {
|
|
484
|
-
return 0;
|
|
486
|
+
|
|
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
|
+
});
|
|
485
493
|
}
|
|
486
494
|
|
|
487
|
-
|
|
488
|
-
|
|
495
|
+
_getFirstItem(index, props = this.props) {
|
|
496
|
+
const { loopClonesPerSide } = props;
|
|
497
|
+
const itemsLength = this._getCustomDataLength(props);
|
|
489
498
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
(!this._needsScrollView() && this._carouselRef.scrollToOffset))
|
|
496
|
-
) {
|
|
497
|
-
return this._carouselRef;
|
|
498
|
-
}
|
|
499
|
-
// https://github.com/facebook/react-native/issues/10635
|
|
500
|
-
// https://stackoverflow.com/a/48786374/8412141
|
|
501
|
-
return (
|
|
502
|
-
this._carouselRef &&
|
|
503
|
-
// @ts-expect-error This is for before 0.62
|
|
504
|
-
this._carouselRef.getNode &&
|
|
505
|
-
// @ts-expect-error This is for before 0.62
|
|
506
|
-
this._carouselRef.getNode()
|
|
507
|
-
);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
_getScrollEnabled() {
|
|
511
|
-
return this._scrollEnabled;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
_setScrollEnabled(scrollEnabled = true) {
|
|
515
|
-
const wrappedRef = this._getWrappedRef();
|
|
516
|
-
|
|
517
|
-
if (!wrappedRef || !wrappedRef.setNativeProps) {
|
|
518
|
-
return;
|
|
499
|
+
if (!itemsLength || index > itemsLength - 1 || index < 0) {
|
|
500
|
+
return 0;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return this._enableLoop() ? index + loopClonesPerSide : index;
|
|
519
504
|
}
|
|
520
505
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
return {
|
|
540
|
-
index,
|
|
541
|
-
length: itemMainDimension,
|
|
542
|
-
offset: itemMainDimension * index, // + this._getContainerInnerMargin()
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// This will allow us to have a proper zIndex even with a FlatList
|
|
547
|
-
// https://github.com/facebook/react-native/issues/18616#issuecomment-389444165
|
|
548
|
-
_getCellRendererComponent({children, index, style, ...props}) {
|
|
549
|
-
const cellStyle = [
|
|
550
|
-
style,
|
|
551
|
-
!IS_ANDROID ? {zIndex: this._getCustomDataLength() - index} : {},
|
|
552
|
-
];
|
|
553
|
-
|
|
554
|
-
return (
|
|
555
|
-
<View style={cellStyle} key={index} {...props}>
|
|
556
|
-
{children}
|
|
557
|
-
</View>
|
|
558
|
-
);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
_getKeyExtractor(_, index) {
|
|
562
|
-
return this._needsScrollView()
|
|
563
|
-
? `scrollview-item-${index}`
|
|
564
|
-
: `flatlist-item-${index}`;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
_getScrollOffset(event) {
|
|
568
|
-
const {vertical} = this.props;
|
|
569
|
-
return (
|
|
570
|
-
(event &&
|
|
571
|
-
event.nativeEvent &&
|
|
572
|
-
event.nativeEvent.contentOffset &&
|
|
573
|
-
event.nativeEvent.contentOffset[vertical ? 'y' : 'x']) ||
|
|
574
|
-
0
|
|
575
|
-
);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
_getContainerInnerMargin(opposite = false) {
|
|
579
|
-
const {activeSlideAlignment} = this.props;
|
|
580
|
-
|
|
581
|
-
if (
|
|
582
|
-
(activeSlideAlignment === 'start' && !opposite) ||
|
|
583
|
-
(activeSlideAlignment === 'end' && opposite)
|
|
584
|
-
) {
|
|
585
|
-
return 0;
|
|
586
|
-
} else if (
|
|
587
|
-
(activeSlideAlignment === 'end' && !opposite) ||
|
|
588
|
-
(activeSlideAlignment === 'start' && opposite)
|
|
589
|
-
) {
|
|
590
|
-
return this.props.vertical
|
|
591
|
-
? this.props.sliderHeight - this.props.itemHeight
|
|
592
|
-
: this.props.sliderWidth - this.props.itemWidth;
|
|
593
|
-
} else {
|
|
594
|
-
return this.props.vertical
|
|
595
|
-
? (this.props.sliderHeight - this.props.itemHeight) / 2
|
|
596
|
-
: (this.props.sliderWidth - this.props.itemWidth) / 2;
|
|
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
|
+
);
|
|
597
524
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
const {activeSlideOffset} = this.props;
|
|
602
|
-
const itemMainDimension = this._getItemMainDimension();
|
|
603
|
-
const minOffset = 10;
|
|
604
|
-
// Make sure activeSlideOffset never prevents the active area from being at least 10 px wide
|
|
605
|
-
return itemMainDimension / 2 - activeSlideOffset >= minOffset
|
|
606
|
-
? activeSlideOffset
|
|
607
|
-
: minOffset;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
_getActiveItem(offset) {
|
|
611
|
-
const itemMainDimension = this._getItemMainDimension();
|
|
612
|
-
const center = offset + itemMainDimension / 2;
|
|
613
|
-
const activeSlideOffset = this._getActiveSlideOffset();
|
|
614
|
-
const lastIndex = this._positions.length - 1;
|
|
615
|
-
let itemIndex;
|
|
616
|
-
|
|
617
|
-
if (offset <= 0) {
|
|
618
|
-
return 0;
|
|
525
|
+
|
|
526
|
+
_getScrollEnabled() {
|
|
527
|
+
return this._scrollEnabled;
|
|
619
528
|
}
|
|
620
529
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
530
|
+
_setScrollEnabled(scrollEnabled = true) {
|
|
531
|
+
const wrappedRef = this._getWrappedRef();
|
|
532
|
+
|
|
533
|
+
if (!wrappedRef || !wrappedRef.setNativeProps) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
|
|
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;
|
|
626
541
|
}
|
|
627
542
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
center - activeSlideOffset <= end
|
|
633
|
-
) {
|
|
634
|
-
itemIndex = i;
|
|
635
|
-
break;
|
|
636
|
-
}
|
|
543
|
+
_getItemMainDimension() {
|
|
544
|
+
return this.props.vertical
|
|
545
|
+
? this.props.itemHeight
|
|
546
|
+
: this.props.itemWidth;
|
|
637
547
|
}
|
|
638
548
|
|
|
639
|
-
|
|
640
|
-
|
|
549
|
+
_getItemScrollOffset(index) {
|
|
550
|
+
return (
|
|
551
|
+
this._positions &&
|
|
552
|
+
this._positions[index] &&
|
|
553
|
+
this._positions[index].start
|
|
554
|
+
);
|
|
555
|
+
}
|
|
641
556
|
|
|
642
|
-
|
|
643
|
-
|
|
557
|
+
_getItemLayout(_, index) {
|
|
558
|
+
const itemMainDimension = this._getItemMainDimension();
|
|
559
|
+
return {
|
|
560
|
+
index,
|
|
561
|
+
length: itemMainDimension,
|
|
562
|
+
offset: itemMainDimension * index, // + this._getContainerInnerMargin()
|
|
563
|
+
};
|
|
564
|
+
}
|
|
644
565
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
this.props,
|
|
659
|
-
layoutCardOffset,
|
|
660
|
-
);
|
|
661
|
-
} else if (this._shouldUseShiftLayout()) {
|
|
662
|
-
return shiftAnimatedStyles(index, animatedValue, this.props);
|
|
663
|
-
} else {
|
|
664
|
-
return defaultAnimatedStyles(index, animatedValue, this.props);
|
|
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
|
+
);
|
|
665
579
|
}
|
|
666
|
-
}
|
|
667
580
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
581
|
+
_getKeyExtractor(_, index) {
|
|
582
|
+
return this._needsScrollView()
|
|
583
|
+
? `scrollview-item-${index}`
|
|
584
|
+
: `flatlist-item-${index}`;
|
|
585
|
+
}
|
|
671
586
|
|
|
672
|
-
|
|
673
|
-
|
|
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
|
+
);
|
|
674
596
|
}
|
|
675
597
|
|
|
676
|
-
|
|
677
|
-
|
|
598
|
+
_getContainerInnerMargin(opposite = false) {
|
|
599
|
+
const { activeSlideAlignment } = this.props;
|
|
678
600
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
601
|
+
if (
|
|
602
|
+
(activeSlideAlignment === 'start' && !opposite) ||
|
|
603
|
+
(activeSlideAlignment === 'end' && opposite)
|
|
604
|
+
) {
|
|
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;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
682
619
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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
|
+
}
|
|
687
629
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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;
|
|
692
636
|
|
|
693
|
-
if (
|
|
694
|
-
|
|
695
|
-
} else if (this._shouldUseStackLayout()) {
|
|
696
|
-
interpolator = stackScrollInterpolator(_index, props);
|
|
697
|
-
} else if (this._shouldUseTinderLayout()) {
|
|
698
|
-
interpolator = tinderScrollInterpolator(_index, props);
|
|
637
|
+
if (offset <= 0) {
|
|
638
|
+
return 0;
|
|
699
639
|
}
|
|
700
640
|
|
|
701
641
|
if (
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
!interpolator.outputRange
|
|
642
|
+
this._positions[lastIndex] &&
|
|
643
|
+
offset >= this._positions[lastIndex].start
|
|
705
644
|
) {
|
|
706
|
-
|
|
645
|
+
return lastIndex;
|
|
707
646
|
}
|
|
708
647
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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
|
+
}
|
|
717
658
|
|
|
718
|
-
|
|
719
|
-
|
|
659
|
+
return itemIndex || 0;
|
|
660
|
+
}
|
|
720
661
|
|
|
721
|
-
|
|
722
|
-
|
|
662
|
+
_getSlideInterpolatedStyle(index, animatedValue) {
|
|
663
|
+
const { layoutCardOffset, slideInterpolatedStyle } = this.props;
|
|
723
664
|
|
|
724
|
-
|
|
725
|
-
|
|
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
|
+
}
|
|
726
686
|
}
|
|
727
687
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
737
|
-
clearTimeout(this._hackSlideAnimationTimeout);
|
|
738
|
-
this._hackSlideAnimationTimeout = setTimeout(() => {
|
|
739
|
-
this._scrollTo({
|
|
740
|
-
offset,
|
|
741
|
-
animated: false,
|
|
742
|
-
});
|
|
743
|
-
}, 1); // works randomly when set to '0'
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
_repositionScroll(index, animated = false) {
|
|
747
|
-
const {data, loopClonesPerSide} = this.props;
|
|
748
|
-
const dataLength = data && data.length;
|
|
749
|
-
|
|
750
|
-
if (typeof index === 'undefined' || !this._shouldRepositionScroll(index)) {
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
688
|
+
_initPositionsAndInterpolators(props = this.props) {
|
|
689
|
+
const { data, scrollInterpolator } = props;
|
|
690
|
+
const itemMainDimension = this._getItemMainDimension();
|
|
691
|
+
|
|
692
|
+
if (!data || !data.length) {
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
753
695
|
|
|
754
|
-
|
|
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
|
+
});
|
|
755
737
|
|
|
756
|
-
|
|
757
|
-
repositionTo = index - dataLength;
|
|
758
|
-
} else if (index < loopClonesPerSide) {
|
|
759
|
-
repositionTo = index + dataLength;
|
|
738
|
+
this.setState({ interpolators });
|
|
760
739
|
}
|
|
761
740
|
|
|
762
|
-
|
|
763
|
-
|
|
741
|
+
_hackActiveSlideAnimation(index, scrollValue = 1) {
|
|
742
|
+
const offset = this._getItemScrollOffset(index);
|
|
764
743
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
744
|
+
if (
|
|
745
|
+
!this._mounted ||
|
|
746
|
+
!this._carouselRef ||
|
|
747
|
+
typeof offset === 'undefined'
|
|
748
|
+
) {
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const multiplier = this._currentScrollOffset === 0 ? 1 : -1;
|
|
753
|
+
const scrollDelta = scrollValue * multiplier;
|
|
754
|
+
|
|
755
|
+
this._scrollTo({ offset: offset + scrollDelta, animated: false });
|
|
756
|
+
|
|
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'
|
|
774
762
|
}
|
|
775
763
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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;
|
|
776
|
+
|
|
777
|
+
if (index >= dataLength + loopClonesPerSide) {
|
|
778
|
+
repositionTo = index - dataLength;
|
|
779
|
+
} else if (index < loopClonesPerSide) {
|
|
780
|
+
repositionTo = index + dataLength;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
this._snapToItem(repositionTo, animated, false);
|
|
781
784
|
}
|
|
782
785
|
|
|
783
|
-
|
|
784
|
-
|
|
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
|
+
}
|
|
796
|
+
|
|
797
|
+
let scrollToOffset;
|
|
798
|
+
if (typeof index !== 'undefined') {
|
|
799
|
+
scrollToOffset = this._getItemScrollOffset(index);
|
|
800
|
+
} else {
|
|
801
|
+
scrollToOffset = offset;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
if (typeof scrollToOffset === 'undefined') {
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
|
|
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
|
+
}
|
|
785
824
|
}
|
|
786
825
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
826
|
+
_onTouchStart(event) {
|
|
827
|
+
const { onTouchStart } = this.props;
|
|
828
|
+
|
|
829
|
+
// `onTouchStart` is fired even when `scrollEnabled` is set to `false`
|
|
830
|
+
if (this._getScrollEnabled() !== false && this._autoplaying) {
|
|
831
|
+
this.pauseAutoPlay();
|
|
792
832
|
}
|
|
793
|
-
: {
|
|
794
|
-
offset,
|
|
795
|
-
animated,
|
|
796
|
-
};
|
|
797
833
|
|
|
798
|
-
|
|
799
|
-
wrappedRef.scrollTo(options);
|
|
800
|
-
} else {
|
|
801
|
-
wrappedRef.scrollToOffset(options);
|
|
834
|
+
onTouchStart && onTouchStart(event);
|
|
802
835
|
}
|
|
803
|
-
}
|
|
804
836
|
|
|
805
|
-
|
|
806
|
-
|
|
837
|
+
_onTouchEnd(event) {
|
|
838
|
+
const { onTouchEnd } = this.props;
|
|
807
839
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
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);
|
|
811
850
|
}
|
|
812
851
|
|
|
813
|
-
|
|
814
|
-
|
|
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
|
+
}
|
|
815
868
|
|
|
816
|
-
|
|
817
|
-
|
|
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
|
+
}
|
|
818
877
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
!this._autoplaying
|
|
823
|
-
) {
|
|
824
|
-
// This event is buggy on Android, so a fallback is provided in _onMomentumScrollEnd()
|
|
825
|
-
this.startAutoplay();
|
|
878
|
+
if (typeof onScroll === 'function' && event) {
|
|
879
|
+
onScroll(event);
|
|
880
|
+
}
|
|
826
881
|
}
|
|
827
882
|
|
|
828
|
-
|
|
829
|
-
|
|
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
|
+
);
|
|
830
893
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
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
|
+
}
|
|
839
907
|
|
|
840
|
-
|
|
908
|
+
onMomentumScrollEnd && onMomentumScrollEnd(event);
|
|
841
909
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
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);
|
|
928
|
+
} else {
|
|
929
|
+
this._onLayoutInitDone = true;
|
|
930
|
+
}
|
|
846
931
|
|
|
847
|
-
|
|
932
|
+
onLayout && onLayout(event);
|
|
848
933
|
}
|
|
849
934
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
935
|
+
_snapToItem(
|
|
936
|
+
index,
|
|
937
|
+
animated = true,
|
|
938
|
+
fireCallback = true,
|
|
939
|
+
forceScrollTo = false,
|
|
855
940
|
) {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
941
|
+
const { onSnapToItem } = this.props;
|
|
942
|
+
const itemsLength = this._getCustomDataLength();
|
|
943
|
+
const wrappedRef = this._getWrappedRef();
|
|
944
|
+
if (!itemsLength || !wrappedRef) {
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
859
947
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
_onMomentumScrollEnd(event) {
|
|
866
|
-
const {autoplayDelay, onMomentumScrollEnd, onSnapToItem} = this.props;
|
|
867
|
-
const scrollOffset = event
|
|
868
|
-
? this._getScrollOffset(event)
|
|
869
|
-
: this._currentScrollOffset;
|
|
870
|
-
const nextActiveItem = this._getActiveItem(scrollOffset);
|
|
871
|
-
const hasSnapped = this._isMultiple(
|
|
872
|
-
scrollOffset,
|
|
873
|
-
this.props.vertical ? this.props.itemHeight : this.props.itemWidth,
|
|
874
|
-
);
|
|
875
|
-
|
|
876
|
-
// WARNING: everything in this condition will probably need to be called on _snapToItem as well because:
|
|
877
|
-
// 1. `onMomentumScrollEnd` won't be called if the scroll isn't animated
|
|
878
|
-
// 2. `onMomentumScrollEnd` won't be called at all on Android when scrolling programmatically
|
|
879
|
-
if (nextActiveItem !== this._activeItem) {
|
|
880
|
-
this._activeItem = nextActiveItem;
|
|
881
|
-
onSnapToItem && onSnapToItem(this._getDataIndex(nextActiveItem));
|
|
882
|
-
|
|
883
|
-
if (hasSnapped && IS_ANDROID) {
|
|
884
|
-
this._repositionScroll(nextActiveItem);
|
|
885
|
-
} else if (IS_IOS) {
|
|
886
|
-
this._repositionScroll(nextActiveItem);
|
|
887
|
-
}
|
|
888
|
-
}
|
|
948
|
+
if (!index || index < 0) {
|
|
949
|
+
index = 0;
|
|
950
|
+
} else if (itemsLength > 0 && index >= itemsLength) {
|
|
951
|
+
index = itemsLength - 1;
|
|
952
|
+
}
|
|
889
953
|
|
|
890
|
-
|
|
954
|
+
if (index === this._activeItem && !forceScrollTo) {
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
891
957
|
|
|
892
|
-
|
|
893
|
-
// https://github.com/facebook/react-native/issues/9439
|
|
894
|
-
if (IS_ANDROID && this._autoplay && !this._autoplaying) {
|
|
895
|
-
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
896
|
-
clearTimeout(this._enableAutoplayTimeout);
|
|
897
|
-
this._enableAutoplayTimeout = setTimeout(() => {
|
|
898
|
-
this.startAutoplay();
|
|
899
|
-
}, autoplayDelay);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
958
|
+
const offset = this._getItemScrollOffset(index);
|
|
902
959
|
|
|
903
|
-
|
|
904
|
-
|
|
960
|
+
if (offset === undefined) {
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
905
963
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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
|
+
}
|
|
912
990
|
}
|
|
913
991
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
992
|
+
startAutoplay() {
|
|
993
|
+
const { autoplayInterval, autoplayDelay } = this.props;
|
|
994
|
+
this._autoplay = true;
|
|
995
|
+
|
|
996
|
+
if (this._autoplaying) {
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
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);
|
|
928
1010
|
}
|
|
929
1011
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
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);
|
|
934
1020
|
}
|
|
935
1021
|
|
|
936
|
-
|
|
937
|
-
|
|
1022
|
+
stopAutoplay() {
|
|
1023
|
+
this._autoplay = false;
|
|
1024
|
+
this.pauseAutoPlay();
|
|
938
1025
|
}
|
|
939
1026
|
|
|
940
|
-
|
|
1027
|
+
snapToItem(index, animated = true, fireCallback = true) {
|
|
1028
|
+
if (!index || index < 0) {
|
|
1029
|
+
index = 0;
|
|
1030
|
+
}
|
|
941
1031
|
|
|
942
|
-
|
|
943
|
-
return;
|
|
944
|
-
}
|
|
1032
|
+
const positionIndex = this._getPositionIndex(index);
|
|
945
1033
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
animated,
|
|
949
|
-
});
|
|
950
|
-
|
|
951
|
-
// On both platforms, `onMomentumScrollEnd` won't be triggered if the scroll isn't animated
|
|
952
|
-
// so we need to trigger the callback manually
|
|
953
|
-
// On Android `onMomentumScrollEnd` won't be triggered when scrolling programmatically
|
|
954
|
-
// Therefore everything critical needs to be manually called here as well, even though the timing might be off
|
|
955
|
-
const requiresManualTrigger = !animated || IS_ANDROID;
|
|
956
|
-
if (requiresManualTrigger) {
|
|
957
|
-
this._activeItem = index;
|
|
958
|
-
|
|
959
|
-
if (fireCallback) {
|
|
960
|
-
onSnapToItem && onSnapToItem(this._getDataIndex(index));
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
// Repositioning on Android
|
|
964
|
-
if (IS_ANDROID && this._shouldRepositionScroll(index)) {
|
|
965
|
-
if (animated) {
|
|
966
|
-
this._androidRepositioningTimeout = setTimeout(() => {
|
|
967
|
-
// Without scroll animation, the behavior is completely buggy...
|
|
968
|
-
this._repositionScroll(index, false);
|
|
969
|
-
}, 400); // Approximate scroll duration on Android
|
|
970
|
-
} else {
|
|
971
|
-
this._repositionScroll(index);
|
|
1034
|
+
if (positionIndex === this._activeItem) {
|
|
1035
|
+
return;
|
|
972
1036
|
}
|
|
973
|
-
|
|
1037
|
+
|
|
1038
|
+
this._snapToItem(positionIndex, animated, fireCallback);
|
|
974
1039
|
}
|
|
975
|
-
}
|
|
976
1040
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
this._autoplay = true;
|
|
1041
|
+
snapToNext(animated = true, fireCallback = true) {
|
|
1042
|
+
const itemsLength = this._getCustomDataLength();
|
|
980
1043
|
|
|
981
|
-
|
|
982
|
-
|
|
1044
|
+
let newIndex = this._activeItem + 1;
|
|
1045
|
+
if (newIndex > itemsLength - 1) {
|
|
1046
|
+
newIndex = 0;
|
|
1047
|
+
}
|
|
1048
|
+
this._snapToItem(newIndex, animated, fireCallback);
|
|
983
1049
|
}
|
|
984
1050
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
this.snapToNext();
|
|
1051
|
+
snapToPrev(animated = true, fireCallback = true) {
|
|
1052
|
+
const itemsLength = this._getCustomDataLength();
|
|
1053
|
+
|
|
1054
|
+
let newIndex = this._activeItem - 1;
|
|
1055
|
+
if (newIndex < 0) {
|
|
1056
|
+
newIndex = itemsLength - 1;
|
|
992
1057
|
}
|
|
993
|
-
|
|
994
|
-
}, autoplayDelay);
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
pauseAutoPlay() {
|
|
998
|
-
this._autoplaying = false;
|
|
999
|
-
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
1000
|
-
clearTimeout(this._autoplayTimeout);
|
|
1001
|
-
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
1002
|
-
clearTimeout(this._enableAutoplayTimeout);
|
|
1003
|
-
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
1004
|
-
clearInterval(this._autoplayInterval);
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
stopAutoplay() {
|
|
1008
|
-
this._autoplay = false;
|
|
1009
|
-
this.pauseAutoPlay();
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
snapToNext(animated = true, fireCallback = true) {
|
|
1013
|
-
const itemsLength = this._getCustomDataLength();
|
|
1014
|
-
|
|
1015
|
-
let newIndex = this._activeItem + 1;
|
|
1016
|
-
if (newIndex > itemsLength - 1) {
|
|
1017
|
-
newIndex = 0;
|
|
1058
|
+
this._snapToItem(newIndex, animated, fireCallback);
|
|
1018
1059
|
}
|
|
1019
|
-
this._snapToItem(newIndex, animated, fireCallback);
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
snapToPrev(animated = true, fireCallback = true) {
|
|
1023
|
-
const itemsLength = this._getCustomDataLength();
|
|
1024
1060
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1061
|
+
// https://github.com/facebook/react-native/issues/1831#issuecomment-231069668
|
|
1062
|
+
triggerRenderingHack(offset = 1) {
|
|
1063
|
+
this._hackActiveSlideAnimation(this._activeItem, offset);
|
|
1028
1064
|
}
|
|
1029
|
-
this._snapToItem(newIndex, animated, fireCallback);
|
|
1030
|
-
}
|
|
1031
1065
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1066
|
+
_renderItem({ item, index }) {
|
|
1067
|
+
const { interpolators } = this.state;
|
|
1068
|
+
const { keyExtractor, slideStyle } = this.props;
|
|
1069
|
+
const animatedValue = interpolators && interpolators[index];
|
|
1036
1070
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
const animatedValue = interpolators && interpolators[index];
|
|
1071
|
+
if (typeof animatedValue === 'undefined') {
|
|
1072
|
+
return null;
|
|
1073
|
+
}
|
|
1041
1074
|
|
|
1042
|
-
|
|
1043
|
-
|
|
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
|
+
);
|
|
1044
1133
|
}
|
|
1045
1134
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
: this.props.renderItem(
|
|
1087
|
-
{
|
|
1088
|
-
item,
|
|
1089
|
-
index,
|
|
1090
|
-
dataIndex,
|
|
1091
|
-
realIndex: this._getDataIndex(index),
|
|
1092
|
-
activeIndex: this._getDataIndex(this._activeItem),
|
|
1093
|
-
},
|
|
1094
|
-
{
|
|
1095
|
-
scrollPosition: this._scrollPos,
|
|
1096
|
-
carouselRef: this._carouselRef,
|
|
1097
|
-
vertical: !!this.props.vertical,
|
|
1098
|
-
sliderWidth: this.props.sliderWidth,
|
|
1099
|
-
itemWidth: this.props.itemWidth,
|
|
1100
|
-
},
|
|
1101
|
-
)}
|
|
1102
|
-
</Component>
|
|
1103
|
-
);
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
_getComponentOverridableProps() {
|
|
1107
|
-
const {hideCarousel} = this.state;
|
|
1108
|
-
const {loopClonesPerSide} = this.props;
|
|
1109
|
-
const visibleItems =
|
|
1110
|
-
Math.ceil(
|
|
1111
|
-
this.props.vertical
|
|
1112
|
-
? this.props.sliderHeight / this.props.itemHeight
|
|
1113
|
-
: this.props.sliderWidth / this.props.itemWidth,
|
|
1114
|
-
) + 1;
|
|
1115
|
-
const initialNumPerSide = this._enableLoop() ? loopClonesPerSide : 2;
|
|
1116
|
-
const initialNumToRender =
|
|
1117
|
-
visibleItems > 2
|
|
1118
|
-
? visibleItems + initialNumPerSide * 2
|
|
1119
|
-
: initialNumPerSide * 2;
|
|
1120
|
-
const maxToRenderPerBatch = initialNumToRender;
|
|
1121
|
-
const windowSize = maxToRenderPerBatch;
|
|
1122
|
-
const specificProps = !this._needsScrollView()
|
|
1123
|
-
? {
|
|
1124
|
-
initialNumToRender,
|
|
1125
|
-
maxToRenderPerBatch,
|
|
1126
|
-
windowSize,
|
|
1127
|
-
// updateCellsBatchingPeriod
|
|
1128
|
-
}
|
|
1129
|
-
: {};
|
|
1130
|
-
|
|
1131
|
-
return {
|
|
1132
|
-
...specificProps,
|
|
1133
|
-
automaticallyAdjustContentInsets: false,
|
|
1134
|
-
decelerationRate: 'fast',
|
|
1135
|
-
directionalLockEnabled: true,
|
|
1136
|
-
disableScrollViewPanResponder: false, // If set to `true`, touch events will be triggered too easily
|
|
1137
|
-
inverted: this._needsRTLAdaptations(),
|
|
1138
|
-
overScrollMode: 'never',
|
|
1139
|
-
pinchGestureEnabled: false,
|
|
1140
|
-
pointerEvents: hideCarousel ? 'none' : 'auto',
|
|
1141
|
-
// removeClippedSubviews: !this._needsScrollView(),
|
|
1142
|
-
// renderToHardwareTextureAndroid: true,
|
|
1143
|
-
scrollsToTop: false,
|
|
1144
|
-
showsHorizontalScrollIndicator: false,
|
|
1145
|
-
showsVerticalScrollIndicator: false,
|
|
1146
|
-
};
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
_getComponentStaticProps() {
|
|
1150
|
-
const {hideCarousel} = this.state;
|
|
1151
|
-
const {
|
|
1152
|
-
activeSlideAlignment,
|
|
1153
|
-
CellRendererComponent,
|
|
1154
|
-
containerCustomStyle,
|
|
1155
|
-
contentContainerCustomStyle,
|
|
1156
|
-
firstItem,
|
|
1157
|
-
getItemLayout,
|
|
1158
|
-
keyExtractor,
|
|
1159
|
-
style,
|
|
1160
|
-
useExperimentalSnap,
|
|
1161
|
-
disableIntervalMomentum,
|
|
1162
|
-
vertical,
|
|
1163
|
-
enableSnap,
|
|
1164
|
-
} = this.props;
|
|
1165
|
-
const containerStyle = [
|
|
1166
|
-
// { overflow: 'hidden' },
|
|
1167
|
-
containerCustomStyle || style || {},
|
|
1168
|
-
hideCarousel ? {opacity: 0} : {},
|
|
1169
|
-
this.props.vertical
|
|
1170
|
-
? {
|
|
1171
|
-
height: this.props.sliderHeight,
|
|
1172
|
-
flexDirection: 'column',
|
|
1173
|
-
} // LTR hack; see https://github.com/facebook/react-native/issues/11960
|
|
1174
|
-
: // and https://github.com/facebook/react-native/issues/13100#issuecomment-328986423
|
|
1175
|
-
{
|
|
1176
|
-
width: this.props.sliderWidth,
|
|
1177
|
-
flexDirection: this._needsRTLAdaptations() ? 'row-reverse' : 'row',
|
|
1178
|
-
},
|
|
1179
|
-
];
|
|
1180
|
-
|
|
1181
|
-
const innerMarginStyle = this.props.vertical
|
|
1182
|
-
? {
|
|
1183
|
-
paddingTop: this._getContainerInnerMargin(),
|
|
1184
|
-
paddingBottom: this._getContainerInnerMargin(true),
|
|
1185
|
-
}
|
|
1186
|
-
: {
|
|
1187
|
-
paddingLeft: this._getContainerInnerMargin(),
|
|
1188
|
-
paddingRight: this._getContainerInnerMargin(true),
|
|
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,
|
|
1189
1175
|
};
|
|
1176
|
+
}
|
|
1190
1177
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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
|
-
|
|
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
|
+
};
|
|
1259
1280
|
}
|
|
1260
1281
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
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
|
+
}
|
|
1286
1316
|
}
|
|
1287
1317
|
|
|
1288
1318
|
Carousel.propTypes = {
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
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
|
-
|
|
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,
|
|
1334
1364
|
};
|
|
1335
1365
|
|
|
1336
1366
|
Carousel.defaultProps = {
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
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,
|
|
1363
1393
|
};
|