@momo-kits/carousel 0.0.65-alpha.11 → 0.0.65-alpha.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CarouselV2.js +1221 -1196
- package/index.js +4 -2
- package/package.json +15 -16
- package/pagination/Pagination.js +34 -64
- package/pagination/PaginationDot.js +39 -45
- package/pagination/styles.js +6 -6
- package/utils/animationsV2.js +207 -230
- package/pagination/NumberPagination.js +0 -43
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,1362 @@ 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
|
-
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});
|
|
108
|
-
}
|
|
109
|
-
if (autoplay) {
|
|
110
|
-
this.startAutoplay();
|
|
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;
|
|
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);
|
|
138
69
|
}
|
|
139
70
|
|
|
140
|
-
|
|
141
|
-
|
|
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;
|
|
71
|
+
get realIndex() {
|
|
72
|
+
return this._activeItem;
|
|
160
73
|
}
|
|
161
74
|
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
75
|
+
// shouldComponentUpdate (
|
|
76
|
+
// nextProps,
|
|
77
|
+
// nextState
|
|
78
|
+
// ) {
|
|
79
|
+
// if (this.props.shouldOptimizeUpdates === false) {
|
|
80
|
+
// return true;
|
|
81
|
+
// } else {
|
|
82
|
+
// return shallowCompare(this, nextProps, nextState);
|
|
83
|
+
// }
|
|
84
|
+
// }
|
|
85
|
+
|
|
86
|
+
get currentIndex() {
|
|
87
|
+
return this._getDataIndex(this._activeItem);
|
|
165
88
|
}
|
|
166
89
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
90
|
+
get currentScrollPosition() {
|
|
91
|
+
return this._currentScrollOffset;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
componentDidMount() {
|
|
95
|
+
const { apparitionDelay, autoplay, firstItem } = this.props;
|
|
96
|
+
|
|
97
|
+
this._mounted = true;
|
|
98
|
+
this._initPositionsAndInterpolators();
|
|
99
|
+
|
|
100
|
+
// Without 'requestAnimationFrame' or a `0` timeout, images will randomly not be rendered on Android...
|
|
101
|
+
this._initTimeout = setTimeout(() => {
|
|
102
|
+
if (!this._mounted) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const apparitionCallback = () => {
|
|
107
|
+
if (apparitionDelay) {
|
|
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);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (interpolators.length !== itemsLength || hasNewSize) {
|
|
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);
|
|
195
|
+
}
|
|
190
196
|
}
|
|
191
197
|
|
|
192
|
-
|
|
193
|
-
|
|
198
|
+
componentWillUnmount() {
|
|
199
|
+
this._mounted = false;
|
|
200
|
+
this.stopAutoplay();
|
|
201
|
+
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
202
|
+
clearTimeout(this._initTimeout);
|
|
203
|
+
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
204
|
+
clearTimeout(this._apparitionTimeout);
|
|
205
|
+
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
206
|
+
clearTimeout(this._hackSlideAnimationTimeout);
|
|
207
|
+
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
208
|
+
clearTimeout(this._enableAutoplayTimeout);
|
|
209
|
+
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
210
|
+
clearTimeout(this._autoplayTimeout);
|
|
211
|
+
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
212
|
+
clearTimeout(this._snapNoMomentumTimeout);
|
|
213
|
+
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
214
|
+
clearTimeout(this._androidRepositioningTimeout);
|
|
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;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
_needsRTLAdaptations() {
|
|
318
|
+
const { vertical } = this.props;
|
|
319
|
+
return IS_RTL && IS_ANDROID && !vertical;
|
|
289
320
|
}
|
|
290
321
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
322
|
+
_enableLoop() {
|
|
323
|
+
const { data, enableSnap, loop } = this.props;
|
|
324
|
+
return enableSnap && loop && data && data.length && data.length > 1;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
_shouldAnimateSlides(props = this.props) {
|
|
328
|
+
const {
|
|
329
|
+
inactiveSlideOpacity,
|
|
330
|
+
inactiveSlideScale,
|
|
331
|
+
scrollInterpolator,
|
|
332
|
+
slideInterpolatedStyle,
|
|
333
|
+
} = props;
|
|
334
|
+
return (
|
|
335
|
+
inactiveSlideOpacity < 1 ||
|
|
336
|
+
inactiveSlideScale < 1 ||
|
|
337
|
+
!!scrollInterpolator ||
|
|
338
|
+
!!slideInterpolatedStyle ||
|
|
339
|
+
this._shouldUseShiftLayout() ||
|
|
340
|
+
this._shouldUseStackLayout() ||
|
|
341
|
+
this._shouldUseTinderLayout()
|
|
295
342
|
);
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
_needsScrollView() {
|
|
301
|
-
const {useScrollView} = this.props;
|
|
302
|
-
// Android's cell renderer is buggy and has a stange overflow
|
|
303
|
-
// TODO: a workaround might be to pass the custom animated styles directly to it
|
|
304
|
-
return IS_ANDROID
|
|
305
|
-
? useScrollView ||
|
|
306
|
-
!Animated.FlatList ||
|
|
307
|
-
this._shouldUseStackLayout() ||
|
|
308
|
-
this._shouldUseTinderLayout()
|
|
309
|
-
: useScrollView || !Animated.FlatList;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
_needsRTLAdaptations() {
|
|
313
|
-
const {vertical} = this.props;
|
|
314
|
-
return IS_RTL && IS_ANDROID && !vertical;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
_enableLoop() {
|
|
318
|
-
const {data, enableSnap, loop} = this.props;
|
|
319
|
-
return enableSnap && loop && data && data.length && data.length > 1;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
_shouldAnimateSlides(props = this.props) {
|
|
323
|
-
const {
|
|
324
|
-
inactiveSlideOpacity,
|
|
325
|
-
inactiveSlideScale,
|
|
326
|
-
scrollInterpolator,
|
|
327
|
-
slideInterpolatedStyle,
|
|
328
|
-
} = props;
|
|
329
|
-
return (
|
|
330
|
-
inactiveSlideOpacity < 1 ||
|
|
331
|
-
inactiveSlideScale < 1 ||
|
|
332
|
-
!!scrollInterpolator ||
|
|
333
|
-
!!slideInterpolatedStyle ||
|
|
334
|
-
this._shouldUseShiftLayout() ||
|
|
335
|
-
this._shouldUseStackLayout() ||
|
|
336
|
-
this._shouldUseTinderLayout()
|
|
337
|
-
);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
_shouldUseShiftLayout() {
|
|
341
|
-
const {inactiveSlideShift, layout} = this.props;
|
|
342
|
-
return layout === 'default' && inactiveSlideShift !== 0;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
_shouldUseStackLayout() {
|
|
346
|
-
return this.props.layout === 'stack';
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
_shouldUseTinderLayout() {
|
|
350
|
-
return this.props.layout === 'tinder';
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
_shouldRepositionScroll(index) {
|
|
354
|
-
const {data, enableSnap, loopClonesPerSide} = this.props;
|
|
355
|
-
const dataLength = data && data.length;
|
|
356
|
-
if (
|
|
357
|
-
!enableSnap ||
|
|
358
|
-
!dataLength ||
|
|
359
|
-
!this._enableLoop() ||
|
|
360
|
-
(index >= loopClonesPerSide && index < dataLength + loopClonesPerSide)
|
|
361
|
-
) {
|
|
362
|
-
return false;
|
|
363
343
|
}
|
|
364
|
-
return true;
|
|
365
|
-
}
|
|
366
344
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
345
|
+
_shouldUseShiftLayout() {
|
|
346
|
+
const { inactiveSlideShift, layout } = this.props;
|
|
347
|
+
return layout === 'default' && inactiveSlideShift !== 0;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
_shouldUseStackLayout() {
|
|
351
|
+
return this.props.layout === 'stack';
|
|
352
|
+
}
|
|
372
353
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
354
|
+
_shouldUseTinderLayout() {
|
|
355
|
+
return this.props.layout === 'tinder';
|
|
356
|
+
}
|
|
376
357
|
|
|
377
|
-
|
|
378
|
-
|
|
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;
|
|
379
371
|
}
|
|
380
372
|
|
|
381
|
-
|
|
382
|
-
|
|
373
|
+
_isMultiple(x, y) {
|
|
374
|
+
// This prevents Javascript precision issues: https://stackoverflow.com/a/58440614/
|
|
375
|
+
// Required because Android viewport size can return pretty complicated decimals numbers
|
|
376
|
+
return Math.round(Math.round(x / y) / (1 / y)) === Math.round(x);
|
|
383
377
|
}
|
|
384
378
|
|
|
385
|
-
|
|
386
|
-
|
|
379
|
+
_getCustomData(props = this.props) {
|
|
380
|
+
const { data, loopClonesPerSide } = props;
|
|
381
|
+
const dataLength = data && data.length;
|
|
382
|
+
|
|
383
|
+
if (!dataLength) {
|
|
384
|
+
return [];
|
|
385
|
+
}
|
|
387
386
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
387
|
+
if (!this._enableLoop()) {
|
|
388
|
+
return data;
|
|
389
|
+
}
|
|
391
390
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
nextItems.push(...data);
|
|
395
|
-
}
|
|
391
|
+
let previousItems = [];
|
|
392
|
+
let nextItems = [];
|
|
396
393
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
previousItems = data.slice(-loopClonesPerSide);
|
|
401
|
-
nextItems = data.slice(0, loopClonesPerSide);
|
|
402
|
-
}
|
|
394
|
+
if (loopClonesPerSide > dataLength) {
|
|
395
|
+
const dataMultiplier = Math.floor(loopClonesPerSide / dataLength);
|
|
396
|
+
const remainder = loopClonesPerSide % dataLength;
|
|
403
397
|
|
|
404
|
-
|
|
405
|
-
|
|
398
|
+
for (let i = 0; i < dataMultiplier; i++) {
|
|
399
|
+
previousItems.push(...data);
|
|
400
|
+
nextItems.push(...data);
|
|
401
|
+
}
|
|
406
402
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
403
|
+
previousItems.unshift(...data.slice(-remainder));
|
|
404
|
+
nextItems.push(...data.slice(0, remainder));
|
|
405
|
+
} else {
|
|
406
|
+
previousItems = data.slice(-loopClonesPerSide);
|
|
407
|
+
nextItems = data.slice(0, loopClonesPerSide);
|
|
408
|
+
}
|
|
410
409
|
|
|
411
|
-
|
|
412
|
-
return 0;
|
|
410
|
+
return previousItems.concat(data, nextItems);
|
|
413
411
|
}
|
|
414
412
|
|
|
415
|
-
|
|
416
|
-
|
|
413
|
+
_getCustomDataLength(props = this.props) {
|
|
414
|
+
const { data, loopClonesPerSide } = props;
|
|
415
|
+
const dataLength = data && data.length;
|
|
417
416
|
|
|
418
|
-
|
|
419
|
-
|
|
417
|
+
if (!dataLength) {
|
|
418
|
+
return 0;
|
|
419
|
+
}
|
|
420
420
|
|
|
421
|
-
|
|
422
|
-
|
|
421
|
+
return this._enableLoop()
|
|
422
|
+
? dataLength + 2 * loopClonesPerSide
|
|
423
|
+
: dataLength;
|
|
423
424
|
}
|
|
424
425
|
|
|
425
|
-
|
|
426
|
-
|
|
426
|
+
_getCustomIndex(index, props = this.props) {
|
|
427
|
+
const itemsLength = this._getCustomDataLength(props);
|
|
428
|
+
|
|
429
|
+
if (!itemsLength || typeof index === 'undefined') {
|
|
430
|
+
return 0;
|
|
431
|
+
}
|
|
427
432
|
|
|
428
|
-
|
|
429
|
-
const {data, loopClonesPerSide} = this.props;
|
|
430
|
-
const dataLength = data && data.length;
|
|
431
|
-
if (!this._enableLoop() || !dataLength) {
|
|
432
|
-
return index;
|
|
433
|
+
return this._needsRTLAdaptations() ? itemsLength - index - 1 : index;
|
|
433
434
|
}
|
|
434
435
|
|
|
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);
|
|
436
|
+
_getDataIndex(index) {
|
|
437
|
+
const { data, loopClonesPerSide } = this.props;
|
|
438
|
+
const dataLength = data && data.length;
|
|
439
|
+
if (!this._enableLoop() || !dataLength) {
|
|
440
|
+
return index;
|
|
449
441
|
}
|
|
450
442
|
|
|
451
|
-
|
|
452
|
-
|
|
443
|
+
if (index >= dataLength + loopClonesPerSide) {
|
|
444
|
+
return loopClonesPerSide > dataLength
|
|
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;
|
|
453
472
|
}
|
|
473
|
+
}
|
|
454
474
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
return index +
|
|
459
|
-
}
|
|
460
|
-
} else {
|
|
461
|
-
return index - loopClonesPerSide;
|
|
475
|
+
// Used with `snapToItem()` and 'PaginationDot'
|
|
476
|
+
_getPositionIndex(index) {
|
|
477
|
+
const { loop, loopClonesPerSide } = this.props;
|
|
478
|
+
return loop ? index + loopClonesPerSide : index;
|
|
462
479
|
}
|
|
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;
|
|
480
|
+
|
|
481
|
+
_getSnapOffsets(props = this.props) {
|
|
482
|
+
const offset = this._getItemMainDimension();
|
|
483
|
+
if (!props.enableSnap) return;
|
|
484
|
+
return [...Array(this._getCustomDataLength(props))].map((_, i) => {
|
|
485
|
+
return i * offset;
|
|
486
|
+
});
|
|
485
487
|
}
|
|
486
488
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
+
_getFirstItem(index, props = this.props) {
|
|
490
|
+
const { loopClonesPerSide } = props;
|
|
491
|
+
const itemsLength = this._getCustomDataLength(props);
|
|
489
492
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
(!this._needsScrollView() && this._carouselRef.scrollToOffset))
|
|
496
|
-
) {
|
|
497
|
-
return this._carouselRef;
|
|
493
|
+
if (!itemsLength || index > itemsLength - 1 || index < 0) {
|
|
494
|
+
return 0;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return this._enableLoop() ? index + loopClonesPerSide : index;
|
|
498
498
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
return;
|
|
499
|
+
|
|
500
|
+
_getWrappedRef() {
|
|
501
|
+
// Starting with RN 0.62, we should no longer call `getNode()` on the ref of an Animated component
|
|
502
|
+
if (
|
|
503
|
+
this._carouselRef &&
|
|
504
|
+
((this._needsScrollView() && this._carouselRef.scrollTo) ||
|
|
505
|
+
(!this._needsScrollView() && this._carouselRef.scrollToOffset))
|
|
506
|
+
) {
|
|
507
|
+
return this._carouselRef;
|
|
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
|
+
);
|
|
519
518
|
}
|
|
520
519
|
|
|
521
|
-
|
|
522
|
-
|
|
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;
|
|
520
|
+
_getScrollEnabled() {
|
|
521
|
+
return this._scrollEnabled;
|
|
597
522
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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;
|
|
523
|
+
|
|
524
|
+
_setScrollEnabled(scrollEnabled = true) {
|
|
525
|
+
const wrappedRef = this._getWrappedRef();
|
|
526
|
+
|
|
527
|
+
if (!wrappedRef || !wrappedRef.setNativeProps) {
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// 'setNativeProps()' is used instead of 'setState()' because the latter
|
|
532
|
+
// really takes a toll on Android behavior when momentum is disabled
|
|
533
|
+
wrappedRef.setNativeProps({ scrollEnabled });
|
|
534
|
+
this._scrollEnabled = scrollEnabled;
|
|
619
535
|
}
|
|
620
536
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
537
|
+
_getItemMainDimension() {
|
|
538
|
+
return this.props.vertical
|
|
539
|
+
? this.props.itemHeight
|
|
540
|
+
: this.props.itemWidth;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
_getItemScrollOffset(index) {
|
|
544
|
+
return (
|
|
545
|
+
this._positions &&
|
|
546
|
+
this._positions[index] &&
|
|
547
|
+
this._positions[index].start
|
|
548
|
+
);
|
|
626
549
|
}
|
|
627
550
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
break;
|
|
636
|
-
}
|
|
551
|
+
_getItemLayout(_, index) {
|
|
552
|
+
const itemMainDimension = this._getItemMainDimension();
|
|
553
|
+
return {
|
|
554
|
+
index,
|
|
555
|
+
length: itemMainDimension,
|
|
556
|
+
offset: itemMainDimension * index, // + this._getContainerInnerMargin()
|
|
557
|
+
};
|
|
637
558
|
}
|
|
638
559
|
|
|
639
|
-
|
|
640
|
-
|
|
560
|
+
// This will allow us to have a proper zIndex even with a FlatList
|
|
561
|
+
// https://github.com/facebook/react-native/issues/18616#issuecomment-389444165
|
|
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
|
+
}
|
|
641
574
|
|
|
642
|
-
|
|
643
|
-
|
|
575
|
+
_getKeyExtractor(_, index) {
|
|
576
|
+
return this._needsScrollView()
|
|
577
|
+
? `scrollview-item-${index}`
|
|
578
|
+
: `flatlist-item-${index}`;
|
|
579
|
+
}
|
|
644
580
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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);
|
|
581
|
+
_getScrollOffset(event) {
|
|
582
|
+
const { vertical } = this.props;
|
|
583
|
+
return (
|
|
584
|
+
(event &&
|
|
585
|
+
event.nativeEvent &&
|
|
586
|
+
event.nativeEvent.contentOffset &&
|
|
587
|
+
event.nativeEvent.contentOffset[vertical ? 'y' : 'x']) ||
|
|
588
|
+
0
|
|
589
|
+
);
|
|
665
590
|
}
|
|
666
|
-
}
|
|
667
591
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
592
|
+
_getContainerInnerMargin(opposite = false) {
|
|
593
|
+
const { activeSlideAlignment } = this.props;
|
|
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
|
+
}
|
|
671
613
|
|
|
672
|
-
|
|
673
|
-
|
|
614
|
+
_getActiveSlideOffset() {
|
|
615
|
+
const { activeSlideOffset } = this.props;
|
|
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;
|
|
674
622
|
}
|
|
675
623
|
|
|
676
|
-
|
|
677
|
-
|
|
624
|
+
_getActiveItem(offset) {
|
|
625
|
+
const itemMainDimension = this._getItemMainDimension();
|
|
626
|
+
const center = offset + itemMainDimension / 2;
|
|
627
|
+
const activeSlideOffset = this._getActiveSlideOffset();
|
|
628
|
+
const lastIndex = this._positions.length - 1;
|
|
629
|
+
let itemIndex;
|
|
678
630
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
631
|
+
if (offset <= 0) {
|
|
632
|
+
return 0;
|
|
633
|
+
}
|
|
682
634
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
635
|
+
if (
|
|
636
|
+
this._positions[lastIndex] &&
|
|
637
|
+
offset >= this._positions[lastIndex].start
|
|
638
|
+
) {
|
|
639
|
+
return lastIndex;
|
|
640
|
+
}
|
|
687
641
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
642
|
+
for (let i = 0; i < this._positions.length; i++) {
|
|
643
|
+
const { start, end } = this._positions[i];
|
|
644
|
+
if (
|
|
645
|
+
center + activeSlideOffset >= start &&
|
|
646
|
+
center - activeSlideOffset <= end
|
|
647
|
+
) {
|
|
648
|
+
itemIndex = i;
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
692
652
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
653
|
+
return itemIndex || 0;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
_getSlideInterpolatedStyle(index, animatedValue) {
|
|
657
|
+
const { layoutCardOffset, slideInterpolatedStyle } = this.props;
|
|
658
|
+
|
|
659
|
+
if (slideInterpolatedStyle) {
|
|
660
|
+
return slideInterpolatedStyle(index, animatedValue, this.props);
|
|
697
661
|
} else if (this._shouldUseTinderLayout()) {
|
|
698
|
-
|
|
662
|
+
return tinderAnimatedStyles(
|
|
663
|
+
index,
|
|
664
|
+
animatedValue,
|
|
665
|
+
this.props,
|
|
666
|
+
layoutCardOffset,
|
|
667
|
+
);
|
|
668
|
+
} else if (this._shouldUseStackLayout()) {
|
|
669
|
+
return stackAnimatedStyles(
|
|
670
|
+
index,
|
|
671
|
+
animatedValue,
|
|
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);
|
|
699
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;
|
|
688
|
+
}
|
|
689
|
+
|
|
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);
|
|
700
737
|
|
|
701
738
|
if (
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
739
|
+
!this._mounted ||
|
|
740
|
+
!this._carouselRef ||
|
|
741
|
+
typeof offset === 'undefined'
|
|
705
742
|
) {
|
|
706
|
-
|
|
743
|
+
return;
|
|
707
744
|
}
|
|
708
745
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
746
|
+
const multiplier = this._currentScrollOffset === 0 ? 1 : -1;
|
|
747
|
+
const scrollDelta = scrollValue * multiplier;
|
|
748
|
+
|
|
749
|
+
this._scrollTo({
|
|
750
|
+
offset: offset + scrollDelta,
|
|
751
|
+
animated: false,
|
|
712
752
|
});
|
|
713
|
-
}
|
|
714
753
|
|
|
715
|
-
|
|
716
|
-
|
|
754
|
+
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
755
|
+
clearTimeout(this._hackSlideAnimationTimeout);
|
|
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;
|
|
717
767
|
|
|
718
|
-
|
|
719
|
-
|
|
768
|
+
if (
|
|
769
|
+
typeof index === 'undefined' ||
|
|
770
|
+
!this._shouldRepositionScroll(index)
|
|
771
|
+
) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
720
774
|
|
|
721
|
-
|
|
722
|
-
const offset = this._getItemScrollOffset(index);
|
|
775
|
+
let repositionTo = index;
|
|
723
776
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
777
|
+
if (index >= dataLength + loopClonesPerSide) {
|
|
778
|
+
repositionTo = index - dataLength;
|
|
779
|
+
} else if (index < loopClonesPerSide) {
|
|
780
|
+
repositionTo = index + dataLength;
|
|
781
|
+
}
|
|
727
782
|
|
|
728
|
-
|
|
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;
|
|
783
|
+
this._snapToItem(repositionTo, animated, false);
|
|
752
784
|
}
|
|
753
785
|
|
|
754
|
-
|
|
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
|
+
}
|
|
755
807
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
+
}
|
|
760
824
|
}
|
|
761
825
|
|
|
762
|
-
|
|
763
|
-
|
|
826
|
+
_onTouchStart(event) {
|
|
827
|
+
const { onTouchStart } = this.props;
|
|
764
828
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
!this._mounted ||
|
|
770
|
-
!wrappedRef ||
|
|
771
|
-
(typeof offset === 'undefined' && typeof index === 'undefined')
|
|
772
|
-
) {
|
|
773
|
-
return;
|
|
774
|
-
}
|
|
829
|
+
// `onTouchStart` is fired even when `scrollEnabled` is set to `false`
|
|
830
|
+
if (this._getScrollEnabled() !== false && this._autoplaying) {
|
|
831
|
+
this.pauseAutoPlay();
|
|
832
|
+
}
|
|
775
833
|
|
|
776
|
-
|
|
777
|
-
if (typeof index !== 'undefined') {
|
|
778
|
-
scrollToOffset = this._getItemScrollOffset(index);
|
|
779
|
-
} else {
|
|
780
|
-
scrollToOffset = offset;
|
|
834
|
+
onTouchStart && onTouchStart(event);
|
|
781
835
|
}
|
|
782
836
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
}
|
|
837
|
+
_onTouchEnd(event) {
|
|
838
|
+
const { onTouchEnd } = this.props;
|
|
786
839
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
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();
|
|
792
847
|
}
|
|
793
|
-
: {
|
|
794
|
-
offset,
|
|
795
|
-
animated,
|
|
796
|
-
};
|
|
797
848
|
|
|
798
|
-
|
|
799
|
-
wrappedRef.scrollTo(options);
|
|
800
|
-
} else {
|
|
801
|
-
wrappedRef.scrollToOffset(options);
|
|
849
|
+
onTouchEnd && onTouchEnd(event);
|
|
802
850
|
}
|
|
803
|
-
}
|
|
804
851
|
|
|
805
|
-
|
|
806
|
-
|
|
852
|
+
_onScroll(event) {
|
|
853
|
+
const { onScroll, onScrollIndexChanged, onSnapToItem } = 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);
|
|
807
860
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
861
|
+
this._currentScrollOffset = scrollOffset;
|
|
862
|
+
|
|
863
|
+
if (nextActiveItem !== this._onScrollActiveItem) {
|
|
864
|
+
this._onScrollActiveItem = nextActiveItem;
|
|
865
|
+
onScrollIndexChanged &&
|
|
866
|
+
onScrollIndexChanged(this._getDataIndex(nextActiveItem));
|
|
812
867
|
|
|
813
|
-
|
|
814
|
-
|
|
868
|
+
onSnapToItem && onSnapToItem(this._getDataIndex(nextActiveItem));
|
|
869
|
+
}
|
|
815
870
|
|
|
816
|
-
|
|
817
|
-
|
|
871
|
+
//last item
|
|
872
|
+
if (
|
|
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
|
+
}
|
|
818
880
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
!this._autoplaying
|
|
823
|
-
) {
|
|
824
|
-
// This event is buggy on Android, so a fallback is provided in _onMomentumScrollEnd()
|
|
825
|
-
this.startAutoplay();
|
|
881
|
+
if (typeof onScroll === 'function' && event) {
|
|
882
|
+
onScroll(event);
|
|
883
|
+
}
|
|
826
884
|
}
|
|
827
885
|
|
|
828
|
-
|
|
829
|
-
|
|
886
|
+
_onMomentumScrollEnd(event) {
|
|
887
|
+
const { autoplayDelay, onMomentumScrollEnd, onSnapToItem } = this.props;
|
|
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
|
+
}
|
|
830
910
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
911
|
+
onMomentumScrollEnd && onMomentumScrollEnd(event);
|
|
912
|
+
|
|
913
|
+
// The touchEnd event is buggy on Android, so this will serve as a fallback whenever needed
|
|
914
|
+
// https://github.com/facebook/react-native/issues/9439
|
|
915
|
+
if (IS_ANDROID && this._autoplay && !this._autoplaying) {
|
|
916
|
+
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
917
|
+
clearTimeout(this._enableAutoplayTimeout);
|
|
918
|
+
this._enableAutoplayTimeout = setTimeout(() => {
|
|
919
|
+
this.startAutoplay();
|
|
920
|
+
}, autoplayDelay);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
839
923
|
|
|
840
|
-
|
|
924
|
+
_onLayout(event) {
|
|
925
|
+
const { onLayout } = this.props;
|
|
841
926
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
927
|
+
// Prevent unneeded actions during the first 'onLayout' (triggered on init)
|
|
928
|
+
if (this._onLayoutInitDone) {
|
|
929
|
+
this._initPositionsAndInterpolators();
|
|
930
|
+
this._snapToItem(this._activeItem, false, false, true);
|
|
931
|
+
} else {
|
|
932
|
+
this._onLayoutInitDone = true;
|
|
933
|
+
}
|
|
846
934
|
|
|
847
|
-
|
|
935
|
+
onLayout && onLayout(event);
|
|
848
936
|
}
|
|
849
937
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
938
|
+
_snapToItem(
|
|
939
|
+
index,
|
|
940
|
+
animated = true,
|
|
941
|
+
fireCallback = true,
|
|
942
|
+
forceScrollTo = false,
|
|
855
943
|
) {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
944
|
+
const { onSnapToItem } = this.props;
|
|
945
|
+
const itemsLength = this._getCustomDataLength();
|
|
946
|
+
const wrappedRef = this._getWrappedRef();
|
|
947
|
+
if (!itemsLength || !wrappedRef) {
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
859
950
|
|
|
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
|
-
}
|
|
951
|
+
if (!index || index < 0) {
|
|
952
|
+
index = 0;
|
|
953
|
+
} else if (itemsLength > 0 && index >= itemsLength) {
|
|
954
|
+
index = itemsLength - 1;
|
|
955
|
+
}
|
|
889
956
|
|
|
890
|
-
|
|
957
|
+
if (index === this._activeItem && !forceScrollTo) {
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
891
960
|
|
|
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
|
-
}
|
|
961
|
+
const offset = this._getItemScrollOffset(index);
|
|
902
962
|
|
|
903
|
-
|
|
904
|
-
|
|
963
|
+
if (offset === undefined) {
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
905
966
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
} else {
|
|
911
|
-
this._onLayoutInitDone = true;
|
|
912
|
-
}
|
|
967
|
+
this._scrollTo({
|
|
968
|
+
offset,
|
|
969
|
+
animated,
|
|
970
|
+
});
|
|
913
971
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
972
|
+
// On both platforms, `onMomentumScrollEnd` won't be triggered if the scroll isn't animated
|
|
973
|
+
// so we need to trigger the callback manually
|
|
974
|
+
// On Android `onMomentumScrollEnd` won't be triggered when scrolling programmatically
|
|
975
|
+
// Therefore everything critical needs to be manually called here as well, even though the timing might be off
|
|
976
|
+
const requiresManualTrigger = !animated || IS_ANDROID;
|
|
977
|
+
if (requiresManualTrigger) {
|
|
978
|
+
this._activeItem = index;
|
|
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
|
+
}
|
|
928
996
|
}
|
|
929
997
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
998
|
+
startAutoplay() {
|
|
999
|
+
const { autoplayInterval, autoplayDelay } = this.props;
|
|
1000
|
+
this._autoplay = true;
|
|
1001
|
+
|
|
1002
|
+
if (this._autoplaying) {
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
935
1005
|
|
|
936
|
-
|
|
937
|
-
|
|
1006
|
+
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
1007
|
+
clearTimeout(this._autoplayTimeout);
|
|
1008
|
+
this._autoplayTimeout = setTimeout(() => {
|
|
1009
|
+
this._autoplaying = true;
|
|
1010
|
+
this._autoplayInterval = setInterval(() => {
|
|
1011
|
+
if (this._autoplaying) {
|
|
1012
|
+
this.snapToNext();
|
|
1013
|
+
}
|
|
1014
|
+
}, autoplayInterval);
|
|
1015
|
+
}, autoplayDelay);
|
|
938
1016
|
}
|
|
939
1017
|
|
|
940
|
-
|
|
1018
|
+
pauseAutoPlay() {
|
|
1019
|
+
this._autoplaying = false;
|
|
1020
|
+
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
1021
|
+
clearTimeout(this._autoplayTimeout);
|
|
1022
|
+
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
1023
|
+
clearTimeout(this._enableAutoplayTimeout);
|
|
1024
|
+
// @ts-expect-error setTimeout / clearTiemout is buggy :/
|
|
1025
|
+
clearInterval(this._autoplayInterval);
|
|
1026
|
+
}
|
|
941
1027
|
|
|
942
|
-
|
|
943
|
-
|
|
1028
|
+
stopAutoplay() {
|
|
1029
|
+
this._autoplay = false;
|
|
1030
|
+
this.pauseAutoPlay();
|
|
944
1031
|
}
|
|
945
1032
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
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);
|
|
1033
|
+
snapToNext(animated = true, fireCallback = true) {
|
|
1034
|
+
const itemsLength = this._getCustomDataLength();
|
|
1035
|
+
|
|
1036
|
+
let newIndex = this._activeItem + 1;
|
|
1037
|
+
if (newIndex > itemsLength - 1) {
|
|
1038
|
+
newIndex = 0;
|
|
972
1039
|
}
|
|
973
|
-
|
|
1040
|
+
this._snapToItem(newIndex, animated, fireCallback);
|
|
974
1041
|
}
|
|
975
|
-
}
|
|
976
1042
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
this._autoplay = true;
|
|
1043
|
+
snapToPrev(animated = true, fireCallback = true) {
|
|
1044
|
+
const itemsLength = this._getCustomDataLength();
|
|
980
1045
|
|
|
981
|
-
|
|
982
|
-
|
|
1046
|
+
let newIndex = this._activeItem - 1;
|
|
1047
|
+
if (newIndex < 0) {
|
|
1048
|
+
newIndex = itemsLength - 1;
|
|
1049
|
+
}
|
|
1050
|
+
this._snapToItem(newIndex, animated, fireCallback);
|
|
983
1051
|
}
|
|
984
1052
|
|
|
985
|
-
//
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
this._autoplaying = true;
|
|
989
|
-
this._autoplayInterval = setInterval(() => {
|
|
990
|
-
if (this._autoplaying) {
|
|
991
|
-
this.snapToNext();
|
|
992
|
-
}
|
|
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;
|
|
1053
|
+
// https://github.com/facebook/react-native/issues/1831#issuecomment-231069668
|
|
1054
|
+
triggerRenderingHack(offset = 1) {
|
|
1055
|
+
this._hackActiveSlideAnimation(this._activeItem, offset);
|
|
1018
1056
|
}
|
|
1019
|
-
this._snapToItem(newIndex, animated, fireCallback);
|
|
1020
|
-
}
|
|
1021
1057
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1058
|
+
_renderItem({ item, index }) {
|
|
1059
|
+
const { interpolators } = this.state;
|
|
1060
|
+
const { keyExtractor, slideStyle } = this.props;
|
|
1061
|
+
const animatedValue = interpolators && interpolators[index];
|
|
1024
1062
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
}
|
|
1029
|
-
this._snapToItem(newIndex, animated, fireCallback);
|
|
1030
|
-
}
|
|
1063
|
+
if (typeof animatedValue === 'undefined') {
|
|
1064
|
+
return null;
|
|
1065
|
+
}
|
|
1031
1066
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1067
|
+
const animate = this._shouldAnimateSlides();
|
|
1068
|
+
const Component = animate ? Animated.View : View;
|
|
1069
|
+
const animatedStyle = animate
|
|
1070
|
+
? this._getSlideInterpolatedStyle(index, animatedValue)
|
|
1071
|
+
: {};
|
|
1072
|
+
const dataIndex = this._getDataIndex(index);
|
|
1073
|
+
|
|
1074
|
+
const mainDimension = this.props.vertical
|
|
1075
|
+
? { height: this.props.itemHeight }
|
|
1076
|
+
: { width: this.props.itemWidth };
|
|
1077
|
+
const specificProps = this._needsScrollView()
|
|
1078
|
+
? {
|
|
1079
|
+
key: keyExtractor
|
|
1080
|
+
? keyExtractor(item, index)
|
|
1081
|
+
: this._getKeyExtractor(item, index),
|
|
1082
|
+
}
|
|
1083
|
+
: {};
|
|
1084
|
+
|
|
1085
|
+
return (
|
|
1086
|
+
<Component
|
|
1087
|
+
style={[mainDimension, slideStyle, animatedStyle]}
|
|
1088
|
+
pointerEvents="box-none"
|
|
1089
|
+
{...specificProps}>
|
|
1090
|
+
{this.props.vertical
|
|
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
|
+
);
|
|
1125
|
+
}
|
|
1036
1126
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1127
|
+
_getComponentOverridableProps() {
|
|
1128
|
+
const { hideCarousel } = this.state;
|
|
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
|
+
}
|
|
1041
1169
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1170
|
+
_getComponentStaticProps() {
|
|
1171
|
+
const { hideCarousel } = this.state;
|
|
1172
|
+
const {
|
|
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
|
+
};
|
|
1044
1275
|
}
|
|
1045
1276
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
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),
|
|
1277
|
+
render() {
|
|
1278
|
+
const { data, renderItem, useScrollView } = this.props;
|
|
1279
|
+
|
|
1280
|
+
if (!data || !renderItem) {
|
|
1281
|
+
return null;
|
|
1185
1282
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1283
|
+
|
|
1284
|
+
const props = {
|
|
1285
|
+
...this._getComponentOverridableProps(),
|
|
1286
|
+
...this.props,
|
|
1287
|
+
...this._getComponentStaticProps(),
|
|
1189
1288
|
};
|
|
1190
1289
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
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;
|
|
1290
|
+
const ScrollViewComponent =
|
|
1291
|
+
typeof useScrollView === 'function'
|
|
1292
|
+
? useScrollView
|
|
1293
|
+
: Animated.ScrollView;
|
|
1294
|
+
return this._needsScrollView() || !Animated.FlatList ? (
|
|
1295
|
+
<ScrollViewComponent {...props}>
|
|
1296
|
+
{this._getCustomData().map((item, index) => {
|
|
1297
|
+
return this._renderItem({
|
|
1298
|
+
item,
|
|
1299
|
+
index,
|
|
1300
|
+
realIndex: this._getDataIndex(index),
|
|
1301
|
+
activeIndex: this._getDataIndex(this._activeItem),
|
|
1302
|
+
});
|
|
1303
|
+
})}
|
|
1304
|
+
</ScrollViewComponent>
|
|
1305
|
+
) : (
|
|
1306
|
+
// @ts-expect-error Seems complicated to make TS 100% happy, while sharing that many things between
|
|
1307
|
+
// flatlist && scrollview implementation. I'll prob try to rewrite parts of the logic to overcome that.
|
|
1308
|
+
<Animated.FlatList {...props} />
|
|
1309
|
+
);
|
|
1259
1310
|
}
|
|
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
|
-
}
|
|
1286
1311
|
}
|
|
1287
1312
|
|
|
1288
1313
|
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
|
-
|
|
1314
|
+
data: PropTypes.array.isRequired,
|
|
1315
|
+
renderItem: PropTypes.func.isRequired,
|
|
1316
|
+
itemWidth: PropTypes.number, // required for horizontal carousel
|
|
1317
|
+
itemHeight: PropTypes.number, // required for vertical carousel
|
|
1318
|
+
sliderWidth: PropTypes.number, // required for horizontal carousel
|
|
1319
|
+
sliderHeight: PropTypes.number, // required for vertical carousel
|
|
1320
|
+
activeSlideAlignment: PropTypes.oneOf(['center', 'end', 'start']),
|
|
1321
|
+
activeSlideOffset: PropTypes.number,
|
|
1322
|
+
apparitionDelay: PropTypes.number,
|
|
1323
|
+
autoplay: PropTypes.bool,
|
|
1324
|
+
autoplayDelay: PropTypes.number,
|
|
1325
|
+
autoplayInterval: PropTypes.number,
|
|
1326
|
+
callbackOffsetMargin: PropTypes.number,
|
|
1327
|
+
containerCustomStyle: PropTypes.oneOfType([
|
|
1328
|
+
PropTypes.object,
|
|
1329
|
+
PropTypes.array,
|
|
1330
|
+
]),
|
|
1331
|
+
contentContainerCustomStyle: PropTypes.oneOfType([
|
|
1332
|
+
PropTypes.object,
|
|
1333
|
+
PropTypes.array,
|
|
1334
|
+
]),
|
|
1335
|
+
enableSnap: PropTypes.bool,
|
|
1336
|
+
firstItem: PropTypes.number,
|
|
1337
|
+
hasParallaxImages: PropTypes.bool,
|
|
1338
|
+
inactiveSlideOpacity: PropTypes.number,
|
|
1339
|
+
inactiveSlideScale: PropTypes.number,
|
|
1340
|
+
inactiveSlideShift: PropTypes.number,
|
|
1341
|
+
layout: PropTypes.oneOf(['default', 'stack', 'tinder']),
|
|
1342
|
+
layoutCardOffset: PropTypes.number,
|
|
1343
|
+
loop: PropTypes.bool,
|
|
1344
|
+
loopClonesPerSide: PropTypes.number,
|
|
1345
|
+
scrollEnabled: PropTypes.bool,
|
|
1346
|
+
scrollInterpolator: PropTypes.func,
|
|
1347
|
+
slideInterpolatedStyle: PropTypes.func,
|
|
1348
|
+
slideStyle: PropTypes.object,
|
|
1349
|
+
shouldOptimizeUpdates: PropTypes.bool,
|
|
1350
|
+
swipeThreshold: PropTypes.number,
|
|
1351
|
+
useScrollView: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
|
|
1352
|
+
vertical: PropTypes.bool,
|
|
1353
|
+
showsPagination: PropTypes.bool,
|
|
1354
|
+
isCustomScrollWidth: PropTypes.bool,
|
|
1355
|
+
disableIntervalMomentum: PropTypes.bool,
|
|
1356
|
+
useExperimentalSnap: PropTypes.bool,
|
|
1357
|
+
onBeforeSnapToItem: PropTypes.func,
|
|
1358
|
+
onSnapToItem: PropTypes.func,
|
|
1334
1359
|
};
|
|
1335
1360
|
|
|
1336
1361
|
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
|
-
|
|
1362
|
+
activeSlideAlignment: 'center',
|
|
1363
|
+
activeSlideOffset: 20,
|
|
1364
|
+
apparitionDelay: 0,
|
|
1365
|
+
autoplay: false,
|
|
1366
|
+
autoplayDelay: 1000,
|
|
1367
|
+
autoplayInterval: 3000,
|
|
1368
|
+
callbackOffsetMargin: 5,
|
|
1369
|
+
containerCustomStyle: {},
|
|
1370
|
+
contentContainerCustomStyle: {},
|
|
1371
|
+
enableSnap: true,
|
|
1372
|
+
firstItem: 0,
|
|
1373
|
+
hasParallaxImages: false,
|
|
1374
|
+
inactiveSlideOpacity: 0.7,
|
|
1375
|
+
inactiveSlideScale: 0.9,
|
|
1376
|
+
inactiveSlideShift: 0,
|
|
1377
|
+
layout: 'default',
|
|
1378
|
+
loop: false,
|
|
1379
|
+
loopClonesPerSide: 3,
|
|
1380
|
+
scrollEnabled: true,
|
|
1381
|
+
slideStyle: {},
|
|
1382
|
+
shouldOptimizeUpdates: true,
|
|
1383
|
+
useScrollView: !Animated.FlatList,
|
|
1384
|
+
vertical: false,
|
|
1385
|
+
isCustomScrollWidth: false,
|
|
1386
|
+
disableIntervalMomentum: IS_ANDROID,
|
|
1387
|
+
useExperimentalSnap: IS_ANDROID,
|
|
1363
1388
|
};
|