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