@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.
Files changed (32) hide show
  1. package/dist/cjs/components/checkbox/Checkbox.d.ts +1 -1
  2. package/dist/cjs/components/contextMenu/contextMenu.d.ts +1 -1
  3. package/dist/cjs/components/dialog/Dialog.d.ts +3 -3
  4. package/dist/cjs/components/forms/Form.d.ts +1 -1
  5. package/dist/cjs/components/popover/Popover.d.ts +1 -1
  6. package/dist/cjs/hooks/index.d.ts +2 -0
  7. package/dist/cjs/hooks/index.js +2 -0
  8. package/dist/cjs/hooks/index.js.map +1 -1
  9. package/dist/cjs/hooks/useAnimationFrame.d.ts +1 -0
  10. package/dist/cjs/hooks/useAnimationFrame.js +25 -0
  11. package/dist/cjs/hooks/useAnimationFrame.js.map +1 -0
  12. package/dist/cjs/hooks/useLongPress.d.ts +11 -0
  13. package/dist/cjs/hooks/useLongPress.js +117 -0
  14. package/dist/cjs/hooks/useLongPress.js.map +1 -0
  15. package/dist/esm/components/checkbox/Checkbox.d.ts +1 -1
  16. package/dist/esm/components/contextMenu/contextMenu.d.ts +1 -1
  17. package/dist/esm/components/dialog/Dialog.d.ts +3 -3
  18. package/dist/esm/components/forms/Form.d.ts +1 -1
  19. package/dist/esm/components/popover/Popover.d.ts +1 -1
  20. package/dist/esm/hooks/index.d.ts +2 -0
  21. package/dist/esm/hooks/index.js +2 -0
  22. package/dist/esm/hooks/index.js.map +1 -1
  23. package/dist/esm/hooks/useAnimationFrame.d.ts +1 -0
  24. package/dist/esm/hooks/useAnimationFrame.js +21 -0
  25. package/dist/esm/hooks/useAnimationFrame.js.map +1 -0
  26. package/dist/esm/hooks/useLongPress.d.ts +11 -0
  27. package/dist/esm/hooks/useLongPress.js +113 -0
  28. package/dist/esm/hooks/useLongPress.js.map +1 -0
  29. package/package.json +1 -1
  30. package/src/hooks/index.ts +2 -0
  31. package/src/hooks/useAnimationFrame.ts +23 -0
  32. 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
+ }