UNPKG

react-native-swipeout-component

Version:

`React Native Swipeout` is a customizable swipeable row component for React Native, allowing you to create swipe actions like deleting, archiving, or other actions you might want to trigger with a swipe.

479 lines (473 loc) 15.5 kB
var __defProp = Object.defineProperty; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; // src/SwipeoutComponent.tsx import React2, { Component } from "react"; import { PanResponder, View as View2, StyleSheet as StyleSheet2 } from "react-native"; import PropTypes from "prop-types"; // src/SwipeoutBtn.tsx import React from "react"; import { View, Text, TouchableOpacity, StyleSheet } from "react-native"; var SwipeoutBtn = ({ backgroundColor, color, component, onPress, text, type, underlayColor, height = 0, width = 0, disabled = false }) => { const styleSwipeoutBtn = [styles.swipeoutBtn]; if (type === "delete") styleSwipeoutBtn.push(styles.colorDelete); else if (type === "primary") styleSwipeoutBtn.push(styles.colorPrimary); else if (type === "secondary") styleSwipeoutBtn.push(styles.colorSecondary); if (backgroundColor) styleSwipeoutBtn.push({ backgroundColor }); styleSwipeoutBtn.push({ height, width }); const styleSwipeoutBtnComponent = [{ height, width }]; const styleSwipeoutBtnText = [styles.swipeoutBtnText]; if (color) styleSwipeoutBtnText.push({ color }); return /* @__PURE__ */ React.createElement( TouchableOpacity, { onPress, disabled, style: [styles.swipeoutBtnTouchable, styleSwipeoutBtn], activeOpacity: underlayColor ? 1 : 0.85, underlayColor }, component ? /* @__PURE__ */ React.createElement(View, { style: styleSwipeoutBtnComponent }, component) : /* @__PURE__ */ React.createElement(Text, { style: styleSwipeoutBtnText }, text) ); }; var styles = StyleSheet.create({ swipeoutBtn: { justifyContent: "center", alignItems: "center" }, swipeoutBtnText: { color: "#ffffff", alignSelf: "center" }, swipeoutBtnTouchable: { flex: 1, justifyContent: "center", alignItems: "center" }, colorDelete: { backgroundColor: "red" }, colorPrimary: { backgroundColor: "blue" }, colorSecondary: { backgroundColor: "gray" } }); var SwipeoutBtn_default = SwipeoutBtn; // src/SwipeoutComponent.tsx import { debounce } from "lodash"; var Swipeout = class extends Component { constructor(props) { super(props); this.swipeoutContent = null; this._handleSwipe = () => { if (this.props.autoOpenRight && !this.props.disabled && this.props.buttonWidth) { this._autoOpenRight(); } if (this.props.autoOpenLeft && !this.props.disabled && this.props.buttonWidth) { this._autoOpenLeft(); } }; /** * _autoOpenBoth = () => { this._autoOpenRight() setTimeout(() => { this.setState({ contentPos: 0, btnsLeftWidth: 0 }); }, this.props.timeout || 2000) this._autoOpenLeft() } */ this._autoOpenRight = () => { setTimeout(() => { this.setState({ contentPos: -this.props.buttonWidth / 2, btnsRightWidth: this.props.buttonWidth / 2 }); }, 500); setTimeout(() => { this.setState({ contentPos: 0, btnsRightWidth: 0 }); }, this.props.timeout || 2e3); }; this._autoOpenLeft = () => { setTimeout(() => { this.setState({ contentPos: this.props.buttonWidth / 2, btnsLeftWidth: this.props.left ? this.props.buttonWidth / 2 * this.props.left.length : 0 }); }, 500); setTimeout(() => { this.setState({ contentPos: 0, btnsLeftWidth: 0 }); }, this.props.timeout || 2e3); }; this._debouncedOnCTA = debounce(() => this.props.onCTA(), 300); this._handlePanResponderGrant = () => { var _a; if (this.props.disabled) return; if (!this.state.openedLeft && !this.state.openedRight) { this._callOnOpen(); } else { this._callOnClose(); } (_a = this.swipeoutContent) == null ? void 0 : _a.measure((ox, oy, width) => { let buttonWidth = width / 5; this.setState({ btnWidth: buttonWidth, btnsLeftWidth: this.props.left ? buttonWidth * this.props.left.length : 0, btnsRightWidth: this.props.right ? buttonWidth * this.props.right.length : 0, swiping: true, timeStart: (/* @__PURE__ */ new Date()).getTime() }); }); }; this._handlePanResponderMove = (event, gestureState) => { if (this.props.disabled) return; let posX = gestureState.dx; let posY = gestureState.dy; const leftWidth = this.state.btnsLeftWidth; const rightWidth = this.state.btnsRightWidth; if (this.state.openedRight) posX = gestureState.dx - rightWidth; else if (this.state.openedLeft) posX = gestureState.dx + leftWidth; const moveX = Math.abs(posX) > Math.abs(posY); if (this.props.scroll) { if (moveX) this.props.scroll(false); else this.props.scroll(true); } if (this.state.swiping) { if (posX < 0 && this.props.right) { this.setState({ backgroundColor: this.props.rightBackgroundColor }); if (this.props.dragToCTA && this.props.right.length == 1 && -(rightWidth + 150) >= Math.round(Math.min(posX, 0))) { return this._debouncedOnCTA(); } this.setState({ contentPos: Math.min(posX, 0) }); } else if (posX > 0 && this.props.left) { this.setState({ backgroundColor: this.props.leftBackgroundColor }); if (this.props.dragToCTA && this.props.left.length == 1 && leftWidth + 70 <= Math.round(Math.max(posX, 0))) { return this._debouncedOnCTA(); } this.setState({ contentPos: Math.max(posX, 0) }); } } }; this._handlePanResponderEnd = (event, gestureState) => { if (this.props.disabled) return; const posX = gestureState.dx; const contentPos = this.state.contentPos; const contentWidth = this.state.contentWidth; const btnsLeftWidth = this.state.btnsLeftWidth; const btnsRightWidth = this.state.btnsRightWidth; const openX = contentWidth * 0.33; let openLeft = posX > openX || posX > btnsLeftWidth / 2; let openRight = posX < -openX || posX < -btnsRightWidth / 2; if (this.state.openedRight) openRight = posX - openX < -openX; if (this.state.openedLeft) openLeft = posX + openX > openX; const timeDiff = (/* @__PURE__ */ new Date()).getTime() - this.state.timeStart < 200; if (timeDiff) { openRight = posX < -openX / 10 && !this.state.openedLeft; openLeft = posX > openX / 10 && !this.state.openedRight; } if (this.state.swiping) { if (openRight && contentPos < 0 && posX < 0) { this._open(-btnsRightWidth, "right"); } else if (openLeft && contentPos > 0 && posX > 0) { this._open(btnsLeftWidth, "left"); } else { this._close(); } } if (this.props.scroll) this.props.scroll(true); }; this._tweenContent = (state, endValue) => { this.setState({ [state]: endValue }); }; this._rubberBandEasing = (value, limit) => { if (value < 0 && value < limit) return limit - Math.pow(limit - value, 0.85); else if (value > 0 && value > limit) return limit + Math.pow(value - limit, 0.85); return value; }; this._autoClose = (btn) => { if (this.state.autoClose) this._close(); const { onPress } = btn; if (onPress) onPress(); }; this._open = (contentPos, direction) => { const left = direction === "left"; const { sectionID, rowID, onOpen } = this.props; onOpen && onOpen(sectionID, rowID, direction); this._tweenContent("contentPos", contentPos); this.setState({ contentPos, openedLeft: left, openedRight: !left, swiping: false }); }; this._close = () => { const { sectionID, rowID, onClose } = this.props; if (onClose && (this.state.openedLeft || this.state.openedRight)) { const direction = this.state.openedRight ? "right" : "left"; onClose(sectionID, rowID, direction); } this._tweenContent("contentPos", 0); this._callOnClose(); this.setState({ openedRight: false, openedLeft: false, swiping: false }); }; this._callOnClose = () => { if (this.props.onClose) this.props.onClose(this.props.sectionID, this.props.rowID); }; this._callOnOpen = () => { if (this.props.onOpen) this.props.onOpen(this.props.sectionID, this.props.rowID); }; this._openRight = () => { var _a; (_a = this.swipeoutContent) == null ? void 0 : _a.measure((ox, oy, width) => { let btnWidth = width / 5; this.setState({ btnWidth, btnsRightWidth: this.props.right ? btnWidth * this.props.right.length : 0 }, () => { this._tweenContent("contentPos", -this.state.btnsRightWidth); this._callOnOpen(); this.setState({ contentPos: -this.state.btnsRightWidth, openedLeft: false, openedRight: true, swiping: false }); }); }); }; this._openLeft = () => { var _a; (_a = this.swipeoutContent) == null ? void 0 : _a.measure((ox, oy, width) => { let btnWidth = width / 5; this.setState({ btnWidth, btnsLeftWidth: this.props.left ? btnWidth * this.props.left.length : 0 }, () => { this._tweenContent("contentPos", this.state.btnsLeftWidth); this._callOnOpen(); this.setState({ contentPos: this.state.btnsLeftWidth, openedLeft: true, openedRight: false, swiping: false }); }); }); }; this._onLayout = (event) => { const { width, height } = event.nativeEvent.layout; this.setState({ contentWidth: width, contentHeight: height }); }; this._renderButtons = (buttons, isVisible, style) => { if (buttons && isVisible) { return /* @__PURE__ */ React2.createElement(View2, { style }, buttons.map(this._renderButton)); } else { return /* @__PURE__ */ React2.createElement(View2, null); } }; this._renderButton = (btn, i) => { return /* @__PURE__ */ React2.createElement( SwipeoutBtn_default, { backgroundColor: btn.buttonBackgroundColor, color: btn.color, component: btn.component, disabled: btn.disabled, height: this.state.contentHeight, key: i, onPress: () => this._autoClose(btn), text: btn.text, type: btn.type, underlayColor: btn.underlayColor, width: this.state.btnWidth } ); }; this.state = { autoClose: this.props.autoClose || false, btnWidth: 0, btnsLeftWidth: 0, btnsRightWidth: 0, contentHeight: 0, contentPos: 0, contentWidth: 0, openedRight: false, openedLeft: false, swiping: false, tweenDuration: 160, timeStart: null, backgroundColor: "" }; this._initializePanResponder(); } componentDidMount() { this._initializePanResponder(); if (this.props.autoOpenRight || this.props.autoOpenLeft) { this._handleSwipe(); } } _initializePanResponder() { this._panResponder = PanResponder.create({ onStartShouldSetPanResponder: () => true, onStartShouldSetPanResponderCapture: () => this.state.openedLeft || this.state.openedRight, onMoveShouldSetPanResponderCapture: (event, gestureState) => Math.abs(gestureState.dx) > this.props.sensitivity && Math.abs(gestureState.dy) <= this.props.sensitivity, onPanResponderGrant: this._handlePanResponderGrant, onPanResponderMove: this._handlePanResponderMove, onPanResponderRelease: this._handlePanResponderEnd, onPanResponderTerminate: this._handlePanResponderEnd, onShouldBlockNativeResponder: () => false, onPanResponderTerminationRequest: () => false }); } render() { const { style, children, right, left } = this.props; const contentWidth = this.state.contentWidth; const height = this.state.contentHeight; const posX = this.state.contentPos; const styleSwipeout = [styles2.swipeout, style]; if (this.state.backgroundColor) { styleSwipeout.push({ backgroundColor: this.state.backgroundColor }); } let limit = -this.state.btnsRightWidth; if (posX > 0) limit = this.state.btnsLeftWidth; const styleLeftPos = { left: { left: 0, overflow: "hidden", width: Math.min(limit * (posX / limit), limit) } }; const styleRightPos = { right: { left: Math.abs(contentWidth + Math.max(limit, posX)), right: 0 } }; const styleContentPos = { content: { transform: [{ translateX: this._rubberBandEasing(posX, limit) }] } }; const styleContent = [styles2.swipeoutContent]; styleContent.push(styleContentPos.content); const styleRight = [styles2.swipeoutBtns]; styleRight.push(styleRightPos.right); const styleLeft = [styles2.swipeoutBtns]; styleLeft.push(styleLeftPos.left); const isRightVisible = posX < 0; const isLeftVisible = posX > 0; return /* @__PURE__ */ React2.createElement( View2, { style: styleSwipeout }, /* @__PURE__ */ React2.createElement( View2, __spreadValues({ ref: (node) => this.swipeoutContent = node, style: [styleContent], onLayout: this._onLayout }, this._panResponder.panHandlers), children ), //@ts-ignore this._renderButtons(right, isRightVisible, styleRight), //@ts-ignore this._renderButtons(left, isLeftVisible, styleLeft) ); } }; Swipeout.propTypes = { autoClose: PropTypes.bool, buttonBackgroundColor: PropTypes.string, close: PropTypes.bool, left: PropTypes.array, onOpen: PropTypes.func, onClose: PropTypes.func, right: PropTypes.array, scroll: PropTypes.func, style: PropTypes.object, sensitivity: PropTypes.number, buttonWidth: PropTypes.number, disabled: PropTypes.bool, autoOpenRight: PropTypes.bool, onCTA: PropTypes.func }; Swipeout.defaultProps = { disabled: false, rowID: -1, sectionID: -1, sensitivity: 50 }; var styles2 = StyleSheet2.create({ swipeout: { overflow: "hidden" }, swipeoutContent: { flex: 1 }, swipeoutBtns: { position: "absolute", top: 0, bottom: 0, flexDirection: "row" } }); var SwipeoutComponent_default = Swipeout; export { SwipeoutComponent_default as Swipeout };