react-swipe-action 0.0.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/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # React swipe action [![npm](https://img.shields.io/npm/v/react-swipe-action.svg)](https://www.npmjs.com/package/react-swipe-action) ![npm type definitions](https://img.shields.io/npm/types/react-swipe-action.svg)
2
+
3
+ ## Installation
4
+
5
+ ```bash
6
+ npm install react-swipe-action
7
+ ```
8
+
9
+ ## How to use
10
+
11
+ ```jsx
12
+ import { SwipeAction } from 'react-swipe-action'
13
+ import 'react-swipe-action/dist/index.css'
14
+
15
+ const App = () => {
16
+ return (
17
+ <SwipeAction
18
+ main={(handle) => <button onClick={() => { alert('Click') }} style={{ position: 'relative' }}>
19
+ Button
20
+ {handle}
21
+ </button>}
22
+ endAction={{
23
+ content: (
24
+ <button
25
+ type="button"
26
+ onClick={() => { alert('Right action') }}
27
+ >
28
+ Right action
29
+ </button>
30
+ ),
31
+ onLongSwipe: () => { alert('Right action') },
32
+ background: <div style={{ backgroundColor: 'red' }} />,
33
+ }}
34
+ />
35
+ )
36
+ }
37
+ ```
@@ -0,0 +1,26 @@
1
+ import type { FunctionComponent, ReactNode, RefObject } from 'react';
2
+ type OnLongSwipe = () => void;
3
+ type Content = ReactNode;
4
+ type Action = {
5
+ background?: ReactNode;
6
+ content?: Content;
7
+ onLongSwipe?: OnLongSwipe;
8
+ } & ({
9
+ content: Content;
10
+ } | {
11
+ onLongSwipe: OnLongSwipe;
12
+ });
13
+ type SwipeActionProps = {
14
+ main: (handle: ReactNode) => ReactNode;
15
+ startAction?: Action;
16
+ endAction?: Action;
17
+ };
18
+ export declare const SwipeAction: FunctionComponent<SwipeActionProps>;
19
+ type Position = 'start' | 'end';
20
+ declare const Action: FunctionComponent<{
21
+ position: Position;
22
+ content: ReactNode;
23
+ background: ReactNode;
24
+ contentRef: RefObject<HTMLDivElement>;
25
+ }>;
26
+ export {};
@@ -0,0 +1,82 @@
1
+ "use client";
2
+ import { __assign } from './_virtual/_tslib.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import { useState, useCallback, useRef, useMemo } from 'react';
5
+ import { useDrag } from 'react-use-drag';
6
+ import styles from './SwipeAction.module.css.js';
7
+
8
+ var SwipeAction = function (_a) {
9
+ var startAction = _a.startAction, endAction = _a.endAction, main = _a.main;
10
+ var _b = useState(0), position = _b[0], setPosition = _b[1];
11
+ var _c = useState(0), positionOffset = _c[0], setPositionOffset = _c[1];
12
+ var _d = useState(false), isSwiping = _d[0], setIsSwiping = _d[1];
13
+ var onRelativePositionChange = useCallback(function (x) {
14
+ if (Math.abs(x) > 5) {
15
+ setIsSwiping(true);
16
+ }
17
+ setPositionOffset(x);
18
+ }, []);
19
+ var onEnd = useCallback(function (x) {
20
+ if (x === 0) {
21
+ setIsSwiping(false);
22
+ }
23
+ else {
24
+ setPosition(function (position) {
25
+ var _a, _b, _c, _d;
26
+ var newPosition = position + x;
27
+ var mainWidth = (_b = (_a = mainRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) !== null && _b !== void 0 ? _b : 0;
28
+ var handleContent = function (ref, position, onLongSwipe) {
29
+ var _a;
30
+ var contentWidth = (_a = ref.current) === null || _a === void 0 ? void 0 : _a.offsetWidth;
31
+ var positionSign = position === 'start' ? 1 : -1;
32
+ var normalizedSwipePosition = positionSign * newPosition;
33
+ if (onLongSwipe &&
34
+ contentWidth !== undefined &&
35
+ normalizedSwipePosition >
36
+ Math.max(mainWidth / 4, contentWidth) * 1.6) {
37
+ onLongSwipe();
38
+ return positionSign * mainWidth;
39
+ }
40
+ if (contentWidth !== 0 &&
41
+ contentWidth !== undefined &&
42
+ normalizedSwipePosition > contentWidth * 0.7) {
43
+ return positionSign * contentWidth;
44
+ }
45
+ if (Math.sign(newPosition) === positionSign) {
46
+ setTimeout(function () {
47
+ setIsSwiping(false);
48
+ }, 200); // Delay to ignore immediate click
49
+ return 0;
50
+ }
51
+ };
52
+ return ((_d = (_c = handleContent(startActionContentRef, 'start', startAction === null || startAction === void 0 ? void 0 : startAction.onLongSwipe)) !== null && _c !== void 0 ? _c : handleContent(endActionContentRef, 'end', endAction === null || endAction === void 0 ? void 0 : endAction.onLongSwipe)) !== null && _d !== void 0 ? _d : newPosition);
53
+ });
54
+ }
55
+ setPositionOffset(0);
56
+ }, [startAction === null || startAction === void 0 ? void 0 : startAction.onLongSwipe, endAction === null || endAction === void 0 ? void 0 : endAction.onLongSwipe]);
57
+ var elementProps = useDrag({
58
+ onRelativePositionChange: onRelativePositionChange,
59
+ onEnd: onEnd,
60
+ }).elementProps;
61
+ var mainRef = useRef(null);
62
+ var startActionContentRef = useRef(null);
63
+ var endActionContentRef = useRef(null);
64
+ var x = useMemo(function () {
65
+ return Math.max(endAction ? Number.NEGATIVE_INFINITY : 0, Math.min(startAction ? Number.POSITIVE_INFINITY : 0, position + positionOffset));
66
+ }, [position, positionOffset, startAction, endAction]);
67
+ return (jsxs("div", { className: styles.wrapper, children: [startAction && x > 0 && (jsx(Action, { position: "start", content: startAction.content, background: startAction.background, contentRef: startActionContentRef })), endAction && x < 0 && (jsx(Action, { position: "end", content: endAction.content, background: endAction.background, contentRef: endActionContentRef })), jsx("div", { className: styles.main, ref: mainRef, style: {
68
+ '--x': "".concat(x, "px"),
69
+ }, children: main(jsx("div", __assign({ className: styles.handle }, elementProps, { onClick: function (event) {
70
+ if (isSwiping) {
71
+ event.stopPropagation();
72
+ }
73
+ } }))) })] }));
74
+ };
75
+ var Action = function (_a) {
76
+ var content = _a.content, background = _a.background, position = _a.position, contentRef = _a.contentRef;
77
+ // @TODO: allow focus by tab key before visible
78
+ return (jsxs("div", { className: "".concat(styles.action, " ").concat(styles["is_position_".concat(position)]), children: [jsx("div", { className: styles.action_background, children: background }), jsx("div", { className: styles.action_content, ref: contentRef, children: content })] }));
79
+ };
80
+
81
+ export { SwipeAction };
82
+ //# sourceMappingURL=SwipeAction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SwipeAction.js","sources":["../src/SwipeAction.tsx"],"sourcesContent":["'use client'\n\nimport type {\n\tCSSProperties,\n\tFunctionComponent,\n\tReactNode,\n\tRefObject,\n} from 'react'\nimport { useCallback, useMemo, useRef, useState } from 'react'\nimport { useDrag } from 'react-use-drag'\nimport styles from './SwipeAction.module.css'\n\n// @TODO: add inertia\n// @TODO: animate snapping\n// @TODO: add threshold - maybe to react-use-drag\n\ntype OnLongSwipe = () => void\ntype Content = ReactNode\n\ntype Action = {\n\tbackground?: ReactNode\n\tcontent?: Content\n\tonLongSwipe?: OnLongSwipe\n} & (\n\t\t| {\n\t\t\tcontent: Content\n\t\t}\n\t\t| {\n\t\t\tonLongSwipe: OnLongSwipe\n\t\t}\n\t)\n\ntype SwipeActionProps = {\n\tmain: (handle: ReactNode) => ReactNode\n\tstartAction?: Action\n\tendAction?: Action\n}\n\nexport const SwipeAction: FunctionComponent<SwipeActionProps> = ({\n\tstartAction,\n\tendAction,\n\tmain,\n}) => {\n\tconst [position, setPosition] = useState(0)\n\tconst [positionOffset, setPositionOffset] = useState(0)\n\tconst [isSwiping, setIsSwiping] = useState(false)\n\tconst onRelativePositionChange = useCallback((x: number) => {\n\t\tif (Math.abs(x) > 5) {\n\t\t\tsetIsSwiping(true)\n\t\t}\n\t\tsetPositionOffset(x)\n\t}, [])\n\tconst onEnd = useCallback(\n\t\t(x: number) => {\n\t\t\tif (x === 0) {\n\t\t\t\tsetIsSwiping(false)\n\t\t\t} else {\n\t\t\t\tsetPosition((position) => {\n\t\t\t\t\tconst newPosition = position + x\n\t\t\t\t\tconst mainWidth = mainRef.current?.offsetWidth ?? 0\n\n\t\t\t\t\tconst handleContent = (\n\t\t\t\t\t\tref: RefObject<HTMLDivElement>,\n\t\t\t\t\t\tposition: Position,\n\t\t\t\t\t\tonLongSwipe: undefined | OnLongSwipe,\n\t\t\t\t\t) => {\n\t\t\t\t\t\tconst contentWidth = ref.current?.offsetWidth\n\t\t\t\t\t\tconst positionSign = position === 'start' ? 1 : -1\n\t\t\t\t\t\tconst normalizedSwipePosition = positionSign * newPosition\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tonLongSwipe &&\n\t\t\t\t\t\t\tcontentWidth !== undefined &&\n\t\t\t\t\t\t\tnormalizedSwipePosition >\n\t\t\t\t\t\t\tMath.max(mainWidth / 4, contentWidth) * 1.6\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tonLongSwipe()\n\t\t\t\t\t\t\treturn positionSign * mainWidth\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tcontentWidth !== 0 &&\n\t\t\t\t\t\t\tcontentWidth !== undefined &&\n\t\t\t\t\t\t\tnormalizedSwipePosition > contentWidth * 0.7\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treturn positionSign * contentWidth\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (Math.sign(newPosition) === positionSign) {\n\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\tsetIsSwiping(false)\n\t\t\t\t\t\t\t}, 200) // Delay to ignore immediate click\n\t\t\t\t\t\t\treturn 0\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn (\n\t\t\t\t\t\thandleContent(\n\t\t\t\t\t\t\tstartActionContentRef,\n\t\t\t\t\t\t\t'start',\n\t\t\t\t\t\t\tstartAction?.onLongSwipe,\n\t\t\t\t\t\t) ??\n\t\t\t\t\t\thandleContent(endActionContentRef, 'end', endAction?.onLongSwipe) ??\n\t\t\t\t\t\tnewPosition\n\t\t\t\t\t)\n\t\t\t\t})\n\t\t\t}\n\t\t\tsetPositionOffset(0)\n\t\t},\n\t\t[startAction?.onLongSwipe, endAction?.onLongSwipe],\n\t)\n\tconst { elementProps } = useDrag({\n\t\tonRelativePositionChange,\n\t\tonEnd,\n\t})\n\tconst mainRef = useRef<HTMLDivElement>(null)\n\tconst startActionContentRef = useRef<HTMLDivElement>(null)\n\tconst endActionContentRef = useRef<HTMLDivElement>(null)\n\n\tconst x = useMemo(\n\t\t() =>\n\t\t\tMath.max(\n\t\t\t\tendAction ? Number.NEGATIVE_INFINITY : 0,\n\t\t\t\tMath.min(\n\t\t\t\t\tstartAction ? Number.POSITIVE_INFINITY : 0,\n\t\t\t\t\tposition + positionOffset,\n\t\t\t\t),\n\t\t\t),\n\t\t[position, positionOffset, startAction, endAction],\n\t)\n\n\treturn (\n\t\t<div className={styles.wrapper}>\n\t\t\t{startAction && x > 0 && (\n\t\t\t\t<Action\n\t\t\t\t\tposition=\"start\"\n\t\t\t\t\tcontent={startAction.content}\n\t\t\t\t\tbackground={startAction.background}\n\t\t\t\t\tcontentRef={startActionContentRef}\n\t\t\t\t/>\n\t\t\t)}\n\t\t\t{endAction && x < 0 && (\n\t\t\t\t<Action\n\t\t\t\t\tposition=\"end\"\n\t\t\t\t\tcontent={endAction.content}\n\t\t\t\t\tbackground={endAction.background}\n\t\t\t\t\tcontentRef={endActionContentRef}\n\t\t\t\t/>\n\t\t\t)}\n\t\t\t<div\n\t\t\t\tclassName={styles.main}\n\t\t\t\tref={mainRef}\n\t\t\t\tstyle={\n\t\t\t\t\t{\n\t\t\t\t\t\t'--x': `${x}px`,\n\t\t\t\t\t} as CSSProperties\n\t\t\t\t}\n\t\t\t>\n\t\t\t\t{main(\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName={styles.handle}\n\t\t\t\t\t\t{...elementProps}\n\t\t\t\t\t\tonClick={(event) => {\n\t\t\t\t\t\t\tif (isSwiping) {\n\t\t\t\t\t\t\t\tevent.stopPropagation()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\ntype Position = 'start' | 'end'\n\nconst Action: FunctionComponent<{\n\tposition: Position\n\tcontent: ReactNode\n\tbackground: ReactNode\n\tcontentRef: RefObject<HTMLDivElement>\n}> = ({ content, background, position, contentRef }) => {\n\t// @TODO: allow focus by tab key before visible\n\treturn (\n\t\t<div className={`${styles.action} ${styles[`is_position_${position}`]}`}>\n\t\t\t<div className={styles.action_background}>{background}</div>\n\t\t\t<div className={styles.action_content} ref={contentRef}>\n\t\t\t\t{content}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"],"names":[],"mappings":";;;;;;;AAsCO;AACN;;;;AAOA;;;;;;AAMA;AAEE;;;;;;AAIE;;AAGA;;;AAMC;AACA;AACA;AAEC;;AAEA;AAEA;;;;AAKA;AACA;;;;AAKA;;AAEA;AACA;;AAEF;AAEA;AASD;;;AAGF;;AAIA;AACA;AACA;AACD;AACA;AACA;;AAIE;;AAUF;;AAwBsB;;;;AAWjB;AAMN;AAIA;;;AAOC;AAQD;;"}
@@ -0,0 +1,4 @@
1
+ var styles = {"wrapper":"SwipeAction-module_wrapper__mMOfu","main":"SwipeAction-module_main__IyTI6","action":"SwipeAction-module_action__vnzRV","is_position_start":"SwipeAction-module_is_position_start__225Dj","is_position_end":"SwipeAction-module_is_position_end__I5n3C","action_content":"SwipeAction-module_action_content__9InP7","action_background":"SwipeAction-module_action_background__7lqwm","handle":"SwipeAction-module_handle__kuDx1"};
2
+
3
+ export { styles as default };
4
+ //# sourceMappingURL=SwipeAction.module.css.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SwipeAction.module.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
@@ -0,0 +1,12 @@
1
+ import type { StoryObj } from '@storybook/react';
2
+ import './SwipeAction.stories.css';
3
+ declare const meta: {
4
+ title: string;
5
+ tags: string[];
6
+ parameters: {
7
+ layout: string;
8
+ };
9
+ };
10
+ export default meta;
11
+ type Story = StoryObj<typeof meta>;
12
+ export declare const All: Story;
@@ -0,0 +1,35 @@
1
+ /******************************************************************************
2
+ Copyright (c) Microsoft Corporation.
3
+
4
+ Permission to use, copy, modify, and/or distribute this software for any
5
+ purpose with or without fee is hereby granted.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13
+ PERFORMANCE OF THIS SOFTWARE.
14
+ ***************************************************************************** */
15
+ /* global Reflect, Promise, SuppressedError, Symbol */
16
+
17
+
18
+ var __assign = function() {
19
+ __assign = Object.assign || function __assign(t) {
20
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
21
+ s = arguments[i];
22
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
23
+ }
24
+ return t;
25
+ };
26
+ return __assign.apply(this, arguments);
27
+ };
28
+
29
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
30
+ var e = new Error(message);
31
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
32
+ };
33
+
34
+ export { __assign };
35
+ //# sourceMappingURL=_tslib.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_tslib.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.css ADDED
@@ -0,0 +1,41 @@
1
+ .SwipeAction-module_wrapper__mMOfu {
2
+ isolation: isolate;
3
+ position: relative;
4
+ }
5
+
6
+ .SwipeAction-module_main__IyTI6 {
7
+ position: relative;
8
+ translate: var(--x, 0) 0;
9
+ }
10
+
11
+ .SwipeAction-module_action__vnzRV {
12
+ display: flex;
13
+ position: absolute;
14
+ inset: 0;
15
+ align-items: stretch;
16
+ }
17
+
18
+ .SwipeAction-module_action__vnzRV.SwipeAction-module_is_position_start__225Dj {
19
+ justify-content: flex-start;
20
+ }
21
+
22
+ .SwipeAction-module_action__vnzRV.SwipeAction-module_is_position_end__I5n3C {
23
+ justify-content: flex-end;
24
+ }
25
+
26
+ .SwipeAction-module_action_content__9InP7 {
27
+ display: grid;
28
+ }
29
+
30
+ .SwipeAction-module_action_background__7lqwm {
31
+ position: absolute;
32
+ inset: 0;
33
+ display: grid;
34
+ z-index: -1;
35
+ }
36
+
37
+ .SwipeAction-module_handle__kuDx1 {
38
+ position: absolute;
39
+ inset: 0;
40
+ touch-action: pan-y;
41
+ }
@@ -0,0 +1 @@
1
+ export * from './SwipeAction';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { SwipeAction } from './SwipeAction.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "react-swipe-action",
3
+ "version": "0.0.1",
4
+ "description": "",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "scripts": {
9
+ "start": "rollup -c -w",
10
+ "build": "rollup -c",
11
+ "prepare": "npm run build",
12
+ "storybook": "storybook dev -p 6006",
13
+ "build-storybook": "storybook build"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/FilipChalupa/react-swipe-action.git"
18
+ },
19
+ "keywords": [
20
+ "typescript",
21
+ "react",
22
+ "swipe"
23
+ ],
24
+ "author": {
25
+ "name": "Filip Chalupa",
26
+ "email": "chalupa.filip@gmail.com",
27
+ "url": "https://www.npmjs.com/~onset"
28
+ },
29
+ "license": "ISC",
30
+ "devDependencies": {
31
+ "@rollup/plugin-commonjs": "^25.0.7",
32
+ "@rollup/plugin-node-resolve": "^15.2.3",
33
+ "@storybook/addon-essentials": "^7.6.16",
34
+ "@storybook/addon-interactions": "^7.6.16",
35
+ "@storybook/addon-links": "^7.6.16",
36
+ "@storybook/addon-onboarding": "^1.0.11",
37
+ "@storybook/blocks": "^7.6.16",
38
+ "@storybook/react": "^7.6.16",
39
+ "@storybook/react-vite": "^7.6.16",
40
+ "@storybook/test": "^7.6.16",
41
+ "@types/react": "^18.2.57",
42
+ "@types/react-dom": "^18.2.19",
43
+ "prettier": "^3.0.3",
44
+ "react": "^18.2.0",
45
+ "react-dom": "^18.2.0",
46
+ "rollup": "^4.2.0",
47
+ "rollup-plugin-delete": "^2.0.0",
48
+ "rollup-plugin-peer-deps-external": "^2.2.4",
49
+ "rollup-plugin-postcss": "^4.0.2",
50
+ "rollup-plugin-preserve-directives": "^0.3.1",
51
+ "rollup-plugin-typescript2": "^0.36.0",
52
+ "storybook": "^7.6.16",
53
+ "typescript": "^5.3.3",
54
+ "typescript-plugin-css-modules": "^5.0.2"
55
+ },
56
+ "peerDependencies": {
57
+ "react": "17 || 18 || 19"
58
+ },
59
+ "files": [
60
+ "/dist/"
61
+ ],
62
+ "dependencies": {
63
+ "react-use-drag": "^0.3.2"
64
+ }
65
+ }