@a-type/ui 0.6.8 → 0.6.9
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/dist/cjs/components/checkbox/Checkbox.d.ts +1 -1
- package/dist/cjs/components/contextMenu/contextMenu.d.ts +1 -1
- package/dist/cjs/components/dialog/Dialog.d.ts +3 -3
- package/dist/cjs/components/forms/Form.d.ts +1 -1
- package/dist/cjs/components/popover/Popover.d.ts +1 -1
- package/dist/cjs/hooks/index.d.ts +2 -0
- package/dist/cjs/hooks/index.js +2 -0
- package/dist/cjs/hooks/index.js.map +1 -1
- package/dist/cjs/hooks/useAnimationFrame.d.ts +1 -0
- package/dist/cjs/hooks/useAnimationFrame.js +25 -0
- package/dist/cjs/hooks/useAnimationFrame.js.map +1 -0
- package/dist/cjs/hooks/useLongPress.d.ts +11 -0
- package/dist/cjs/hooks/useLongPress.js +117 -0
- package/dist/cjs/hooks/useLongPress.js.map +1 -0
- package/dist/esm/components/checkbox/Checkbox.d.ts +1 -1
- package/dist/esm/components/contextMenu/contextMenu.d.ts +1 -1
- package/dist/esm/components/dialog/Dialog.d.ts +3 -3
- package/dist/esm/components/forms/Form.d.ts +1 -1
- package/dist/esm/components/popover/Popover.d.ts +1 -1
- package/dist/esm/hooks/index.d.ts +2 -0
- package/dist/esm/hooks/index.js +2 -0
- package/dist/esm/hooks/index.js.map +1 -1
- package/dist/esm/hooks/useAnimationFrame.d.ts +1 -0
- package/dist/esm/hooks/useAnimationFrame.js +21 -0
- package/dist/esm/hooks/useAnimationFrame.js.map +1 -0
- package/dist/esm/hooks/useLongPress.d.ts +11 -0
- package/dist/esm/hooks/useLongPress.js +113 -0
- package/dist/esm/hooks/useLongPress.js.map +1 -0
- package/package.json +1 -1
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useAnimationFrame.ts +23 -0
- package/src/hooks/useLongPress.ts +135 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useStableCallback } from './useStableCallback.js';
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
|
|
4
|
+
export function useAnimationFrame<Context>(
|
|
5
|
+
callback: (deltaTime: number, context: Context) => void,
|
|
6
|
+
initialContext?: Context,
|
|
7
|
+
) {
|
|
8
|
+
const requestRef = useRef<number>();
|
|
9
|
+
const previousTimeRef = useRef<number>();
|
|
10
|
+
const contextRef = useRef<Context>(initialContext!);
|
|
11
|
+
const animate = useStableCallback((time: number) => {
|
|
12
|
+
if (previousTimeRef.current !== undefined) {
|
|
13
|
+
const deltaTime = time - previousTimeRef.current;
|
|
14
|
+
callback(deltaTime, contextRef.current);
|
|
15
|
+
}
|
|
16
|
+
previousTimeRef.current = time;
|
|
17
|
+
requestRef.current = requestAnimationFrame(animate);
|
|
18
|
+
});
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
requestRef.current = requestAnimationFrame(animate);
|
|
21
|
+
return () => cancelAnimationFrame(requestRef.current!);
|
|
22
|
+
}, [animate]);
|
|
23
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { useDrag } from '@use-gesture/react';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { useAnimationFrame } from './useAnimationFrame.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The press gesture must remain within THRESHOLD_DISTANCE until delay time has passed
|
|
7
|
+
* to be considered a press.
|
|
8
|
+
*
|
|
9
|
+
* After delay, the gesture must remain within CANCEL_DISTANCE or be cancelled.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const THRESHOLD_DISTANCE = 10;
|
|
13
|
+
const CANCEL_DISTANCE = 30;
|
|
14
|
+
|
|
15
|
+
export function useLongPress({
|
|
16
|
+
onActivate,
|
|
17
|
+
duration = 2000,
|
|
18
|
+
delay = 200,
|
|
19
|
+
}: {
|
|
20
|
+
onActivate: () => void;
|
|
21
|
+
duration?: number;
|
|
22
|
+
delay?: number;
|
|
23
|
+
}) {
|
|
24
|
+
const [gestureState, setGestureState] = useState<'released' | 'pressed'>(
|
|
25
|
+
'released',
|
|
26
|
+
);
|
|
27
|
+
const [state, setState] = useState<'holding' | 'idle' | 'failed' | 'pending'>(
|
|
28
|
+
'idle',
|
|
29
|
+
);
|
|
30
|
+
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
31
|
+
const ref = useRef<any>(null);
|
|
32
|
+
|
|
33
|
+
const gestureStateRef = useRef<{ distance: number; startedAt: number }>({
|
|
34
|
+
distance: 0,
|
|
35
|
+
startedAt: 0,
|
|
36
|
+
});
|
|
37
|
+
useDrag(
|
|
38
|
+
({ first, cancel, elapsedTime, down, distance }) => {
|
|
39
|
+
const totalDistance = Math.sqrt(
|
|
40
|
+
Math.pow(distance[0], 2) + Math.pow(distance[1], 2),
|
|
41
|
+
);
|
|
42
|
+
gestureStateRef.current.distance = totalDistance;
|
|
43
|
+
|
|
44
|
+
if (elapsedTime < delay && totalDistance > THRESHOLD_DISTANCE) {
|
|
45
|
+
cancel();
|
|
46
|
+
setGestureState('released');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (totalDistance > CANCEL_DISTANCE) {
|
|
51
|
+
cancel();
|
|
52
|
+
setGestureState('released');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (first) {
|
|
57
|
+
gestureStateRef.current.startedAt = Date.now();
|
|
58
|
+
try {
|
|
59
|
+
navigator?.vibrate?.(200);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.log(err);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (down) {
|
|
66
|
+
setGestureState('pressed');
|
|
67
|
+
} else {
|
|
68
|
+
setGestureState('released');
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
// triggerAllEvents: true,
|
|
73
|
+
// preventDefault: true,
|
|
74
|
+
target: ref,
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
useAnimationFrame(() => {
|
|
79
|
+
const gestureDuration = gestureStateRef.current.startedAt
|
|
80
|
+
? Date.now() - gestureStateRef.current.startedAt
|
|
81
|
+
: 0;
|
|
82
|
+
const distance = gestureStateRef.current.distance;
|
|
83
|
+
|
|
84
|
+
// nothing to do in this case
|
|
85
|
+
if (
|
|
86
|
+
gestureState === 'released' &&
|
|
87
|
+
(state === 'idle' || state === 'failed')
|
|
88
|
+
) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (gestureState === 'released') {
|
|
93
|
+
if (state === 'holding') {
|
|
94
|
+
// holding for longer than duration - activate
|
|
95
|
+
if (gestureDuration >= duration + delay && distance < CANCEL_DISTANCE) {
|
|
96
|
+
onActivate();
|
|
97
|
+
setState('idle');
|
|
98
|
+
} else {
|
|
99
|
+
// normal release before duration - cancel
|
|
100
|
+
setState('idle');
|
|
101
|
+
}
|
|
102
|
+
} else if (state === 'pending' && distance < THRESHOLD_DISTANCE) {
|
|
103
|
+
setState('failed');
|
|
104
|
+
}
|
|
105
|
+
} else if (gestureState === 'pressed') {
|
|
106
|
+
// begin a new press
|
|
107
|
+
if (state === 'idle' || state === 'failed') {
|
|
108
|
+
setState('pending');
|
|
109
|
+
} else if (state === 'pending' && gestureDuration >= delay) {
|
|
110
|
+
// begin holding after delay has passed
|
|
111
|
+
setState('holding');
|
|
112
|
+
} else if (distance > CANCEL_DISTANCE) {
|
|
113
|
+
// cancel if moved too far
|
|
114
|
+
setState('idle');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (state === 'failed') {
|
|
121
|
+
const timeout = setTimeout(() => {
|
|
122
|
+
setState('idle');
|
|
123
|
+
}, 1000);
|
|
124
|
+
return () => {
|
|
125
|
+
clearTimeout(timeout);
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}, [state]);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
ref,
|
|
132
|
+
timeoutRef,
|
|
133
|
+
state,
|
|
134
|
+
};
|
|
135
|
+
}
|