@chayns-components/swipeable-wrapper
Version:
A set of beautiful React components for developing your own applications with chayns.
201 lines (195 loc) • 8.71 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _chaynsApi = require("chayns-api");
var _react = require("motion/react");
var _react2 = _interopRequireWildcard(require("react"));
var _threshold = require("../../utils/threshold");
var _SwipeableAction = _interopRequireWildcard(require("./swipeable-action/SwipeableAction"));
var _SwipeableWrapper = require("./SwipeableWrapper.styles");
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
const SwipeableWrapper = ({
children,
leftActions = [],
rightActions = [],
shouldUseOpacityAnimation,
isDisabled = false,
onSwipeEnd,
onSwipeStart
}) => {
const [leftThreshold, setLeftThreshold] = (0, _react2.useState)((0, _threshold.calcThreshold)({
actionCount: leftActions.length,
direction: 'left',
width: window.innerWidth
}));
const [rightThreshold, setRightThreshold] = (0, _react2.useState)((0, _threshold.calcThreshold)({
actionCount: rightActions.length,
direction: 'right',
width: window.innerWidth
}));
const swipeableWrapperRef = (0, _react2.useRef)(null);
const isSwipingRef = (0, _react2.useRef)(false);
const listItemXOffset = (0, _react.useMotionValue)(0);
const close = (0, _react2.useCallback)(() => {
void (0, _react.animate)(listItemXOffset, 0);
}, [listItemXOffset]);
const open = (0, _react2.useCallback)(direction => {
switch (direction) {
case 'left':
void (0, _react.animate)(listItemXOffset, _SwipeableAction.SWIPEABLE_ACTION_WIDTH * leftActions.length);
break;
case 'right':
void (0, _react.animate)(listItemXOffset, -_SwipeableAction.SWIPEABLE_ACTION_WIDTH * rightActions.length);
break;
default:
break;
}
}, [leftActions.length, listItemXOffset, rightActions.length]);
(0, _react2.useEffect)(() => {
var _swipeableWrapperRef$;
const width = (_swipeableWrapperRef$ = swipeableWrapperRef.current) === null || _swipeableWrapperRef$ === void 0 || (_swipeableWrapperRef$ = _swipeableWrapperRef$.parentElement) === null || _swipeableWrapperRef$ === void 0 ? void 0 : _swipeableWrapperRef$.offsetWidth;
// This check was deliberately chosen because a width of 0 is also not permitted.
if (width) {
setLeftThreshold((0, _threshold.calcThreshold)({
actionCount: leftActions.length,
direction: 'left',
width
}));
setRightThreshold((0, _threshold.calcThreshold)({
actionCount: rightActions.length,
direction: 'right',
width
}));
}
}, [leftActions.length, rightActions.length]);
// Close an opened menu when anything outside it is tapped
(0, _react2.useEffect)(() => {
function closeCallback(event) {
var _swipeableWrapperRef$2;
const eventTarget = event.target;
// @ts-expect-error: Pretty sure that the event target is always a Node.
if (eventTarget && !((_swipeableWrapperRef$2 = swipeableWrapperRef.current) !== null && _swipeableWrapperRef$2 !== void 0 && _swipeableWrapperRef$2.contains(eventTarget))) {
close();
}
}
document.addEventListener('mousedown', closeCallback);
document.addEventListener('touchstart', closeCallback);
return () => {
document.removeEventListener('mousedown', closeCallback);
document.removeEventListener('touchstart', closeCallback);
};
}, [close]);
// Vibrate when the threshold is passed
(0, _react2.useEffect)(() => listItemXOffset.on('change', newValue => {
const previous = listItemXOffset.getPrevious();
if (!previous) {
return;
}
const hasCrossedLeftThreshold = previous < leftThreshold && newValue >= leftThreshold || previous > leftThreshold && newValue <= leftThreshold;
const hasCrossedRightThreshold = previous < rightThreshold && newValue >= rightThreshold || previous > rightThreshold && newValue <= rightThreshold;
if (hasCrossedLeftThreshold || hasCrossedRightThreshold) {
void (0, _chaynsApi.vibrate)({
iOSFeedbackVibration: 6,
pattern: [150]
});
}
}), [leftThreshold, listItemXOffset, rightThreshold]);
const handlePan = (0, _react2.useCallback)((_, info) => {
if (!isSwipingRef.current) {
isSwipingRef.current = true;
if (typeof onSwipeStart === 'function') {
onSwipeStart();
}
}
const currentXOffset = listItemXOffset.get();
const dampingFactor = info.offset.x > 0 && leftActions.length > 0 || info.offset.x < 0 && rightActions.length > 0 || currentXOffset > 0 && info.delta.x < 0 || currentXOffset < 0 && info.delta.x > 0 ? 1 : 0.75 / (Math.abs(info.offset.x) / 9);
if (Math.abs(info.offset.x) > 30 || currentXOffset > 0) {
listItemXOffset.set(currentXOffset + info.delta.x * dampingFactor);
}
}, [leftActions.length, listItemXOffset, onSwipeStart, rightActions.length]);
const handlePanEnd = (0, _react2.useCallback)(() => {
if (typeof onSwipeEnd === 'function') {
onSwipeEnd();
}
isSwipingRef.current = false;
const offset = listItemXOffset.get();
if (offset > leftThreshold) {
var _leftActions$;
(_leftActions$ = leftActions[0]) === null || _leftActions$ === void 0 || _leftActions$.action();
close();
} else if (offset < rightThreshold) {
var _rightActions;
(_rightActions = rightActions[rightActions.length - 1]) === null || _rightActions === void 0 || _rightActions.action();
close();
} else {
let state;
if (offset > 2) {
state = 'left-open';
} else if (offset < -2) {
state = 'right-open';
} else {
state = 'closed';
}
// eslint-disable-next-line default-case
switch (state) {
case 'left-open':
if (offset < _SwipeableAction.SWIPEABLE_ACTION_WIDTH) {
close();
} else {
open('left');
}
break;
case 'right-open':
if (offset > -_SwipeableAction.SWIPEABLE_ACTION_WIDTH) {
close();
} else {
open('right');
}
break;
case 'closed':
if (offset > _SwipeableAction.SWIPEABLE_ACTION_WIDTH) {
open('left');
} else if (offset < -_SwipeableAction.SWIPEABLE_ACTION_WIDTH) {
open('right');
} else {
close();
}
}
}
}, [close, leftActions, leftThreshold, listItemXOffset, onSwipeEnd, open, rightActions, rightThreshold]);
const leftActionElements = (0, _react2.useMemo)(() => Array.from(leftActions).reverse().map((item, index) => /*#__PURE__*/_react2.default.createElement(_SwipeableAction.default, {
activationThreshold: leftThreshold,
close: close,
index: index,
item: item,
key: item.key,
listItemXOffset: listItemXOffset,
position: "left",
shouldUseOpacityAnimation: shouldUseOpacityAnimation,
totalActionCount: leftActions.length
})), [close, leftActions, leftThreshold, listItemXOffset, shouldUseOpacityAnimation]);
const rightActionElements = (0, _react2.useMemo)(() => rightActions.map((item, index) => /*#__PURE__*/_react2.default.createElement(_SwipeableAction.default, {
activationThreshold: rightThreshold,
close: close,
index: index,
item: item,
key: item.key,
listItemXOffset: listItemXOffset,
position: "right",
shouldUseOpacityAnimation: shouldUseOpacityAnimation,
totalActionCount: rightActions.length
})), [rightActions, rightThreshold, close, listItemXOffset, shouldUseOpacityAnimation]);
return /*#__PURE__*/_react2.default.createElement(_SwipeableWrapper.StyledMotionSwipeableWrapper, {
onPan: isDisabled ? undefined : handlePan,
onPanEnd: isDisabled ? undefined : handlePanEnd,
ref: swipeableWrapperRef,
style: {
x: listItemXOffset
}
}, leftActionElements, /*#__PURE__*/_react2.default.createElement(_SwipeableWrapper.StyledSwipeableWrapperContent, null, children), rightActionElements);
};
SwipeableWrapper.displayName = 'SwipeableWrapper';
var _default = exports.default = SwipeableWrapper;
//# sourceMappingURL=SwipeableWrapper.js.map