Skip to content

Commit a611f91

Browse files
committed
[IMPL] useLongPress hook
1 parent 792cc25 commit a611f91

10 files changed

Lines changed: 186 additions & 6 deletions

File tree

apps/react-tools-demo/src/App.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,4 +592,9 @@ button:not(:disabled) {
592592
.container-themed[data-color="light"] {
593593
border-color: purple;
594594
color: lightskyblue;
595+
}
596+
597+
.btn-animated {
598+
background-color: #1a1a1a;
599+
animation: spin 1s linear forwards;
595600
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { useState } from "react";
2+
import { useLongPress } from "../../../../../../packages/react-tools/src";
3+
4+
/**
5+
The component has:
6+
- a __pressCounter__ state variable.
7+
- a __clickCounter__ state variable.
8+
- a RefCallback __cbRef__ attached to a button __Press me__. It is returned from _useLongPress_ hook that has a cb a function to update __pressCounter__ state and _normalPress_ option to update __clickCounter__ state.
9+
- it renders a div with __Press me__ button and two p elements that display __pressCounter__ and __clickCounter__.
10+
11+
When the button is clicked for less than a second, _normalPress_ callback is executed, otherwise _long press_ callback is executed.
12+
*/
13+
export const UseLongPress = () => {
14+
const [pressCounter, setPressCounter] = useState(0);
15+
const [clickCounter, setClickCounter] = useState(0);
16+
const [color, setColor] = useState(-1)
17+
18+
19+
const cbRef = useLongPress<HTMLButtonElement>(() => {
20+
setPressCounter((s) => s + 1);
21+
setColor(1);
22+
}, {
23+
normalPress: () => setClickCounter((s) => s + 1),
24+
onStart() {
25+
setColor(0);
26+
},
27+
onFinish() {
28+
setColor(-1);
29+
},
30+
});
31+
32+
return (
33+
<div>
34+
<button ref={cbRef} type="button" style={{ ...(color === -1 ? {} : { backgroundColor: color === -1 ? "inherit" : color === 0 ? "#8e830f" : "#0b5f07"})}}>
35+
Press me
36+
</button>
37+
<p>pressCounter: {pressCounter}</p>
38+
<p>clickCounter: {clickCounter}</p>
39+
</div>
40+
);
41+
}

apps/react-tools-demo/src/constants/components.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ export const COMPONENTS = [
5959
"useMeasure",
6060
"useVisible",
6161
"useScrollIntoView",
62-
"useMouse"
62+
"useMouse",
63+
"useLongPress"
6364
],
6465
//API DOM
6566
[
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# useLongPress
2+
Hook to execute a callback on a long press event.
3+
4+
## Usage
5+
6+
```tsx
7+
export const UseLongPress = () => {
8+
const [pressCounter, setPressCounter] = useState(0);
9+
const [clickCounter, setClickCounter] = useState(0);
10+
const [color, setColor] = useState(-1)
11+
12+
13+
const cbRef = useLongPress<HTMLButtonElement>(() => {
14+
setPressCounter((s) => s + 1);
15+
setColor(1);
16+
}, {
17+
normalPress: () => setClickCounter((s) => s + 1),
18+
onStart() {
19+
setColor(0);
20+
},
21+
onFinish() {
22+
setColor(-1);
23+
},
24+
});
25+
26+
return (
27+
<div>
28+
<button ref={cbRef} type="button" style={{ ...(color === -1 ? {} : { backgroundColor: color === -1 ? "inherit" : color === 0 ? "#8e830f" : "#0b5f07"})}}>
29+
Press me
30+
</button>
31+
<p>pressCounter: {pressCounter}</p>
32+
<p>clickCounter: {clickCounter}</p>
33+
</div>
34+
);
35+
}
36+
```
37+
38+
> The component has:
39+
> - a __pressCounter__ state variable.
40+
> - a __clickCounter__ state variable.
41+
> - a RefCallback __cbRef__ attached to a button __Press me__. It is returned from _useLongPress_ hook that has a cb a function to update __pressCounter__ state and _normalPress_ option to update __clickCounter__ state.
42+
> - it renders a div with __Press me__ button and two p elements that display __pressCounter__ and __clickCounter__.
43+
>
44+
> When the button is clicked for less than a second, _normalPress_ callback is executed, otherwise _long press_ callback is executed.
45+
46+
47+
## API
48+
49+
```tsx
50+
useLongPress <T extends Element = Element, E extends Event = Event>(cb: useLongPressCallback<E>, { duration = 1000, normalPress, onStart, onFinish }: useLongPressOptions<E>): RefCallback<T>
51+
```
52+
53+
> ### Params
54+
>
55+
> - __cb__: _(evt:E)=>void_
56+
callback to execute after a certain duration.
57+
> - __opts__: _Object_
58+
> - __opts.duration=1000?__: _number_
59+
long press event duration in milliseconds.
60+
> - __opts.onStart?__: _(evt:E)=>void_
61+
callback that will be executed on initial press event.
62+
> - __opts.onFinish?__: _(evt:E)=>void_
63+
callback that will be executed when long press callback has done.
64+
> - __opts.normalPress?__: _(evt:E)=>void_
65+
callback executed on normal press event.
66+
>
67+
68+
> ### Returns
69+
>
70+
> __ref callback__: to be attached on target element.
71+
> - _RefCallback<T>_
72+
>

apps/react-tools-demo/src/markdown/useMouse.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ if it is presents, position is relative to element.
5656
5757
> ### Returns
5858
>
59-
> __ object- postion by axis and if relativeElement is present, relativeElement dimensions also.__
59+
> __ object__: postion by axis and if relativeElement is present, relativeElement dimensions also.
6060
> - __Union of__:
6161
> - __Object__:
6262
> - __x__ : _number|null_

packages/react-tools/README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,11 @@
6262
- [x] useVisibile
6363
- [x] useScrollIntoView
6464
- [x] useMouse
65-
- [ ] useLongPress
65+
- [x] useLongPress
6666
- [ ] useKeysEvents
6767
- [ ] useOrientation
6868
- [ ] useImageOnLoad
6969
- [ ] usePinchZoom
70-
7170
- [ ] useInfiniteScroll
7271
- [ ] useDragAndDrop (check for mobile usage)
7372

packages/react-tools/src/hooks/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,5 @@ export { useFullscreen } from './useFullscreen';
5858
export { useVisible } from './useVisible';
5959
export { useReducedMotion } from './useReducedMotion';
6060
export { useScrollIntoView } from './useScrollIntoView';
61-
export { useMouse } from './useMouse';
61+
export { useMouse } from './useMouse';
62+
export { useLongPress } from './useLongPress';
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { RefCallback, useCallback, useRef } from "react";
2+
import { useLongPressCallback, useLongPressOptions } from "../models/useLongPress.model";
3+
4+
/**
5+
* **`useLongPress`**: Hook to execute a callback on a long press event.
6+
* @param {(evt:E)=>void} cb - callback to execute after a certain duration.
7+
* @param {Object} opts
8+
* @param {number} [opts.duration=1000] - long press event duration in milliseconds.
9+
* @param {(evt:E)=>void} [opts.onStart] - callback that will be executed on initial press event.
10+
* @param {(evt:E)=>void} [opts.onFinish] - callback that will be executed when long press callback has done.
11+
* @param {(evt:E)=>void} [opts.normalPress] - callback executed on normal press event.
12+
* @returns {RefCallback<T>} ref callback - to be attached on target element.
13+
*/
14+
export const useLongPress = <T extends Element = Element, E extends Event = Event>(cb: useLongPressCallback<E>, { duration = 1000, normalPress, onStart, onFinish }: useLongPressOptions<E>): RefCallback<T> => {
15+
const evtRef = useRef<E>();
16+
const idTimeout = useRef<number|undefined>();
17+
const longPressActive = useRef(false);
18+
const target = useRef<T>();
19+
20+
const pointerUp = useCallback(() => {
21+
clearTimeout(idTimeout.current);
22+
onFinish && onFinish(evtRef.current as E);
23+
!longPressActive.current && normalPress && normalPress(evtRef.current!);
24+
}, [normalPress, onFinish]);
25+
26+
const pointerDown = useCallback((evt: Event) => {
27+
longPressActive.current = false;
28+
evtRef.current = evt as E;
29+
30+
onStart && onStart(evt as E);
31+
32+
idTimeout.current = setTimeout(async () => {
33+
longPressActive.current = true;
34+
await cb(evt as E);
35+
}, duration) as unknown as number;
36+
37+
}, [cb, duration, onStart]);
38+
39+
return useCallback((node: T) => {
40+
if (node) {
41+
node.addEventListener("pointerdown", pointerDown);
42+
node.addEventListener("pointerup", pointerUp);
43+
target.current = node;
44+
} else {
45+
target.current?.removeEventListener("pointerdown", pointerDown);
46+
target.current?.removeEventListener("pointerup", pointerUp);
47+
target.current = undefined;
48+
}
49+
}, [pointerDown, pointerUp]);
50+
}

packages/react-tools/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ export {
7272
useVisible,
7373
useReducedMotion,
7474
useScrollIntoView,
75-
useMouse
75+
useMouse,
76+
useLongPress
7677
} from './hooks'
7778

7879
export {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export interface useLongPressCallback<E extends Event = Event> {
2+
(evt: E):void | Promise<void>
3+
}
4+
5+
export interface useLongPressOptions<E extends Event = Event> {
6+
duration?: number,
7+
normalPress?: (evt: E) => void,
8+
onStart?: (evt: E) => void,
9+
onFinish?: (evt: E) => void
10+
}

0 commit comments

Comments
 (0)