@momo-kits/swipe 0.0.74-beta → 0.72.1

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/SwipeAction.js CHANGED
@@ -10,20 +10,19 @@
10
10
  /* eslint-disable no-unused-expressions */
11
11
  /* eslint-disable react/destructuring-assignment */
12
12
 
13
- import React, {
14
- Component,
15
- } from 'react';
13
+ import React, {Component} from 'react';
16
14
  import PropTypes from 'prop-types';
17
15
  import {
18
- Dimensions,
19
- Animated,
20
- PanResponder,
21
- StyleSheet,
22
- TouchableOpacity,
23
- View,
24
- Image
16
+ Dimensions,
17
+ Animated,
18
+ PanResponder,
19
+ StyleSheet,
20
+ TouchableOpacity,
21
+ View,
22
+ Image,
25
23
  } from 'react-native';
26
- import { Colors, ValueUtil, Text } from '@momo-kits/core';
24
+ import {Colors, ValueUtil, Text} from '@momo-kits/core';
25
+ import {TouchableHighlight} from 'react-native';
27
26
 
28
27
  const DEFAULT_PREVIEW_OPEN_DELAY = 700;
29
28
  const PREVIEW_CLOSE_DELAY = 300;
@@ -31,626 +30,679 @@ const MAX_VELOCITY_CONTRIBUTION = 5;
31
30
  const SCROLL_LOCK_MILLISECONDS = 300;
32
31
  const SCREEN_WIDTH = Dimensions.get('window').width;
33
32
  const WIDTH_HIDDEN_RIGHT = 190;
33
+
34
34
  class SwipeAction extends Component {
35
- constructor(props) {
36
- super(props);
37
- this.isOpen = false;
38
- this.previousTrackedTranslateX = 0;
39
- this.previousTrackedDirection = null;
40
- this.horizontalSwipeGestureBegan = false;
41
- this.swipeInitialX = null;
42
- this.parentScrollEnabled = true;
43
- this.ranPreview = false;
44
- this._ensureScrollEnabledTimer = null;
45
- this.isForceClosing = false;
46
- this.state = {
47
- dimensionsSet: false,
48
- hiddenHeight: this.props.disableHiddenLayoutCalculation ? '100%' : 0,
49
- hiddenWidth: this.props.disableHiddenLayoutCalculation ? '100%' : 0,
50
- isLeftActionVisible: false,
51
- isRightActionVisible: false,
52
- leftOpenValue: 0,
53
- rightOpenValue: 0
54
- };
55
- const { leftAction, rightAction } = this.props;
56
- const buttonCellWidth = WIDTH_HIDDEN_RIGHT / 2;
57
- // leftAction ? this.leftOpenValue = leftAction.length * buttonCellWidth : 0;
58
- // rightAction ? this.rightOpenValue = -rightAction.length * buttonCellWidth : 0;
59
- this.stopLeftSwipe = SCREEN_WIDTH;
60
- this.stopRightSwipe = -SCREEN_WIDTH;
61
- this._translateX = new Animated.Value(0);
62
- if (this.props.onSwipeValueChange) {
63
- this._translateX.addListener(({ value }) => {
64
- let direction = this.previousTrackedDirection;
65
- if (value !== this.previousTrackedTranslateX) {
66
- direction = value > this.previousTrackedTranslateX ? 'right' : 'left';
67
- }
68
- this.props.onSwipeValueChange && this.props.onSwipeValueChange({
69
- isOpen: this.isOpen,
70
- direction,
71
- value,
72
- });
73
- this.previousTrackedTranslateX = value;
74
- this.previousTrackedDirection = direction;
75
- });
76
- }
77
-
78
- if (this.props.forceCloseToRightThreshold && this.props.forceCloseToRightThreshold > 0) {
79
- this._translateX.addListener(({ value }) => {
80
- if (!this.isForceClosing && (SCREEN_WIDTH + value) < this.props.forceCloseToRightThreshold) {
81
- this.isForceClosing = true;
82
- this.forceCloseRow('right');
83
- if (this.props.onForceCloseToRight) {
84
- this.props.onForceCloseToRight();
85
- }
86
- }
87
- });
88
- }
89
-
90
- if (this.props.forceCloseToLeftThreshold && this.props.forceCloseToLeftThreshold > 0) {
91
- this._translateX.addListener(({ value }) => {
92
- if (!this.isForceClosing && (SCREEN_WIDTH - value) < this.props.forceCloseToLeftThreshold) {
93
- this.isForceClosing = true;
94
- this.forceCloseRow('left');
95
- if (this.props.onForceCloseToLeft) {
96
- this.props.onForceCloseToLeft();
97
- }
98
- }
99
- });
100
- }
101
- }
102
-
103
- UNSAFE_componentWillMount() {
104
- this._panResponder = PanResponder.create({
105
- onMoveShouldSetPanResponder: (e, gs) => this.handleOnMoveShouldSetPanResponder(e, gs),
106
- onPanResponderMove: (e, gs) => this.handlePanResponderMove(e, gs),
107
- onPanResponderRelease: (e, gs) => this.handlePanResponderEnd(e, gs),
108
- onPanResponderTerminate: (e, gs) => this.handlePanResponderEnd(e, gs),
109
- onShouldBlockNativeResponder: (_) => false,
110
- });
111
- }
112
-
113
- componentWillUnmount() {
114
- clearTimeout(this._ensureScrollEnabledTimer);
115
- this._translateX.removeAllListeners();
116
- }
117
-
118
- shouldComponentUpdate(nextProps, nextState) {
119
- if (this.state.hiddenHeight !== nextState.hiddenHeight
120
- || this.state.hiddenWidth !== nextState.hiddenWidth
121
- || !this.props.shouldItemUpdate
122
- || (this.props.shouldItemUpdate && this.props.shouldItemUpdate(this.props.item, nextProps.item))) {
123
- return true;
124
- }
125
-
126
- return false;
127
- }
128
-
129
- getPreviewAnimation(toValue, delay) {
130
- return Animated.timing(
131
- this._translateX,
132
- {
133
- duration: this.props.previewDuration, toValue, delay, useNativeDriver: this.props.useNativeDriver
134
- }
135
- );
136
- }
137
-
138
- onContentLayout(e) {
139
- this.setState({
140
- dimensionsSet: !this.props.recalculateHiddenLayout,
141
- ...(!this.props.disableHiddenLayoutCalculation ? {
142
- hiddenHeight: e.nativeEvent.layout.height,
143
- hiddenWidth: e.nativeEvent.layout.width,
144
- } : {})
145
- });
146
-
147
- if (this.props.preview && !this.ranPreview) {
148
- this.ranPreview = true;
149
- const previewOpenValue = this.props.previewOpenValue || this.state.rightOpenValue * 0.5;
150
- this.getPreviewAnimation(previewOpenValue, this.props.previewOpenDelay)
151
- .start((_) => {
152
- this.getPreviewAnimation(0, PREVIEW_CLOSE_DELAY).start();
153
- });
154
- }
155
- }
156
-
157
- onPress() {
158
- if (this.swipeInitialX == null || this.swipeInitialX === 0) {
159
- if (this.props.onPress && typeof this.props.onPress === 'function') {
160
- this.props.onPress();
161
- }
162
- } else {
163
- if (this.props.closeOnPress) {
164
- this.closeRow();
165
- }
166
- }
167
- }
168
-
169
- handleOnMoveShouldSetPanResponder(e, gs) {
170
- const { dx } = gs;
171
- return Math.abs(dx) > this.props.directionalDistanceChangeThreshold;
172
- }
173
-
174
- handlePanResponderMove(e, gestureState) {
175
- /* If the view is force closing, then ignore Moves. Return */
176
- if (this.isForceClosing) {
177
- return;
178
- }
179
-
180
- /* Else, do normal job */
181
- const { dx, dy } = gestureState;
182
- const absDx = Math.abs(dx);
183
- const absDy = Math.abs(dy);
184
-
185
- // this check may not be necessary because we don't capture the move until we pass the threshold
186
- // just being extra safe here
187
- if (absDx > this.props.directionalDistanceChangeThreshold || absDy > this.props.directionalDistanceChangeThreshold) {
188
- // we have enough to determine direction
189
- if (absDy > absDx && !this.horizontalSwipeGestureBegan) {
190
- // user is moving vertically, do nothing, listView will handle
191
- return;
192
- }
193
-
194
- // user is moving horizontally
195
- if (this.parentScrollEnabled) {
196
- // disable scrolling on the listView parent
197
- this.parentScrollEnabled = false;
198
- this.props.setScrollEnabled && this.props.setScrollEnabled(false);
199
- }
200
-
201
- if (this.swipeInitialX === null) {
202
- // set tranlateX value when user started swiping
203
- this.swipeInitialX = this._translateX._value;
204
- }
205
- if (!this.horizontalSwipeGestureBegan) {
206
- this.horizontalSwipeGestureBegan = true;
207
- const direction = dx > 0 ? 'TO_RIGHT' : 'TO_LEFT';
208
- this.props.swipeGestureBegan && this.props.swipeGestureBegan(direction);
209
- }
210
-
211
- let newDX = this.swipeInitialX + dx;
212
- if (this.props.disableRightAction && newDX < 0) { newDX = 0; }
213
- if (this.props.disableLeftAction && newDX > 0) { newDX = 0; }
214
-
215
- if (this.stopLeftSwipe && newDX > this.stopLeftSwipe) { newDX = this.stopLeftSwipe; }
216
- if (this.stopRightSwipe && newDX < this.stopRightSwipe) { newDX = this.stopRightSwipe; }
217
-
218
- // set action buttons visibility
219
- if (newDX > 0) {
220
- this.setState({
221
- isLeftActionVisible: true,
222
- isRightActionVisible: false
223
- });
224
- } else if (newDX < 0) {
225
- this.setState({
226
- isRightActionVisible: true,
227
- isLeftActionVisible: false,
228
- });
229
- }
230
-
231
- this._translateX.setValue(newDX);
232
- }
233
- }
234
-
235
- ensureScrollEnabled = () => {
236
- if (!this.parentScrollEnabled) {
237
- this.parentScrollEnabled = true;
238
- this.props.setScrollEnabled && this.props.setScrollEnabled(true);
239
- }
240
- }
241
-
242
- handlePanResponderEnd(e, gestureState) {
243
- /* PandEnd will reset the force-closing state when it's true. */
244
- if (this.isForceClosing) {
245
- this.isForceClosing = false;
246
- }
247
- // decide how much the velocity will affect the final position that the list item settles in.
248
- const { swipeToOpenVelocityContribution } = this.props;
249
- const possibleExtraPixels = this.state.rightOpenValue * (swipeToOpenVelocityContribution);
250
- const clampedVelocity = Math.min(gestureState.vx, MAX_VELOCITY_CONTRIBUTION);
251
- const projectedExtraPixels = possibleExtraPixels * (clampedVelocity / MAX_VELOCITY_CONTRIBUTION);
252
-
253
- // re-enable scrolling on listView parent
254
- this._ensureScrollEnabledTimer = setTimeout(this.ensureScrollEnabled, SCROLL_LOCK_MILLISECONDS);
255
-
256
- // finish up the animation
257
- let toValue = 0;
258
- if (this._translateX._value >= 0) {
259
- // trying to swipe right
260
- if (this.swipeInitialX < this._translateX._value) {
261
- if ((this._translateX._value - projectedExtraPixels) > this.state.leftOpenValue * (this.props.swipeToOpenPercent / 100)) {
262
- // we're more than halfway
263
- toValue = this.state.leftOpenValue;
264
- }
265
- } else if ((this._translateX._value - projectedExtraPixels) > this.state.leftOpenValue * (1 - (this.props.swipeToClosePercent / 100))) {
266
- toValue = this.state.leftOpenValue;
267
- }
268
- } else {
269
- // trying to swipe left
270
- if (this.swipeInitialX > this._translateX._value) {
271
- if ((this._translateX._value - projectedExtraPixels) < this.state.rightOpenValue * (this.props.swipeToOpenPercent / 100)) {
272
- // we're more than halfway
273
- toValue = this.state.rightOpenValue;
274
- }
275
- } else if ((this._translateX._value - projectedExtraPixels) < this.state.rightOpenValue * (1 - (this.props.swipeToClosePercent / 100))) {
276
- toValue = this.state.rightOpenValue;
277
- }
278
- }
279
-
280
- this.props.swipeGestureEnd && this.props.swipeGestureEnd();
281
- this.manuallySwipeRow(toValue);
282
- }
283
-
284
- /*
285
- * This method is called by SwipeListView
286
- */
287
- closeRow() {
288
- this.manuallySwipeRow(0);
289
- }
290
-
291
- /**
292
- * Force close the row toward the end of the given direction.
293
- * @param {String} direction The direction to force close.
294
- */
295
- forceCloseRow(direction) {
296
- this.manuallySwipeRow(0, () => {
297
- if (direction === 'right' && this.props.onForceCloseToRightEnd) {
298
- this.props.onForceCloseToRightEnd();
299
- } else if (direction === 'left' && this.props.onForceCloseToLeftEnd) {
300
- this.props.onForceCloseToLeftEnd();
301
- }
302
- });
303
- }
304
-
305
- closeRowWithoutAnimation() {
306
- this._translateX.setValue(0);
307
-
308
- this.ensureScrollEnabled();
309
- this.isOpen = false;
310
- this.props.onDidClose && this.props.onDidClose();
311
-
312
- this.props.onClose && this.props.onClose();
313
-
314
- this.swipeInitialX = null;
315
- this.horizontalSwipeGestureBegan = false;
316
- }
317
-
318
- manuallySwipeRow(toValue, onAnimationEnd) {
319
- Animated.spring(
320
- this._translateX,
321
- {
322
- toValue,
323
- friction: this.props.friction,
324
- tension: this.props.tension,
325
- useNativeDriver: this.props.useNativeDriver,
326
- }
327
- ).start((_) => {
328
- this._translateX.setValue(toValue);
329
- this.ensureScrollEnabled();
330
- if (toValue === 0) {
331
- this.isOpen = false;
332
- this.props.onDidClose && this.props.onDidClose();
333
- } else {
334
- this.isOpen = true;
335
- this.props.onDidOpen && this.props.onDidOpen(toValue);
336
- }
337
- if (onAnimationEnd) {
338
- onAnimationEnd();
339
- }
340
- });
341
-
342
- if (toValue === 0) {
343
- this.props.onClose && this.props.onClose();
344
- } else {
345
- this.props.onOpen && this.props.onOpen(toValue);
346
- }
347
-
348
- // reset everything
349
- this.swipeInitialX = toValue;
350
- this.horizontalSwipeGestureBegan = false;
351
- }
352
-
353
- renderVisibleContent() {
354
- if (this.props.children) {
355
- // handle touchables
356
- const { onPress } = this.props.children.props;
357
-
358
- if (onPress) {
359
- const newOnPress = (_) => {
360
- this.onPress();
361
- onPress();
362
- };
363
- return React.cloneElement(
364
- this.props.children,
365
- {
366
- ...this.props.children.props,
367
- onPress: newOnPress
368
- }
369
- );
370
- }
371
- return (
372
- <TouchableOpacity
373
- activeOpacity={1}
374
- onPress={(_) => this.onPress()}
375
- >
376
- {this.props.children}
377
- </TouchableOpacity>
378
- );
379
- }
380
- }
381
-
382
- renderRowContent() {
383
- // We do this annoying if statement for performance.
384
- // We don't want the onLayout func to run after it runs once.
385
- if (this.state.dimensionsSet) {
386
- return (
387
- <Animated.View
388
- manipulationModes={['translateX']}
389
- {...this._panResponder.panHandlers}
390
- style={{
391
- zIndex: 2,
392
- transform: [
393
- { translateX: this._translateX }
394
- ]
395
- }}
396
- >
397
- {this.renderVisibleContent()}
398
- </Animated.View>
399
- );
400
- }
401
- return (
402
- <Animated.View
403
- manipulationModes={['translateX']}
404
- {...this._panResponder.panHandlers}
405
- onLayout={(e) => this.onContentLayout(e)}
406
- style={{
407
- zIndex: 2,
408
- transform: [
409
- { translateX: this._translateX }
410
- ]
411
- }}
412
- >
413
- {this.renderVisibleContent()}
414
- </Animated.View>
415
- );
416
- }
417
-
418
- onLeftActionLayout = ({ nativeEvent }) => {
419
- this.setState({
420
- leftOpenValue: nativeEvent.layout.width
421
- });
422
- }
423
-
424
- onRightActionLayout = ({ nativeEvent }) => {
425
- this.setState({
426
- rightOpenValue: -nativeEvent.layout.width
427
- });
428
- }
429
-
430
- renderHiddenContent = () => {
431
- const {
432
- actionBackground, rowIndex, rightAction, leftAction
433
- } = this.props;
434
- const hiddenRowStyle = {
435
- ...styles.standaloneRowBack,
436
- ...{ backgroundColor: actionBackground }
437
- };
438
- return (
439
- <View style={hiddenRowStyle}>
440
- {
441
- this.state.isLeftActionVisible
442
- && (
443
- <View style={styles.leftItemRow}>
444
- <View style={{ flexDirection: 'row' }} onLayout={this.onLeftActionLayout}>
445
- {
446
- leftAction?.map?.((item, index) => this.renderLeftAction(item, index))
447
- }
448
- </View>
449
- </View>
450
- )
451
- }
452
- {
453
- this.state.isRightActionVisible
454
- && (
455
- <View style={styles.rightItemRow}>
456
- <View style={{ flexDirection: 'row' }} onLayout={this.onRightActionLayout}>
457
- {
458
- rightAction?.slice?.(0)?.reverse().map((item, index) => this.renderRightAction(item, index))
459
- }
460
- </View>
461
- </View>
462
- )
463
- }
464
-
465
- </View>
466
- );
467
- }
468
-
469
- renderIcon = (icon) => {
470
- const { actionIconStyle } = this.props;
471
- const iconSource = ValueUtil.getImageSource(icon);
472
- return (
473
- <Image
474
- source={iconSource}
475
- resizeMode="contain"
476
- style={[styles.icon, actionIconStyle]}
477
- />
478
- );
479
- }
480
-
481
- renderTitle = (title) => {
482
- const { actionTextStyle } = this.props;
483
- return (
484
- <Text.SubTitle style={[styles.text, actionTextStyle]}>
485
- {title}
486
- </Text.SubTitle>
487
- );
488
- }
489
-
490
- onActionPress = (item, rowIndex) => () => {
491
- const { onPress } = item;
492
- onPress?.(item, rowIndex);
493
- }
494
-
495
- renderRightAction = (item, index) => {
496
- const { rowIndex, renderRightAction } = this.props;
497
- if (renderRightAction) {
498
- return (
499
- <View key={item + index}>
500
- {renderRightAction({ item, index, rowIndex })}
501
- </View>
502
- );
503
- }
504
- return (
505
- <TouchableOpacity
506
- key={index.toString()}
507
- style={[
508
- styles.buttonContainer,
509
- { backgroundColor: item.color, height: this.state.hiddenHeight }
510
- ]}
511
- onPress={this.onActionPress(item, rowIndex)}
512
- >
513
- {item.icon && this.renderIcon(item.icon)}
514
- {item.title && this.renderTitle(item.title)}
515
- </TouchableOpacity>
516
- );
517
- }
518
-
519
- renderLeftAction = (item, index) => {
520
- const { rowIndex, renderLeftAction } = this.props;
521
- if (renderLeftAction) {
522
- return (
523
- <View key={item + index}>
524
- {renderLeftAction({ item, index, rowIndex })}
525
- </View>
526
- );
527
- }
528
- return (
529
- <TouchableOpacity
530
- key={index.toString()}
531
- style={[
532
- styles.buttonContainer,
533
- { backgroundColor: item.color, height: this.state.hiddenHeight }
534
- ]}
535
- onPress={this.onActionPress(item, rowIndex)}
536
- >
537
- {item.icon && this.renderIcon(item.icon)}
538
- {item.title && this.renderTitle(item.title)}
539
- </TouchableOpacity>
540
- );
541
- }
542
-
543
- render() {
544
- return (
545
- <View style={this.props.style ? this.props.style : styles.container}>
546
- <View style={[
547
- styles.hidden,
548
- {
549
- height: this.state.hiddenHeight,
550
- width: this.state.hiddenWidth,
551
- }
552
- ]}
553
- >
554
- {this.renderHiddenContent()}
555
- </View>
556
- {this.renderRowContent()}
557
- </View>
558
- );
559
- }
35
+ constructor(props) {
36
+ super(props);
37
+ this.isOpen = false;
38
+ this.previousTrackedTranslateX = 0;
39
+ this.previousTrackedDirection = null;
40
+ this.horizontalSwipeGestureBegan = false;
41
+ this.swipeInitialX = null;
42
+ this.parentScrollEnabled = true;
43
+ this.ranPreview = false;
44
+ this._ensureScrollEnabledTimer = null;
45
+ this.isForceClosing = false;
46
+ this.state = {
47
+ dimensionsSet: false,
48
+ hiddenHeight: this.props.disableHiddenLayoutCalculation ? '100%' : 0,
49
+ hiddenWidth: this.props.disableHiddenLayoutCalculation ? '100%' : 0,
50
+ isLeftActionVisible: false,
51
+ isRightActionVisible: false,
52
+ leftOpenValue: 0,
53
+ rightOpenValue: 0,
54
+ stopLeftSwipe: SCREEN_WIDTH,
55
+ stopRightSwipe: -SCREEN_WIDTH,
56
+ };
57
+ const {leftAction, rightAction} = this.props;
58
+ const buttonCellWidth = WIDTH_HIDDEN_RIGHT / 2;
59
+ // leftAction ? this.leftOpenValue = leftAction.length * buttonCellWidth : 0;
60
+ // rightAction ? this.rightOpenValue = -rightAction.length * buttonCellWidth : 0;
61
+ this._translateX = new Animated.Value(0);
62
+ if (this.props.onSwipeValueChange) {
63
+ this._translateX.addListener(({value}) => {
64
+ let direction = this.previousTrackedDirection;
65
+ if (value !== this.previousTrackedTranslateX) {
66
+ direction = value > this.previousTrackedTranslateX ? 'right' : 'left';
67
+ }
68
+ this.props.onSwipeValueChange &&
69
+ this.props.onSwipeValueChange({
70
+ isOpen: this.isOpen,
71
+ direction,
72
+ value,
73
+ });
74
+ this.previousTrackedTranslateX = value;
75
+ this.previousTrackedDirection = direction;
76
+ });
77
+ }
78
+
79
+ if (
80
+ this.props.forceCloseToRightThreshold &&
81
+ this.props.forceCloseToRightThreshold > 0
82
+ ) {
83
+ this._translateX.addListener(({value}) => {
84
+ if (
85
+ !this.isForceClosing &&
86
+ SCREEN_WIDTH + value < this.props.forceCloseToRightThreshold
87
+ ) {
88
+ this.isForceClosing = true;
89
+ this.forceCloseRow('right');
90
+ if (this.props.onForceCloseToRight) {
91
+ this.props.onForceCloseToRight();
92
+ }
93
+ }
94
+ });
95
+ }
96
+
97
+ if (
98
+ this.props.forceCloseToLeftThreshold &&
99
+ this.props.forceCloseToLeftThreshold > 0
100
+ ) {
101
+ this._translateX.addListener(({value}) => {
102
+ if (
103
+ !this.isForceClosing &&
104
+ SCREEN_WIDTH - value < this.props.forceCloseToLeftThreshold
105
+ ) {
106
+ this.isForceClosing = true;
107
+ this.forceCloseRow('left');
108
+ if (this.props.onForceCloseToLeft) {
109
+ this.props.onForceCloseToLeft();
110
+ }
111
+ }
112
+ });
113
+ }
114
+ }
115
+
116
+ UNSAFE_componentWillMount() {
117
+ this._panResponder = PanResponder.create({
118
+ onMoveShouldSetPanResponder: (e, gs) =>
119
+ this.handleOnMoveShouldSetPanResponder(e, gs),
120
+ onPanResponderMove: (e, gs) => this.handlePanResponderMove(e, gs),
121
+ onPanResponderRelease: (e, gs) => this.handlePanResponderEnd(e, gs),
122
+ onPanResponderTerminate: (e, gs) => this.handlePanResponderEnd(e, gs),
123
+ onShouldBlockNativeResponder: _ => false,
124
+ });
125
+ }
126
+
127
+ componentWillUnmount() {
128
+ clearTimeout(this._ensureScrollEnabledTimer);
129
+ this._translateX.removeAllListeners();
130
+ }
131
+
132
+ shouldComponentUpdate(nextProps, nextState) {
133
+ if (
134
+ this.state.hiddenHeight !== nextState.hiddenHeight ||
135
+ this.state.hiddenWidth !== nextState.hiddenWidth ||
136
+ !this.props.shouldItemUpdate ||
137
+ (this.props.shouldItemUpdate &&
138
+ this.props.shouldItemUpdate(this.props.item, nextProps.item))
139
+ ) {
140
+ return true;
141
+ }
142
+
143
+ return false;
144
+ }
145
+
146
+ getPreviewAnimation(toValue, delay) {
147
+ return Animated.timing(this._translateX, {
148
+ duration: this.props.previewDuration,
149
+ toValue,
150
+ delay,
151
+ useNativeDriver: this.props.useNativeDriver,
152
+ });
153
+ }
154
+
155
+ onContentLayout(e) {
156
+ this.setState({
157
+ dimensionsSet: !this.props.recalculateHiddenLayout,
158
+ ...(!this.props.disableHiddenLayoutCalculation
159
+ ? {
160
+ hiddenHeight: e.nativeEvent.layout.height,
161
+ hiddenWidth: e.nativeEvent.layout.width,
162
+ }
163
+ : {}),
164
+ });
165
+
166
+ if (this.props.preview && !this.ranPreview) {
167
+ this.ranPreview = true;
168
+ const previewOpenValue =
169
+ this.props.previewOpenValue || this.state.rightOpenValue * 0.5;
170
+ this.getPreviewAnimation(
171
+ previewOpenValue,
172
+ this.props.previewOpenDelay,
173
+ ).start(_ => {
174
+ this.getPreviewAnimation(0, PREVIEW_CLOSE_DELAY).start();
175
+ });
176
+ }
177
+ }
178
+
179
+ onPress() {
180
+ if (this.swipeInitialX == null || this.swipeInitialX === 0) {
181
+ if (this.props.onPress && typeof this.props.onPress === 'function') {
182
+ this.props.onPress();
183
+ }
184
+ } else {
185
+ if (this.props.closeOnPress) {
186
+ this.closeRow();
187
+ }
188
+ }
189
+ }
190
+
191
+ handleOnMoveShouldSetPanResponder(e, gs) {
192
+ const {dx} = gs;
193
+ return Math.abs(dx) > this.props.directionalDistanceChangeThreshold;
194
+ }
195
+
196
+ handlePanResponderMove(e, gestureState) {
197
+ /* If the view is force closing, then ignore Moves. Return */
198
+ if (this.isForceClosing) {
199
+ return;
200
+ }
201
+
202
+ /* Else, do normal job */
203
+ const {dx, dy} = gestureState;
204
+ const absDx = Math.abs(dx);
205
+ const absDy = Math.abs(dy);
206
+
207
+ // this check may not be necessary because we don't capture the move until we pass the threshold
208
+ // just being extra safe here
209
+ if (
210
+ absDx > this.props.directionalDistanceChangeThreshold ||
211
+ absDy > this.props.directionalDistanceChangeThreshold
212
+ ) {
213
+ // we have enough to determine direction
214
+ if (absDy > absDx && !this.horizontalSwipeGestureBegan) {
215
+ // user is moving vertically, do nothing, listView will handle
216
+ return;
217
+ }
218
+
219
+ // user is moving horizontally
220
+ if (this.parentScrollEnabled) {
221
+ // disable scrolling on the listView parent
222
+ this.parentScrollEnabled = false;
223
+ this.props.setScrollEnabled && this.props.setScrollEnabled(false);
224
+ }
225
+
226
+ if (this.swipeInitialX === null) {
227
+ // set tranlateX value when user started swiping
228
+ this.swipeInitialX = this._translateX._value;
229
+ }
230
+ if (!this.horizontalSwipeGestureBegan) {
231
+ this.horizontalSwipeGestureBegan = true;
232
+ const direction = dx > 0 ? 'TO_RIGHT' : 'TO_LEFT';
233
+ this.props.swipeGestureBegan && this.props.swipeGestureBegan(direction);
234
+ }
235
+
236
+ let newDX = this.swipeInitialX + dx;
237
+ if (this.props.disableRightAction && newDX < 0) {
238
+ newDX = 0;
239
+ }
240
+ if (this.props.disableLeftAction && newDX > 0) {
241
+ newDX = 0;
242
+ }
243
+
244
+ if (this.state.stopLeftSwipe && newDX > this.state.stopLeftSwipe) {
245
+ newDX = this.state.stopLeftSwipe;
246
+ }
247
+ if (this.state.stopRightSwipe && newDX < this.state.stopRightSwipe) {
248
+ newDX = this.state.stopRightSwipe;
249
+ }
250
+
251
+ // set action buttons visibility
252
+ if (newDX > 0) {
253
+ this.setState({
254
+ isLeftActionVisible: true,
255
+ isRightActionVisible: false,
256
+ });
257
+ } else if (newDX < 0) {
258
+ this.setState({
259
+ isRightActionVisible: true,
260
+ isLeftActionVisible: false,
261
+ });
262
+ }
263
+
264
+ this._translateX.setValue(newDX);
265
+ }
266
+ }
267
+
268
+ ensureScrollEnabled = () => {
269
+ if (!this.parentScrollEnabled) {
270
+ this.parentScrollEnabled = true;
271
+ this.props.setScrollEnabled && this.props.setScrollEnabled(true);
272
+ }
273
+ };
274
+
275
+ handlePanResponderEnd(e, gestureState) {
276
+ /* PandEnd will reset the force-closing state when it's true. */
277
+ if (this.isForceClosing) {
278
+ this.isForceClosing = false;
279
+ }
280
+ // decide how much the velocity will affect the final position that the list item settles in.
281
+ const {swipeToOpenVelocityContribution} = this.props;
282
+ const possibleExtraPixels =
283
+ this.state.rightOpenValue * swipeToOpenVelocityContribution;
284
+ const clampedVelocity = Math.min(
285
+ gestureState.vx,
286
+ MAX_VELOCITY_CONTRIBUTION,
287
+ );
288
+ const projectedExtraPixels =
289
+ possibleExtraPixels * (clampedVelocity / MAX_VELOCITY_CONTRIBUTION);
290
+
291
+ // re-enable scrolling on listView parent
292
+ this._ensureScrollEnabledTimer = setTimeout(
293
+ this.ensureScrollEnabled,
294
+ SCROLL_LOCK_MILLISECONDS,
295
+ );
296
+
297
+ // finish up the animation
298
+ let toValue = 0;
299
+ if (this._translateX._value >= 0) {
300
+ // trying to swipe right
301
+ if (this.swipeInitialX < this._translateX._value) {
302
+ if (
303
+ this._translateX._value - projectedExtraPixels >
304
+ this.state.leftOpenValue * (this.props.swipeToOpenPercent / 100)
305
+ ) {
306
+ // we're more than halfway
307
+ toValue = this.state.leftOpenValue;
308
+ }
309
+ } else if (
310
+ this._translateX._value - projectedExtraPixels >
311
+ this.state.leftOpenValue * (1 - this.props.swipeToClosePercent / 100)
312
+ ) {
313
+ toValue = this.state.leftOpenValue;
314
+ }
315
+ } else {
316
+ // trying to swipe left
317
+ if (this.swipeInitialX > this._translateX._value) {
318
+ if (
319
+ this._translateX._value - projectedExtraPixels <
320
+ this.state.rightOpenValue * (this.props.swipeToOpenPercent / 100)
321
+ ) {
322
+ // we're more than halfway
323
+ toValue = this.state.rightOpenValue;
324
+ }
325
+ } else if (
326
+ this._translateX._value - projectedExtraPixels <
327
+ this.state.rightOpenValue * (1 - this.props.swipeToClosePercent / 100)
328
+ ) {
329
+ toValue = this.state.rightOpenValue;
330
+ }
331
+ }
332
+
333
+ this.props.swipeGestureEnd && this.props.swipeGestureEnd();
334
+ this.manuallySwipeRow(toValue);
335
+ }
336
+
337
+ /*
338
+ * This method is called by SwipeListView
339
+ */
340
+ closeRow() {
341
+ this.manuallySwipeRow(0);
342
+ }
343
+
344
+ /**
345
+ * Force close the row toward the end of the given direction.
346
+ * @param {String} direction The direction to force close.
347
+ */
348
+ forceCloseRow(direction) {
349
+ this.manuallySwipeRow(0, () => {
350
+ if (direction === 'right' && this.props.onForceCloseToRightEnd) {
351
+ this.props.onForceCloseToRightEnd();
352
+ } else if (direction === 'left' && this.props.onForceCloseToLeftEnd) {
353
+ this.props.onForceCloseToLeftEnd();
354
+ }
355
+ });
356
+ }
357
+
358
+ closeRowWithoutAnimation() {
359
+ this._translateX.setValue(0);
360
+
361
+ this.ensureScrollEnabled();
362
+ this.isOpen = false;
363
+ this.props.onDidClose && this.props.onDidClose();
364
+
365
+ this.props.onClose && this.props.onClose();
366
+
367
+ this.swipeInitialX = null;
368
+ this.horizontalSwipeGestureBegan = false;
369
+ }
370
+
371
+ manuallySwipeRow(toValue, onAnimationEnd) {
372
+ Animated.spring(this._translateX, {
373
+ toValue,
374
+ friction: this.props.friction,
375
+ tension: this.props.tension,
376
+ useNativeDriver: this.props.useNativeDriver,
377
+ }).start(_ => {
378
+ this._translateX.setValue(toValue);
379
+ this.ensureScrollEnabled();
380
+ if (toValue === 0) {
381
+ this.isOpen = false;
382
+ this.props.onDidClose && this.props.onDidClose();
383
+ } else {
384
+ this.isOpen = true;
385
+ this.props.onDidOpen && this.props.onDidOpen(toValue);
386
+ }
387
+ if (onAnimationEnd) {
388
+ onAnimationEnd();
389
+ }
390
+ });
391
+
392
+ if (toValue === 0) {
393
+ this.props.onClose && this.props.onClose();
394
+ } else {
395
+ this.props.onOpen && this.props.onOpen(toValue);
396
+ }
397
+
398
+ // reset everything
399
+ this.swipeInitialX = toValue;
400
+ this.horizontalSwipeGestureBegan = false;
401
+ }
402
+
403
+ renderVisibleContent() {
404
+ if (this.props.children) {
405
+ // handle touchables
406
+ const {onPress} = this.props.children.props;
407
+
408
+ if (onPress) {
409
+ const newOnPress = _ => {
410
+ this.onPress();
411
+ onPress();
412
+ };
413
+ return React.cloneElement(this.props.children, {
414
+ ...this.props.children.props,
415
+ onPress: newOnPress,
416
+ });
417
+ }
418
+ return (
419
+ <TouchableHighlight
420
+ activeOpacity={0.8}
421
+ underlayColor="white"
422
+ onPress={_ => this.onPress()}>
423
+ {this.props.children}
424
+ </TouchableHighlight>
425
+ );
426
+ }
427
+ }
428
+
429
+ renderRowContent() {
430
+ // We do this annoying if statement for performance.
431
+ // We don't want the onLayout func to run after it runs once.
432
+ if (this.state.dimensionsSet) {
433
+ return (
434
+ <Animated.View
435
+ manipulationModes={['translateX']}
436
+ {...this._panResponder.panHandlers}
437
+ style={{
438
+ zIndex: 2,
439
+ transform: [{translateX: this._translateX}],
440
+ }}>
441
+ {this.renderVisibleContent()}
442
+ </Animated.View>
443
+ );
444
+ }
445
+ return (
446
+ <Animated.View
447
+ manipulationModes={['translateX']}
448
+ {...this._panResponder.panHandlers}
449
+ onLayout={e => this.onContentLayout(e)}
450
+ style={{
451
+ zIndex: 2,
452
+ transform: [{translateX: this._translateX}],
453
+ }}>
454
+ {this.renderVisibleContent()}
455
+ </Animated.View>
456
+ );
457
+ }
458
+
459
+ onLeftActionLayout = ({nativeEvent}) => {
460
+ this.setState({
461
+ leftOpenValue: nativeEvent.layout.width,
462
+ stopLeftSwipe: nativeEvent.layout.width,
463
+ });
464
+ };
465
+
466
+ onRightActionLayout = ({nativeEvent}) => {
467
+ this.setState({
468
+ rightOpenValue: -nativeEvent.layout.width,
469
+ stopRightSwipe: -nativeEvent.layout.width,
470
+ });
471
+ };
472
+
473
+ renderHiddenContent = () => {
474
+ const {actionBackground, rowIndex, rightAction, leftAction} = this.props;
475
+ const hiddenRowStyle = {
476
+ ...styles.standaloneRowBack,
477
+ ...{backgroundColor: actionBackground},
478
+ };
479
+ console.log(this.state.stopLeftSwipe);
480
+ return (
481
+ <View style={hiddenRowStyle}>
482
+ {this.state.isLeftActionVisible && (
483
+ <View style={styles.leftItemRow}>
484
+ <View
485
+ style={{flexDirection: 'row'}}
486
+ onLayout={this.onLeftActionLayout}>
487
+ {leftAction?.map?.((item, index) =>
488
+ this.renderLeftAction(item, index),
489
+ )}
490
+ </View>
491
+ </View>
492
+ )}
493
+ {this.state.isRightActionVisible && (
494
+ <View style={styles.rightItemRow}>
495
+ <View
496
+ style={{flexDirection: 'row'}}
497
+ onLayout={this.onRightActionLayout}>
498
+ {rightAction
499
+ ?.slice?.(0)
500
+ ?.reverse()
501
+ .map((item, index) => this.renderRightAction(item, index))}
502
+ </View>
503
+ </View>
504
+ )}
505
+ </View>
506
+ );
507
+ };
508
+
509
+ renderIcon = icon => {
510
+ const {actionIconStyle} = this.props;
511
+ const iconSource = ValueUtil.getImageSource(icon);
512
+ return (
513
+ <Image
514
+ source={iconSource}
515
+ resizeMode="contain"
516
+ style={[styles.icon, actionIconStyle]}
517
+ />
518
+ );
519
+ };
520
+
521
+ renderTitle = title => {
522
+ const {actionTextStyle} = this.props;
523
+ return (
524
+ <Text.SubTitle style={[styles.text, actionTextStyle]}>
525
+ {title}
526
+ </Text.SubTitle>
527
+ );
528
+ };
529
+
530
+ onActionPress = (item, rowIndex) => () => {
531
+ const {onPress} = item;
532
+ onPress?.(item, rowIndex);
533
+ };
534
+
535
+ renderRightAction = (item, index) => {
536
+ const {rowIndex, renderRightAction} = this.props;
537
+ if (renderRightAction) {
538
+ return (
539
+ <View key={item + index}>
540
+ {renderRightAction({
541
+ item,
542
+ index,
543
+ rowIndex,
544
+ })}
545
+ </View>
546
+ );
547
+ }
548
+ return (
549
+ <TouchableOpacity
550
+ key={index.toString()}
551
+ style={[
552
+ styles.buttonContainer,
553
+ {
554
+ backgroundColor: item.color,
555
+ height: this.state.hiddenHeight,
556
+ },
557
+ ]}
558
+ onPress={this.onActionPress(item, rowIndex)}>
559
+ {item.icon && this.renderIcon(item.icon)}
560
+ {item.title && this.renderTitle(item.title)}
561
+ </TouchableOpacity>
562
+ );
563
+ };
564
+
565
+ renderLeftAction = (item, index) => {
566
+ const {rowIndex, renderLeftAction} = this.props;
567
+ if (renderLeftAction) {
568
+ return (
569
+ <View key={item + index}>
570
+ {renderLeftAction({
571
+ item,
572
+ index,
573
+ rowIndex,
574
+ })}
575
+ </View>
576
+ );
577
+ }
578
+ return (
579
+ <TouchableOpacity
580
+ key={index.toString()}
581
+ style={[
582
+ styles.buttonContainer,
583
+ {
584
+ backgroundColor: item.color,
585
+ height: this.state.hiddenHeight,
586
+ },
587
+ ]}
588
+ onPress={this.onActionPress(item, rowIndex)}>
589
+ {item.icon && this.renderIcon(item.icon)}
590
+ {item.title && this.renderTitle(item.title)}
591
+ </TouchableOpacity>
592
+ );
593
+ };
594
+
595
+ render() {
596
+ return (
597
+ <View style={this.props.style ? this.props.style : styles.container}>
598
+ <View
599
+ style={[
600
+ styles.hidden,
601
+ {
602
+ height: this.state.hiddenHeight,
603
+ width: this.state.hiddenWidth,
604
+ },
605
+ ]}>
606
+ {this.renderHiddenContent()}
607
+ </View>
608
+ {this.renderRowContent()}
609
+ </View>
610
+ );
611
+ }
560
612
  }
561
613
 
562
614
  const styles = StyleSheet.create({
563
- container: {
564
- // As of RN 0.29 flex: 1 is causing all rows to be the same height
565
- // flex: 1
566
- },
567
- hidden: {
568
- zIndex: 1,
569
- bottom: 0,
570
- left: 0,
571
- overflow: 'hidden',
572
- position: 'absolute',
573
- right: 0,
574
- top: 0,
575
- },
576
- standaloneRowBack: {
577
- backgroundColor: Colors.white,
578
- alignItems: 'center',
579
- flex: 1,
580
- flexDirection: 'row',
581
- justifyContent: 'space-between'
582
- },
583
- leftItemRow: {
584
- alignItems: 'center',
585
- flex: 1,
586
- flexDirection: 'row',
587
- justifyContent: 'flex-start',
588
- paddingRight: 10
589
- },
590
- rightItemRow: {
591
- alignItems: 'center',
592
- flex: 1,
593
- flexDirection: 'row',
594
- justifyContent: 'flex-end',
595
- paddingLeft: 10,
596
- },
597
- buttonContainer: {
598
- backgroundColor: 'red',
599
- alignItems: 'center',
600
- justifyContent: 'center',
601
- width: 88
602
- },
603
- icon: {
604
- height: 24,
605
- width: 24,
606
- tintColor: 'white'
607
- },
608
- text: {
609
- color: Colors.white,
610
- textAlign: 'center',
611
- marginHorizontal: 10,
612
- marginTop: 10
613
- }
615
+ container: {
616
+ // As of RN 0.29 flex: 1 is causing all rows to be the same height
617
+ // flex: 1
618
+ },
619
+ hidden: {
620
+ zIndex: 1,
621
+ bottom: 0,
622
+ left: 0,
623
+ overflow: 'hidden',
624
+ position: 'absolute',
625
+ right: 0,
626
+ top: 0,
627
+ },
628
+ standaloneRowBack: {
629
+ backgroundColor: Colors.white,
630
+ alignItems: 'center',
631
+ flex: 1,
632
+ flexDirection: 'row',
633
+ justifyContent: 'space-between',
634
+ },
635
+ leftItemRow: {
636
+ alignItems: 'center',
637
+ flex: 1,
638
+ flexDirection: 'row',
639
+ justifyContent: 'flex-start',
640
+ paddingRight: 10,
641
+ },
642
+ rightItemRow: {
643
+ alignItems: 'center',
644
+ flex: 1,
645
+ flexDirection: 'row',
646
+ justifyContent: 'flex-end',
647
+ paddingLeft: 10,
648
+ },
649
+ buttonContainer: {
650
+ backgroundColor: 'red',
651
+ alignItems: 'center',
652
+ justifyContent: 'center',
653
+ width: 88,
654
+ },
655
+ icon: {
656
+ height: 24,
657
+ width: 24,
658
+ tintColor: 'white',
659
+ },
660
+ text: {
661
+ color: Colors.white,
662
+ textAlign: 'center',
663
+ marginHorizontal: 10,
664
+ marginTop: 10,
665
+ },
614
666
  });
615
667
 
616
668
  SwipeAction.propTypes = {
617
- onOpen: PropTypes.func,
618
- closeOnPress: PropTypes.bool,
619
- disableRightAction: PropTypes.bool,
620
- disableLeftAction: PropTypes.bool,
621
- onClose: PropTypes.func,
622
- style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
623
- leftAction: PropTypes.array,
624
- rightAction: PropTypes.array,
625
- renderRightAction: PropTypes.func,
626
- renderLeftAction: PropTypes.func,
627
- onPress: PropTypes.func,
628
- children: PropTypes.element.isRequired,
629
- actionBackground: PropTypes.string,
630
- actionTextStyle: PropTypes.object,
631
- swipeGestureBegan: PropTypes.func,
632
- swipeGestureEnd: PropTypes.func,
669
+ onOpen: PropTypes.func,
670
+ closeOnPress: PropTypes.bool,
671
+ disableRightAction: PropTypes.bool,
672
+ disableLeftAction: PropTypes.bool,
673
+ onClose: PropTypes.func,
674
+ style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
675
+ leftAction: PropTypes.array,
676
+ rightAction: PropTypes.array,
677
+ renderRightAction: PropTypes.func,
678
+ renderLeftAction: PropTypes.func,
679
+ onPress: PropTypes.func,
680
+ children: PropTypes.element.isRequired,
681
+ actionBackground: PropTypes.string,
682
+ actionTextStyle: PropTypes.object,
683
+ swipeGestureBegan: PropTypes.func,
684
+ swipeGestureEnd: PropTypes.func,
633
685
  };
634
686
 
635
687
  SwipeAction.defaultProps = {
636
- closeOnPress: true,
637
- disableRightAction: true,
638
- disableLeftAction: true,
639
- recalculateHiddenLayout: false,
640
- disableHiddenLayoutCalculation: false,
641
- preview: false,
642
- previewDuration: 300,
643
- previewOpenDelay: DEFAULT_PREVIEW_OPEN_DELAY,
644
- directionalDistanceChangeThreshold: 2,
645
- swipeToOpenPercent: 50,
646
- swipeToOpenVelocityContribution: 0,
647
- swipeToClosePercent: 50,
648
- swipeToPerformActionPercent: 50,
649
- item: {},
650
- useNativeDriver: true,
651
- rightAction: [],
652
- leftAction: [],
653
- actionBackground: Colors.white
688
+ closeOnPress: true,
689
+ disableRightAction: true,
690
+ disableLeftAction: true,
691
+ recalculateHiddenLayout: false,
692
+ disableHiddenLayoutCalculation: false,
693
+ preview: false,
694
+ previewDuration: 300,
695
+ previewOpenDelay: DEFAULT_PREVIEW_OPEN_DELAY,
696
+ directionalDistanceChangeThreshold: 2,
697
+ swipeToOpenPercent: 50,
698
+ swipeToOpenVelocityContribution: 0,
699
+ swipeToClosePercent: 50,
700
+ swipeToPerformActionPercent: 50,
701
+ item: {},
702
+ useNativeDriver: true,
703
+ rightAction: [],
704
+ leftAction: [],
705
+ actionBackground: Colors.white,
654
706
  };
655
707
 
656
708
  export default SwipeAction;