@momo-kits/swipe 0.0.49-beta → 0.0.49-beta.2

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/Swiper.js CHANGED
@@ -15,15 +15,14 @@
15
15
  import React, { Component } from 'react';
16
16
  import PropTypes from 'prop-types';
17
17
  import {
18
- Text,
19
18
  View,
20
19
  ScrollView,
21
20
  Dimensions,
22
21
  TouchableOpacity,
23
22
  Platform,
24
- ActivityIndicator
23
+ ActivityIndicator,
25
24
  } from 'react-native';
26
- import { ViewPager as ViewPagerAndroid } from '@momo-kits/core';
25
+ import { ViewPager as ViewPagerAndroid, Text } from '@momo-kits/core';
27
26
 
28
27
  /**
29
28
  * Default styles
@@ -33,7 +32,7 @@ const styles = {
33
32
  container: {
34
33
  backgroundColor: 'transparent',
35
34
  position: 'relative',
36
- flex: 1
35
+ flex: 1,
37
36
  },
38
37
 
39
38
  wrapperIOS: {
@@ -42,7 +41,7 @@ const styles = {
42
41
 
43
42
  wrapperAndroid: {
44
43
  backgroundColor: 'transparent',
45
- flex: 1
44
+ flex: 1,
46
45
  },
47
46
 
48
47
  slide: {
@@ -58,7 +57,7 @@ const styles = {
58
57
  flex: 1,
59
58
  justifyContent: 'center',
60
59
  alignItems: 'center',
61
- backgroundColor: 'transparent'
60
+ backgroundColor: 'transparent',
62
61
  },
63
62
 
64
63
  pagination_y: {
@@ -70,7 +69,7 @@ const styles = {
70
69
  flex: 1,
71
70
  justifyContent: 'center',
72
71
  alignItems: 'center',
73
- backgroundColor: 'transparent'
72
+ backgroundColor: 'transparent',
74
73
  },
75
74
 
76
75
  title: {
@@ -82,7 +81,7 @@ const styles = {
82
81
  left: 0,
83
82
  flexWrap: 'nowrap',
84
83
  width: 250,
85
- backgroundColor: 'transparent'
84
+ backgroundColor: 'transparent',
86
85
  },
87
86
 
88
87
  buttonWrapper: {
@@ -95,414 +94,448 @@ const styles = {
95
94
  paddingHorizontal: 10,
96
95
  paddingVertical: 10,
97
96
  justifyContent: 'space-between',
98
- alignItems: 'center'
97
+ alignItems: 'center',
99
98
  },
100
99
 
101
100
  buttonText: {
102
101
  fontSize: 50,
103
- color: '#007aff'
104
- }
102
+ color: '#007aff',
103
+ },
105
104
  };
106
105
 
107
106
  // missing `module.exports = exports['default'];` with babel6
108
107
  // export default React.createClass({
109
108
  export default class Swiper extends Component {
110
- /**
111
- * Props Validation
112
- * @type {Object}
113
- */
114
- static propTypes = {
115
- horizontal: PropTypes.bool,
116
- children: PropTypes.node.isRequired,
117
- containerStyle: PropTypes.oneOfType([
118
- PropTypes.object,
119
- PropTypes.number,
120
- ]),
121
- style: PropTypes.oneOfType([
122
- PropTypes.object,
123
- PropTypes.number,
124
- ]),
125
- scrollViewStyle: PropTypes.oneOfType([
126
- PropTypes.object,
127
- PropTypes.number,
128
- ]),
129
- pagingEnabled: PropTypes.bool,
130
- showsHorizontalScrollIndicator: PropTypes.bool,
131
- showsVerticalScrollIndicator: PropTypes.bool,
132
- bounces: PropTypes.bool,
133
- scrollsToTop: PropTypes.bool,
134
- removeClippedSubviews: PropTypes.bool,
135
- automaticallyAdjustContentInsets: PropTypes.bool,
136
- showsPagination: PropTypes.bool,
137
- showsButtons: PropTypes.bool,
138
- disableNextButton: PropTypes.bool,
139
- loadMinimal: PropTypes.bool,
140
- loadMinimalSize: PropTypes.number,
141
- loadMinimalLoader: PropTypes.element,
142
- loop: PropTypes.bool,
143
- autoplay: PropTypes.bool,
144
- autoplayTimeout: PropTypes.number,
145
- autoplayDirection: PropTypes.bool,
146
- index: PropTypes.number,
147
- renderPagination: PropTypes.func,
148
- dotStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
149
- activeDotStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
150
- dotColor: PropTypes.string,
151
- activeDotColor: PropTypes.string,
152
- /**
153
- * Called when the index has changed because the user swiped.
109
+ /**
110
+ * Props Validation
111
+ * @type {Object}
112
+ */
113
+ static propTypes = {
114
+ horizontal: PropTypes.bool,
115
+ children: PropTypes.node.isRequired,
116
+ containerStyle: PropTypes.oneOfType([
117
+ PropTypes.object,
118
+ PropTypes.number,
119
+ ]),
120
+ style: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
121
+ scrollViewStyle: PropTypes.oneOfType([
122
+ PropTypes.object,
123
+ PropTypes.number,
124
+ ]),
125
+ pagingEnabled: PropTypes.bool,
126
+ showsHorizontalScrollIndicator: PropTypes.bool,
127
+ showsVerticalScrollIndicator: PropTypes.bool,
128
+ bounces: PropTypes.bool,
129
+ scrollsToTop: PropTypes.bool,
130
+ removeClippedSubviews: PropTypes.bool,
131
+ automaticallyAdjustContentInsets: PropTypes.bool,
132
+ showsPagination: PropTypes.bool,
133
+ showsButtons: PropTypes.bool,
134
+ disableNextButton: PropTypes.bool,
135
+ loadMinimal: PropTypes.bool,
136
+ loadMinimalSize: PropTypes.number,
137
+ loadMinimalLoader: PropTypes.element,
138
+ loop: PropTypes.bool,
139
+ autoplay: PropTypes.bool,
140
+ autoplayTimeout: PropTypes.number,
141
+ autoplayDirection: PropTypes.bool,
142
+ index: PropTypes.number,
143
+ renderPagination: PropTypes.func,
144
+ dotStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
145
+ activeDotStyle: PropTypes.oneOfType([
146
+ PropTypes.object,
147
+ PropTypes.number,
148
+ ]),
149
+ dotColor: PropTypes.string,
150
+ activeDotColor: PropTypes.string,
151
+ /**
152
+ * Called when the index has changed because the user swiped.
153
+ */
154
+ onIndexChanged: PropTypes.func,
155
+ };
156
+
157
+ /**
158
+ * Default props
159
+ * @return {object} props
160
+ * @see http://facebook.github.io/react-native/docs/scrollview.html
161
+ */
162
+ static defaultProps = {
163
+ horizontal: true,
164
+ pagingEnabled: true,
165
+ showsHorizontalScrollIndicator: false,
166
+ showsVerticalScrollIndicator: false,
167
+ bounces: false,
168
+ scrollsToTop: false,
169
+ removeClippedSubviews: true,
170
+ automaticallyAdjustContentInsets: false,
171
+ showsPagination: true,
172
+ showsButtons: false,
173
+ disableNextButton: false,
174
+ loop: true,
175
+ loadMinimal: false,
176
+ loadMinimalSize: 1,
177
+ autoplay: false,
178
+ autoplayTimeout: 2.5,
179
+ autoplayDirection: true,
180
+ index: 0,
181
+ onIndexChanged: () => null,
182
+ };
183
+
184
+ /**
185
+ * Init states
186
+ * @return {object} states
187
+ */
188
+ state = this.initState(this.props);
189
+
190
+ /**
191
+ * Initial render flag
192
+ * @type {bool}
193
+ */
194
+ initialRender = true;
195
+
196
+ /**
197
+ * autoplay timer
198
+ * @type {null}
199
+ */
200
+ autoplayTimer = null;
201
+
202
+ loopJumpTimer = null;
203
+
204
+ UNSAFE_componentWillReceiveProps(nextProps) {
205
+ if (!nextProps.autoplay && this.autoplayTimer)
206
+ clearTimeout(this.autoplayTimer);
207
+ // Fix render last item first
208
+ if (nextProps.index === this.props.index) return;
209
+ this.setState(
210
+ this.initState(nextProps, this.props.index !== nextProps.index),
211
+ );
212
+ }
213
+
214
+ componentDidMount() {
215
+ this.autoplay();
216
+ }
217
+
218
+ componentWillUnmount() {
219
+ this.autoplayTimer && clearTimeout(this.autoplayTimer);
220
+ this.loopJumpTimer && clearTimeout(this.loopJumpTimer);
221
+ }
222
+
223
+ UNSAFE_componentWillUpdate(nextProps, nextState) {
224
+ // If the index has changed, we notify the parent via the onIndexChanged callback
225
+ if (this.state.index !== nextState.index)
226
+ this.props.onIndexChanged(nextState.index);
227
+ }
228
+
229
+ initState(props, updateIndex = false) {
230
+ // set the current state
231
+ const state = this.state || {
232
+ width: 0,
233
+ height: 0,
234
+ offset: { x: 0, y: 0 },
235
+ };
236
+
237
+ const initState = {
238
+ autoplayEnd: false,
239
+ loopJump: false,
240
+ offset: {},
241
+ };
242
+
243
+ initState.total = props.children ? props.children.length || 1 : 0;
244
+
245
+ if (state.total === initState.total && !updateIndex) {
246
+ // retain the index
247
+ initState.index = state.index;
248
+ } else {
249
+ initState.index =
250
+ initState.total > 1
251
+ ? Math.min(props.index, initState.total - 1)
252
+ : 0;
253
+ }
254
+
255
+ // Default: horizontal
256
+ const { width, height } = Dimensions.get('window');
257
+
258
+ initState.dir = props.horizontal === false ? 'y' : 'x';
259
+
260
+ if (props.width) {
261
+ initState.width = props.width;
262
+ } else if (this.state && this.state.width) {
263
+ initState.width = this.state.width;
264
+ } else {
265
+ initState.width = width;
266
+ }
267
+
268
+ if (props.height) {
269
+ initState.height = props.height;
270
+ } else if (this.state && this.state.height) {
271
+ initState.height = this.state.height;
272
+ } else {
273
+ initState.height = height;
274
+ }
275
+
276
+ initState.offset[initState.dir] =
277
+ initState.dir === 'y' ? height * props.index : width * props.index;
278
+
279
+ this.internals = {
280
+ ...this.internals,
281
+ isScrolling: false,
282
+ };
283
+ return initState;
284
+ }
285
+
286
+ // include internals with state
287
+ fullState() {
288
+ return { ...this.state, ...this.internals };
289
+ }
290
+
291
+ onLayout = (event) => {
292
+ const { width, height } = event.nativeEvent.layout;
293
+ const offset = (this.internals.offset = {});
294
+ const state = { width, height };
295
+
296
+ if (this.state.total > 1) {
297
+ let setup = this.state.index;
298
+ if (this.props.loop) {
299
+ setup++;
300
+ }
301
+ offset[this.state.dir] =
302
+ this.state.dir === 'y' ? height * setup : width * setup;
303
+ }
304
+
305
+ // only update the offset in state if needed, updating offset while swiping
306
+ // causes some bad jumping / stuttering
307
+ if (
308
+ !this.state.offset ||
309
+ width !== this.state.width ||
310
+ height !== this.state.height
311
+ ) {
312
+ state.offset = offset;
313
+ }
314
+
315
+ // related to https://github.com/leecade/react-native-swiper/issues/570
316
+ // contentOffset is not working in react 0.48.x so we need to use scrollTo
317
+ // to emulate offset.
318
+ if (Platform.OS === 'ios') {
319
+ if (this.initialRender && this.state.total > 1) {
320
+ this.scrollView.scrollTo({ ...offset, animated: false });
321
+ this.initialRender = false;
322
+ }
323
+ }
324
+
325
+ this.setState(state);
326
+ };
327
+
328
+ loopJump = () => {
329
+ if (!this.state.loopJump) return;
330
+ const i = this.state.index + (this.props.loop ? 1 : 0);
331
+ const { scrollView } = this;
332
+ this.loopJumpTimer = setTimeout(
333
+ () =>
334
+ scrollView.setPageWithoutAnimation &&
335
+ scrollView.setPageWithoutAnimation(i),
336
+ 50,
337
+ );
338
+ };
339
+
340
+ /**
341
+ * Automatic rolling
154
342
  */
155
- onIndexChanged: PropTypes.func
156
- }
157
-
158
- /**
159
- * Default props
160
- * @return {object} props
161
- * @see http://facebook.github.io/react-native/docs/scrollview.html
162
- */
163
- static defaultProps = {
164
- horizontal: true,
165
- pagingEnabled: true,
166
- showsHorizontalScrollIndicator: false,
167
- showsVerticalScrollIndicator: false,
168
- bounces: false,
169
- scrollsToTop: false,
170
- removeClippedSubviews: true,
171
- automaticallyAdjustContentInsets: false,
172
- showsPagination: true,
173
- showsButtons: false,
174
- disableNextButton: false,
175
- loop: true,
176
- loadMinimal: false,
177
- loadMinimalSize: 1,
178
- autoplay: false,
179
- autoplayTimeout: 2.5,
180
- autoplayDirection: true,
181
- index: 0,
182
- onIndexChanged: () => null
183
- }
184
-
185
- /**
186
- * Init states
187
- * @return {object} states
188
- */
189
- state = this.initState(this.props)
190
-
191
- /**
192
- * Initial render flag
193
- * @type {bool}
194
- */
195
- initialRender = true
196
-
197
- /**
198
- * autoplay timer
199
- * @type {null}
200
- */
201
- autoplayTimer = null
202
-
203
- loopJumpTimer = null
204
-
205
- UNSAFE_componentWillReceiveProps(nextProps) {
206
- if (!nextProps.autoplay && this.autoplayTimer) clearTimeout(this.autoplayTimer);
207
- // Fix render last item first
208
- if (nextProps.index === this.props.index) return;
209
- this.setState(this.initState(nextProps, this.props.index !== nextProps.index));
210
- }
211
-
212
- componentDidMount() {
213
- this.autoplay();
214
- }
215
-
216
- componentWillUnmount() {
217
- this.autoplayTimer && clearTimeout(this.autoplayTimer);
218
- this.loopJumpTimer && clearTimeout(this.loopJumpTimer);
219
- }
220
-
221
- UNSAFE_componentWillUpdate(nextProps, nextState) {
222
- // If the index has changed, we notify the parent via the onIndexChanged callback
223
- if (this.state.index !== nextState.index) this.props.onIndexChanged(nextState.index);
224
- }
225
-
226
- initState(props, updateIndex = false) {
227
- // set the current state
228
- const state = this.state || { width: 0, height: 0, offset: { x: 0, y: 0 } };
229
-
230
- const initState = {
231
- autoplayEnd: false,
232
- loopJump: false,
233
- offset: {}
234
- };
235
-
236
- initState.total = props.children ? props.children.length || 1 : 0;
237
-
238
- if (state.total === initState.total && !updateIndex) {
239
- // retain the index
240
- initState.index = state.index;
241
- } else {
242
- initState.index = initState.total > 1 ? Math.min(props.index, initState.total - 1) : 0;
243
- }
244
-
245
- // Default: horizontal
246
- const { width, height } = Dimensions.get('window');
247
-
248
- initState.dir = props.horizontal === false ? 'y' : 'x';
249
-
250
- if (props.width) {
251
- initState.width = props.width;
252
- } else if (this.state && this.state.width) {
253
- initState.width = this.state.width;
254
- } else {
255
- initState.width = width;
256
- }
257
-
258
- if (props.height) {
259
- initState.height = props.height;
260
- } else if (this.state && this.state.height) {
261
- initState.height = this.state.height;
262
- } else {
263
- initState.height = height;
264
- }
265
-
266
- initState.offset[initState.dir] = initState.dir === 'y'
267
- ? height * props.index
268
- : width * props.index;
269
-
270
- this.internals = {
271
- ...this.internals,
272
- isScrolling: false
273
- };
274
- return initState;
275
- }
276
-
277
- // include internals with state
278
- fullState() {
279
- return { ...this.state, ...this.internals };
280
- }
281
-
282
- onLayout = (event) => {
283
- const { width, height } = event.nativeEvent.layout;
284
- const offset = this.internals.offset = {};
285
- const state = { width, height };
286
-
287
- if (this.state.total > 1) {
288
- let setup = this.state.index;
289
- if (this.props.loop) {
290
- setup++;
291
- }
292
- offset[this.state.dir] = this.state.dir === 'y'
293
- ? height * setup
294
- : width * setup;
295
- }
296
-
297
- // only update the offset in state if needed, updating offset while swiping
298
- // causes some bad jumping / stuttering
299
- if (!this.state.offset || width !== this.state.width || height !== this.state.height) {
300
- state.offset = offset;
301
- }
302
-
303
- // related to https://github.com/leecade/react-native-swiper/issues/570
304
- // contentOffset is not working in react 0.48.x so we need to use scrollTo
305
- // to emulate offset.
306
- if (Platform.OS === 'ios') {
307
- if (this.initialRender && this.state.total > 1) {
308
- this.scrollView.scrollTo({ ...offset, animated: false });
309
- this.initialRender = false;
310
- }
311
- }
312
-
313
- this.setState(state);
314
- }
315
-
316
- loopJump = () => {
317
- if (!this.state.loopJump) return;
318
- const i = this.state.index + (this.props.loop ? 1 : 0);
319
- const { scrollView } = this;
320
- this.loopJumpTimer = setTimeout(() => scrollView.setPageWithoutAnimation
321
- && scrollView.setPageWithoutAnimation(i), 50);
322
- }
323
-
324
- /**
325
- * Automatic rolling
326
- */
327
- autoplay = () => {
328
- if (!Array.isArray(this.props.children)
329
- || !this.props.autoplay
330
- || this.internals.isScrolling
331
- || this.state.autoplayEnd) return;
332
-
333
- this.autoplayTimer && clearTimeout(this.autoplayTimer);
334
- this.autoplayTimer = setTimeout(() => {
335
- if (!this.props.loop && (
336
- this.props.autoplayDirection
337
- ? this.state.index === this.state.total - 1
338
- : this.state.index === 0
339
- )
340
- ) return this.setState({ autoplayEnd: true });
341
-
342
- this.scrollBy(this.props.autoplayDirection ? 1 : -1);
343
- }, this.props.autoplayTimeout * 1000);
344
- }
345
-
346
- /**
347
- * Scroll begin handle
348
- * @param {object} e native event
349
- */
350
- onScrollBegin = (e) => {
351
- // update scroll state
352
- this.internals.isScrolling = true;
353
- this.props.onScrollBeginDrag && this.props.onScrollBeginDrag(e, this.fullState(), this);
354
- }
355
-
356
- /**
357
- * Scroll end handle
358
- * @param {object} e native event
359
- */
360
- onScrollEnd = (e) => {
361
- // update scroll state
362
- this.internals.isScrolling = false;
363
-
364
- // making our events coming from android compatible to updateIndex logic
365
- if (!e.nativeEvent.contentOffset) {
366
- if (this.state.dir === 'x') {
367
- e.nativeEvent.contentOffset = { x: e.nativeEvent.position * this.state.width };
368
- } else {
369
- e.nativeEvent.contentOffset = { y: e.nativeEvent.position * this.state.height };
370
- }
371
- }
372
-
373
- this.updateIndex(e.nativeEvent.contentOffset, this.state.dir, () => {
374
- this.autoplay();
375
- this.loopJump();
376
-
377
- // if `onMomentumScrollEnd` registered will be called here
378
- this.props.onMomentumScrollEnd && this.props.onMomentumScrollEnd(e, this.fullState(), this);
379
- });
380
- }
381
-
382
- /*
383
- * Drag end handle
384
- * @param {object} e native event
385
- */
386
- onScrollEndDrag = (e) => {
387
- const { contentOffset } = e.nativeEvent;
388
- const { horizontal, children } = this.props;
389
- const { index } = this.state;
390
- const { offset } = this.internals;
391
- const previousOffset = horizontal ? offset.x : offset.y;
392
- const newOffset = horizontal ? contentOffset.x : contentOffset.y;
393
-
394
- if (previousOffset === newOffset
395
- && (index === 0 || index === children.length - 1)) {
396
- this.internals.isScrolling = false;
397
- }
398
- }
399
-
400
- /**
401
- * Update index after scroll
402
- * @param {object} offset content offset
403
- * @param {string} dir 'x' || 'y'
404
- */
405
- updateIndex = (offset, dir, cb) => {
406
- const { state } = this;
407
- let { index } = state;
408
- if (!this.internals.offset) // Android not setting this onLayout first? https://github.com/leecade/react-native-swiper/issues/582
409
- { this.internals.offset = {}; }
410
- const diff = offset[dir] - this.internals.offset[dir];
411
- const step = dir === 'x' ? state.width : state.height;
412
- let loopJump = false;
413
-
414
- // Do nothing if offset no change.
415
- if (!diff) return;
416
-
417
- // Note: if touch very very quickly and continuous,
418
- // the variation of `index` more than 1.
419
- // parseInt() ensures it's always an integer
420
- index = parseInt(index + Math.round(diff / step));
421
-
422
- if (this.props.loop) {
423
- if (index <= -1) {
424
- index = state.total - 1;
425
- offset[dir] = step * state.total;
426
- loopJump = true;
427
- } else if (index >= state.total) {
428
- index = 0;
429
- offset[dir] = step;
430
- loopJump = true;
431
- }
432
- }
433
-
434
- const newState = {};
435
- newState.index = index;
436
- newState.loopJump = loopJump;
437
-
438
- this.internals.offset = offset;
439
-
440
- // only update offset in state if loopJump is true
441
- if (loopJump) {
442
- // when swiping to the beginning of a looping set for the third time,
443
- // the new offset will be the same as the last one set in state.
444
- // Setting the offset to the same thing will not do anything,
445
- // so we increment it by 1 then immediately set it to what it should be,
446
- // after render.
447
- if (offset[dir] === this.internals.offset[dir]) {
448
- newState.offset = { x: 0, y: 0 };
449
- newState.offset[dir] = offset[dir] + 1;
450
- this.setState(newState, () => {
451
- this.setState({ offset }, cb);
452
- });
453
- } else {
454
- newState.offset = offset;
455
- this.setState(newState, cb);
456
- }
457
- } else {
458
- this.setState(newState, cb);
459
- }
460
- }
461
-
462
- /**
463
- * Scroll by index
464
- * @param {number} index offset index
465
- * @param {bool} animated
466
- */
467
-
468
- scrollBy = (index, animated = true) => {
469
- if (this.internals.isScrolling || this.state.total < 2) return;
470
- const { state } = this;
471
- const diff = (this.props.loop ? 1 : 0) + index + this.state.index;
472
- let x = 0;
473
- let y = 0;
474
- if (state.dir === 'x') x = diff * state.width;
475
- if (state.dir === 'y') y = diff * state.height;
476
-
477
- if (Platform.OS !== 'ios') {
478
- this.scrollView && this.scrollView[animated ? 'setPage' : 'setPageWithoutAnimation'](diff);
479
- } else {
480
- this.scrollView && this.scrollView.scrollTo({ x, y, animated });
481
- }
482
-
483
- // update scroll state
484
- this.internals.isScrolling = true;
485
- this.setState({
486
- autoplayEnd: false
487
- });
488
-
489
- // trigger onScrollEnd manually in android
490
- if (!animated || Platform.OS !== 'ios') {
491
- setImmediate(() => {
492
- this.onScrollEnd({
493
- nativeEvent: {
494
- position: diff
495
- }
496
- });
497
- });
498
- }
499
- }
500
-
501
- scrollViewPropOverrides = () => {
502
- const { props } = this;
503
- const overrides = {};
504
-
505
- /*
343
+ autoplay = () => {
344
+ if (
345
+ !Array.isArray(this?.props?.children) ||
346
+ !this.props.autoplay ||
347
+ this.internals.isScrolling ||
348
+ this.state.autoplayEnd
349
+ )
350
+ return;
351
+
352
+ this.autoplayTimer && clearTimeout(this.autoplayTimer);
353
+ this.autoplayTimer = setTimeout(() => {
354
+ if (
355
+ !this.props.loop &&
356
+ (this.props.autoplayDirection
357
+ ? this.state.index === this.state.total - 1
358
+ : this.state.index === 0)
359
+ )
360
+ return this.setState({ autoplayEnd: true });
361
+
362
+ this.scrollBy(this.props.autoplayDirection ? 1 : -1);
363
+ }, this.props.autoplayTimeout * 1000);
364
+ };
365
+
366
+ /**
367
+ * Scroll begin handle
368
+ * @param {object} e native event
369
+ */
370
+ onScrollBegin = (e) => {
371
+ // update scroll state
372
+ this.internals.isScrolling = true;
373
+ this.props.onScrollBeginDrag &&
374
+ this.props.onScrollBeginDrag(e, this.fullState(), this);
375
+ };
376
+
377
+ /**
378
+ * Scroll end handle
379
+ * @param {object} e native event
380
+ */
381
+ onScrollEnd = (e) => {
382
+ // update scroll state
383
+ this.internals.isScrolling = false;
384
+
385
+ // making our events coming from android compatible to updateIndex logic
386
+ if (!e.nativeEvent.contentOffset) {
387
+ if (this.state.dir === 'x') {
388
+ e.nativeEvent.contentOffset = {
389
+ x: e.nativeEvent.position * this.state.width,
390
+ };
391
+ } else {
392
+ e.nativeEvent.contentOffset = {
393
+ y: e.nativeEvent.position * this.state.height,
394
+ };
395
+ }
396
+ }
397
+
398
+ this.updateIndex(e.nativeEvent.contentOffset, this.state.dir, () => {
399
+ this.autoplay();
400
+ this.loopJump();
401
+
402
+ // if `onMomentumScrollEnd` registered will be called here
403
+ this.props.onMomentumScrollEnd &&
404
+ this.props.onMomentumScrollEnd(e, this.fullState(), this);
405
+ });
406
+ };
407
+
408
+ /*
409
+ * Drag end handle
410
+ * @param {object} e native event
411
+ */
412
+ onScrollEndDrag = (e) => {
413
+ const { contentOffset } = e.nativeEvent;
414
+ const { horizontal, children } = this.props;
415
+ const { index } = this.state;
416
+ const { offset } = this.internals;
417
+ const previousOffset = horizontal ? offset.x : offset.y;
418
+ const newOffset = horizontal ? contentOffset.x : contentOffset.y;
419
+
420
+ if (
421
+ previousOffset === newOffset &&
422
+ (index === 0 || index === children.length - 1)
423
+ ) {
424
+ this.internals.isScrolling = false;
425
+ }
426
+ };
427
+
428
+ /**
429
+ * Update index after scroll
430
+ * @param {object} offset content offset
431
+ * @param {string} dir 'x' || 'y'
432
+ */
433
+ updateIndex = (offset, dir, cb) => {
434
+ const { state } = this;
435
+ let { index } = state;
436
+ if (!this.internals.offset) {
437
+ // Android not setting this onLayout first? https://github.com/leecade/react-native-swiper/issues/582
438
+ this.internals.offset = {};
439
+ }
440
+ const diff = offset[dir] - this.internals.offset[dir];
441
+ const step = dir === 'x' ? state.width : state.height;
442
+ let loopJump = false;
443
+
444
+ // Do nothing if offset no change.
445
+ if (!diff) return;
446
+
447
+ // Note: if touch very very quickly and continuous,
448
+ // the variation of `index` more than 1.
449
+ // parseInt() ensures it's always an integer
450
+ index = parseInt(index + Math.round(diff / step));
451
+
452
+ if (this.props.loop) {
453
+ if (index <= -1) {
454
+ index = state.total - 1;
455
+ offset[dir] = step * state.total;
456
+ loopJump = true;
457
+ } else if (index >= state.total) {
458
+ index = 0;
459
+ offset[dir] = step;
460
+ loopJump = true;
461
+ }
462
+ }
463
+
464
+ const newState = {};
465
+ newState.index = index;
466
+ newState.loopJump = loopJump;
467
+
468
+ this.internals.offset = offset;
469
+
470
+ // only update offset in state if loopJump is true
471
+ if (loopJump) {
472
+ // when swiping to the beginning of a looping set for the third time,
473
+ // the new offset will be the same as the last one set in state.
474
+ // Setting the offset to the same thing will not do anything,
475
+ // so we increment it by 1 then immediately set it to what it should be,
476
+ // after render.
477
+ if (offset[dir] === this.internals.offset[dir]) {
478
+ newState.offset = { x: 0, y: 0 };
479
+ newState.offset[dir] = offset[dir] + 1;
480
+ this.setState(newState, () => {
481
+ this.setState({ offset }, cb);
482
+ });
483
+ } else {
484
+ newState.offset = offset;
485
+ this.setState(newState, cb);
486
+ }
487
+ } else {
488
+ this.setState(newState, cb);
489
+ }
490
+ };
491
+
492
+ /**
493
+ * Scroll by index
494
+ * @param {number} index offset index
495
+ * @param {bool} animated
496
+ */
497
+
498
+ scrollBy = (index, animated = true) => {
499
+ if (this.internals.isScrolling || this.state.total < 2) return;
500
+ const { state } = this;
501
+ const diff = (this.props.loop ? 1 : 0) + index + this.state.index;
502
+ let x = 0;
503
+ let y = 0;
504
+ if (state.dir === 'x') x = diff * state.width;
505
+ if (state.dir === 'y') y = diff * state.height;
506
+
507
+ if (Platform.OS !== 'ios') {
508
+ this.scrollView &&
509
+ this.scrollView[
510
+ animated ? 'setPage' : 'setPageWithoutAnimation'
511
+ ](diff);
512
+ } else {
513
+ this.scrollView && this.scrollView.scrollTo({ x, y, animated });
514
+ }
515
+
516
+ // update scroll state
517
+ this.internals.isScrolling = true;
518
+ this.setState({
519
+ autoplayEnd: false,
520
+ });
521
+
522
+ // trigger onScrollEnd manually in android
523
+ if (!animated || Platform.OS !== 'ios') {
524
+ setImmediate(() => {
525
+ this.onScrollEnd({
526
+ nativeEvent: {
527
+ position: diff,
528
+ },
529
+ });
530
+ });
531
+ }
532
+ };
533
+
534
+ scrollViewPropOverrides = () => {
535
+ const { props } = this;
536
+ const overrides = {};
537
+
538
+ /*
506
539
  const scrollResponders = [
507
540
  'onMomentumScrollBegin',
508
541
  'onTouchStartCapture',
@@ -512,263 +545,296 @@ export default class Swiper extends Component {
512
545
  ]
513
546
  */
514
547
 
515
- for (const prop in props) {
516
- // if(~scrollResponders.indexOf(prop)
517
- if (typeof props[prop] === 'function'
518
- && prop !== 'onMomentumScrollEnd'
519
- && prop !== 'renderPagination'
520
- && prop !== 'onScrollBeginDrag'
521
- ) {
522
- const originResponder = props[prop];
523
- overrides[prop] = (e) => originResponder(e, this.fullState(), this);
524
- }
525
- }
526
-
527
- return overrides;
528
- }
529
-
530
- /**
531
- * Render pagination
532
- * @return {object} react-dom
533
- */
534
- renderPagination = () => {
535
- // By default, dots only show when `total` >= 2
536
- if (this.state.total <= 1) return null;
537
-
538
- const dots = [];
539
- const ActiveDot = this.props.activeDot || (
540
- <View style={[{
541
- backgroundColor: this.props.activeDotColor || '#007aff',
542
- width: 8,
543
- height: 8,
544
- borderRadius: 4,
545
- marginLeft: 3,
546
- marginRight: 3,
547
- marginTop: 3,
548
- marginBottom: 3
549
- }, this.props.activeDotStyle]}
550
- />
551
- );
552
- const Dot = this.props.dot || (
553
- <View style={[{
554
- backgroundColor: this.props.dotColor || 'rgba(0,0,0,.2)',
555
- width: 8,
556
- height: 8,
557
- borderRadius: 4,
558
- marginLeft: 3,
559
- marginRight: 3,
560
- marginTop: 3,
561
- marginBottom: 3
562
- }, this.props.dotStyle]}
563
- />
564
- );
565
- for (let i = 0; i < this.state.total; i++) {
566
- dots.push(i === this.state.index
567
- ? React.cloneElement(ActiveDot, { key: i })
568
- : React.cloneElement(Dot, { key: i })
569
- );
570
- }
571
-
572
- return (
573
- <View pointerEvents="none" style={[styles[`pagination_${this.state.dir}`], this.props.paginationStyle]}>
574
- {dots}
575
- </View>
576
- );
577
- }
578
-
579
- renderTitle = () => {
580
- const child = this.props.children[this.state.index];
581
- const title = child && child.props && child.props.title;
582
- return title
583
- ? (
584
- <View style={styles.title}>
585
- {this.props.children[this.state.index].props.title}
586
- </View>
587
- )
588
- : null;
589
- }
590
-
591
- renderNextButton = () => {
592
- let button = null;
593
-
594
- if (this.props.loop
595
- || this.state.index !== this.state.total - 1) {
596
- button = this.props.nextButton || <Text style={styles.buttonText}>›</Text>;
597
- }
598
-
599
- return (
600
- <TouchableOpacity
601
- onPress={() => button !== null && this.scrollBy(1)}
602
- disabled={this.props.disableNextButton}
603
- >
604
- <View>
605
- {button}
606
- </View>
607
- </TouchableOpacity>
608
- );
609
- }
610
-
611
- renderPrevButton = () => {
612
- let button = null;
613
-
614
- if (this.props.loop || this.state.index !== 0) {
615
- button = this.props.prevButton || <Text style={styles.buttonText}>‹</Text>;
616
- }
617
-
618
- return (
619
- <TouchableOpacity onPress={() => button !== null && this.scrollBy(-1)}>
620
- <View>
621
- {button}
622
- </View>
623
- </TouchableOpacity>
624
- );
625
- }
626
-
627
- renderButtons = () => (
628
- <View
629
- pointerEvents="box-none"
630
- style={[styles.buttonWrapper, {
631
- width: this.state.width,
632
- height: this.state.height
633
- }, this.props.buttonWrapperStyle]}
634
- >
635
- {this.renderPrevButton()}
636
- {this.renderNextButton()}
637
- </View>
638
- )
639
-
640
- refScrollView = (view) => {
641
- this.scrollView = view;
642
- }
643
-
644
- onPageScrollStateChanged = (state) => {
645
- switch (state) {
646
- case 'dragging':
647
- return this.onScrollBegin();
648
-
649
- case 'idle':
650
- case 'settling':
651
- if (this.props.onTouchEnd) this.props.onTouchEnd();
652
- }
653
- }
654
-
655
- renderScrollView = (pages) => {
656
- if (Platform.OS === 'ios') {
657
- return (
658
- <ScrollView
659
- ref={this.refScrollView}
660
- {...this.props}
661
- {...this.scrollViewPropOverrides()}
662
- contentContainerStyle={[styles.wrapperIOS, this.props.style]}
663
- contentOffset={this.state.offset}
664
- onScrollBeginDrag={this.onScrollBegin}
665
- onMomentumScrollEnd={this.onScrollEnd}
666
- onScrollEndDrag={this.onScrollEndDrag}
667
- style={this.props.scrollViewStyle}
668
- >
669
- {pages}
670
- </ScrollView>
671
- );
672
- }
673
- return (
674
- <ViewPagerAndroid
675
- ref={this.refScrollView}
676
- {...this.props}
677
- initialPage={this.props.loop ? this.state.index + 1 : this.state.index}
678
- onPageScrollStateChanged={this.onPageScrollStateChanged}
679
- onPageSelected={this.onScrollEnd}
680
- key={pages.length}
681
- style={[styles.wrapperAndroid, this.props.style]}
682
- >
683
- {pages}
684
- </ViewPagerAndroid>
685
- );
686
- }
687
-
688
- checkPagesBeforeAfter = (children) => {
689
- let str = '';
690
- if (children && Array.isArray(children)) {
691
- for (const child in children) {
692
- str += children[child].key || child;
693
- }
694
- }
695
- return str;
696
- };
697
-
698
- /**
699
- * Default render
700
- * @return {object} react-dom
701
- */
702
- render() {
703
- const {
704
- index,
705
- total,
706
- width,
707
- height
708
- } = this.state;
709
- const {
710
- children,
711
- containerStyle,
712
- loop,
713
- loadMinimal,
714
- loadMinimalSize,
715
- loadMinimalLoader,
716
- renderPagination,
717
- showsButtons,
718
- showsPagination,
719
- } = this.props;
720
- // let dir = state.dir
721
- // let key = 0
722
- const loopVal = loop ? 1 : 0;
723
- let pages = [];
724
-
725
- const pageStyle = [{ width, height }, styles.slide];
726
- const pageStyleLoading = {
727
- width,
728
- height,
729
- flex: 1,
730
- justifyContent: 'center',
731
- alignItems: 'center'
732
- };
733
-
734
- // For make infinite at least total > 1
735
- if (total > 1) {
736
- // Re-design a loop model for avoid img flickering
737
- pages = Object.keys(children);
738
- // console.log(`checkPagesBeforeAfter children: ${JSON.stringify(this.checkPagesBeforeAfter(pages))}`);
739
-
740
- if (loop) {
741
- pages.unshift(`${total - 1}`);
742
- pages.push('0');
743
- }
744
-
745
- pages = pages.map((page, i) => {
746
- if (loadMinimal) {
747
- if (i >= (index + loopVal - loadMinimalSize)
748
- && i <= (index + loopVal + loadMinimalSize)) {
749
- return <View style={pageStyle} key={i}>{children[page]}</View>;
750
- }
751
- return (
752
- <View style={pageStyleLoading} key={i}>
753
- {loadMinimalLoader || <ActivityIndicator />}
754
- </View>
755
- );
756
- }
757
- return <View style={pageStyle} key={i}>{children[page]}</View>;
758
- });
759
- } else {
760
- pages = <View style={pageStyle} key={0}>{children}</View>;
761
- }
762
-
763
- return (
764
- <View style={[styles.container, containerStyle]} onLayout={this.onLayout}>
765
- {this.renderScrollView(pages)}
766
- {showsPagination && (renderPagination
767
- ? renderPagination(index, total, this)
768
- : this.renderPagination())}
769
- {this.renderTitle()}
770
- {showsButtons && this.renderButtons()}
771
- </View>
772
- );
773
- }
548
+ for (const prop in props) {
549
+ // if(~scrollResponders.indexOf(prop)
550
+ if (
551
+ typeof props[prop] === 'function' &&
552
+ prop !== 'onMomentumScrollEnd' &&
553
+ prop !== 'renderPagination' &&
554
+ prop !== 'onScrollBeginDrag'
555
+ ) {
556
+ const originResponder = props[prop];
557
+ overrides[prop] = (e) =>
558
+ originResponder(e, this.fullState(), this);
559
+ }
560
+ }
561
+
562
+ return overrides;
563
+ };
564
+
565
+ /**
566
+ * Render pagination
567
+ * @return {object} react-dom
568
+ */
569
+ renderPagination = () => {
570
+ // By default, dots only show when `total` >= 2
571
+ if (this.state.total <= 1) return null;
572
+
573
+ const dots = [];
574
+ const ActiveDot = this.props.activeDot || (
575
+ <View
576
+ style={[
577
+ {
578
+ backgroundColor: this.props.activeDotColor || '#007aff',
579
+ width: 8,
580
+ height: 8,
581
+ borderRadius: 4,
582
+ marginLeft: 3,
583
+ marginRight: 3,
584
+ marginTop: 3,
585
+ marginBottom: 3,
586
+ },
587
+ this.props.activeDotStyle,
588
+ ]}
589
+ />
590
+ );
591
+ const Dot = this.props.dot || (
592
+ <View
593
+ style={[
594
+ {
595
+ backgroundColor:
596
+ this.props.dotColor || 'rgba(0,0,0,.2)',
597
+ width: 8,
598
+ height: 8,
599
+ borderRadius: 4,
600
+ marginLeft: 3,
601
+ marginRight: 3,
602
+ marginTop: 3,
603
+ marginBottom: 3,
604
+ },
605
+ this.props.dotStyle,
606
+ ]}
607
+ />
608
+ );
609
+ for (let i = 0; i < this.state.total; i++) {
610
+ dots.push(
611
+ i === this.state.index
612
+ ? React.cloneElement(ActiveDot, { key: i })
613
+ : React.cloneElement(Dot, { key: i }),
614
+ );
615
+ }
616
+
617
+ return (
618
+ <View
619
+ pointerEvents="none"
620
+ style={[
621
+ styles[`pagination_${this.state.dir}`],
622
+ this.props.paginationStyle,
623
+ ]}>
624
+ {dots}
625
+ </View>
626
+ );
627
+ };
628
+
629
+ renderTitle = () => {
630
+ let { children = [] } = this.props;
631
+ const child = children[this.state.index];
632
+ const title = child && child.props && child.props.title;
633
+ return !!title ? (
634
+ <View style={styles.title}>
635
+ {this.props.children?.[this.state.index].props.title}
636
+ </View>
637
+ ) : null;
638
+ };
639
+
640
+ renderNextButton = () => {
641
+ let button = null;
642
+
643
+ if (this.props.loop || this.state.index !== this.state.total - 1) {
644
+ button = this.props.nextButton || (
645
+ <Text style={styles.buttonText}>›</Text>
646
+ );
647
+ }
648
+
649
+ return (
650
+ <TouchableOpacity
651
+ onPress={() => button !== null && this.scrollBy(1)}
652
+ disabled={this.props.disableNextButton}>
653
+ <View>{button}</View>
654
+ </TouchableOpacity>
655
+ );
656
+ };
657
+
658
+ renderPrevButton = () => {
659
+ let button = null;
660
+
661
+ if (this.props.loop || this.state.index !== 0) {
662
+ button = this.props.prevButton || (
663
+ <Text style={styles.buttonText}>‹</Text>
664
+ );
665
+ }
666
+
667
+ return (
668
+ <TouchableOpacity
669
+ onPress={() => button !== null && this.scrollBy(-1)}>
670
+ <View>{button}</View>
671
+ </TouchableOpacity>
672
+ );
673
+ };
674
+
675
+ renderButtons = () => (
676
+ <View
677
+ pointerEvents="box-none"
678
+ style={[
679
+ styles.buttonWrapper,
680
+ {
681
+ width: this.state.width,
682
+ height: this.state.height,
683
+ },
684
+ this.props.buttonWrapperStyle,
685
+ ]}>
686
+ {this.renderPrevButton()}
687
+ {this.renderNextButton()}
688
+ </View>
689
+ );
690
+
691
+ refScrollView = (view) => {
692
+ this.scrollView = view;
693
+ };
694
+
695
+ onPageScrollStateChanged = (state) => {
696
+ switch (state) {
697
+ case 'dragging':
698
+ return this.onScrollBegin();
699
+
700
+ case 'idle':
701
+ case 'settling':
702
+ if (this.props.onTouchEnd) this.props.onTouchEnd();
703
+ }
704
+ };
705
+
706
+ renderScrollView = (pages) => {
707
+ if (Platform.OS === 'ios') {
708
+ return (
709
+ <ScrollView
710
+ ref={this.refScrollView}
711
+ {...this.props}
712
+ {...this.scrollViewPropOverrides()}
713
+ contentContainerStyle={[
714
+ styles.wrapperIOS,
715
+ this.props.style,
716
+ ]}
717
+ contentOffset={this.state.offset}
718
+ onScrollBeginDrag={this.onScrollBegin}
719
+ onMomentumScrollEnd={this.onScrollEnd}
720
+ onScrollEndDrag={this.onScrollEndDrag}
721
+ style={this.props.scrollViewStyle}>
722
+ {pages}
723
+ </ScrollView>
724
+ );
725
+ }
726
+ return (
727
+ <ViewPagerAndroid
728
+ ref={this.refScrollView}
729
+ {...this.props}
730
+ initialPage={
731
+ this.props.loop ? this.state.index + 1 : this.state.index
732
+ }
733
+ onPageScrollStateChanged={this.onPageScrollStateChanged}
734
+ onPageSelected={this.onScrollEnd}
735
+ key={pages.length}
736
+ style={[styles.wrapperAndroid, this.props.style]}>
737
+ {pages}
738
+ </ViewPagerAndroid>
739
+ );
740
+ };
741
+
742
+ checkPagesBeforeAfter = (children) => {
743
+ let str = '';
744
+ if (children && Array.isArray(children)) {
745
+ for (const child in children) {
746
+ str += children[child].key || child;
747
+ }
748
+ }
749
+ return str;
750
+ };
751
+
752
+ /**
753
+ * Default render
754
+ * @return {object} react-dom
755
+ */
756
+ render() {
757
+ const { index, total, width, height } = this.state;
758
+ const {
759
+ children,
760
+ containerStyle,
761
+ loop,
762
+ loadMinimal,
763
+ loadMinimalSize,
764
+ loadMinimalLoader,
765
+ renderPagination,
766
+ showsButtons,
767
+ showsPagination,
768
+ } = this.props;
769
+ // let dir = state.dir
770
+ // let key = 0
771
+ const loopVal = loop ? 1 : 0;
772
+ let pages = [];
773
+
774
+ const pageStyle = [{ width, height }, styles.slide];
775
+ const pageStyleLoading = {
776
+ width,
777
+ height,
778
+ flex: 1,
779
+ justifyContent: 'center',
780
+ alignItems: 'center',
781
+ };
782
+
783
+ // For make infinite at least total > 1
784
+ if (total > 1) {
785
+ // Re-design a loop model for avoid img flickering
786
+ pages = Object.keys(children);
787
+ // console.log(`checkPagesBeforeAfter children: ${JSON.stringify(this.checkPagesBeforeAfter(pages))}`);
788
+
789
+ if (loop) {
790
+ pages.unshift(`${total - 1}`);
791
+ pages.push('0');
792
+ }
793
+
794
+ pages = pages.map((page, i) => {
795
+ if (loadMinimal) {
796
+ if (
797
+ i >= index + loopVal - loadMinimalSize &&
798
+ i <= index + loopVal + loadMinimalSize
799
+ ) {
800
+ return (
801
+ <View style={pageStyle} key={i}>
802
+ {children[page]}
803
+ </View>
804
+ );
805
+ }
806
+ return (
807
+ <View style={pageStyleLoading} key={i}>
808
+ {loadMinimalLoader || <ActivityIndicator />}
809
+ </View>
810
+ );
811
+ }
812
+ return (
813
+ <View style={pageStyle} key={i}>
814
+ {children[page]}
815
+ </View>
816
+ );
817
+ });
818
+ } else {
819
+ pages = (
820
+ <View style={pageStyle} key={0}>
821
+ {children}
822
+ </View>
823
+ );
824
+ }
825
+
826
+ return (
827
+ <View
828
+ style={[styles.container, containerStyle]}
829
+ onLayout={this.onLayout}>
830
+ {this.renderScrollView(pages)}
831
+ {showsPagination &&
832
+ (renderPagination
833
+ ? renderPagination(index, total, this)
834
+ : this.renderPagination())}
835
+ {this.renderTitle()}
836
+ {showsButtons && this.renderButtons()}
837
+ </View>
838
+ );
839
+ }
774
840
  }