@stream-io/video-react-sdk 1.28.1 → 1.29.0

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.
@@ -0,0 +1,162 @@
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ interface DragScrollState {
4
+ isDragging: boolean;
5
+ isPointerActive: boolean;
6
+ prevX: number;
7
+ prevY: number;
8
+ velocityX: number;
9
+ velocityY: number;
10
+ rafId: number;
11
+ startX: number;
12
+ startY: number;
13
+ }
14
+
15
+ interface DragToScrollOptions {
16
+ decay?: number;
17
+ minVelocity?: number;
18
+ dragThreshold?: number;
19
+ enabled?: boolean;
20
+ }
21
+
22
+ /**
23
+ * Enables drag-to-scroll functionality with momentum scrolling on a scrollable element.
24
+ *
25
+ * This hook allows users to click and drag to scroll an element, with momentum scrolling
26
+ * that continues after the drag ends. The drag only activates after moving beyond a threshold
27
+ * distance, which prevents accidental drags from clicks.
28
+ *
29
+ * @param element - The HTML element to enable drag to scroll on.
30
+ * @param options - Options for customizing the drag-to-scroll behavior.
31
+ */
32
+ export function useDragToScroll(
33
+ element: HTMLElement | null,
34
+ options: DragToScrollOptions = {},
35
+ ) {
36
+ const stateRef = useRef<DragScrollState>({
37
+ isDragging: false,
38
+ isPointerActive: false,
39
+ prevX: 0,
40
+ prevY: 0,
41
+ velocityX: 0,
42
+ velocityY: 0,
43
+ rafId: 0,
44
+ startX: 0,
45
+ startY: 0,
46
+ });
47
+
48
+ useEffect(() => {
49
+ if (!element || !options.enabled) return;
50
+
51
+ const { decay = 0.95, minVelocity = 0.5, dragThreshold = 5 } = options;
52
+
53
+ const state = stateRef.current;
54
+
55
+ const stopMomentum = () => {
56
+ if (state.rafId) {
57
+ cancelAnimationFrame(state.rafId);
58
+ state.rafId = 0;
59
+ }
60
+ state.velocityX = 0;
61
+ state.velocityY = 0;
62
+ };
63
+
64
+ const momentumStep = () => {
65
+ state.velocityX *= decay;
66
+ state.velocityY *= decay;
67
+
68
+ element.scrollLeft -= state.velocityX;
69
+ element.scrollTop -= state.velocityY;
70
+
71
+ if (
72
+ Math.abs(state.velocityX) < minVelocity &&
73
+ Math.abs(state.velocityY) < minVelocity
74
+ ) {
75
+ state.rafId = 0;
76
+ return;
77
+ }
78
+
79
+ state.rafId = requestAnimationFrame(momentumStep);
80
+ };
81
+
82
+ const onPointerDown = (e: PointerEvent) => {
83
+ if (e.pointerType !== 'mouse') return;
84
+
85
+ stopMomentum();
86
+
87
+ state.isDragging = false;
88
+ state.isPointerActive = true;
89
+
90
+ state.prevX = e.clientX;
91
+ state.prevY = e.clientY;
92
+ state.startX = e.clientX;
93
+ state.startY = e.clientY;
94
+ };
95
+
96
+ const onPointerMove = (e: PointerEvent) => {
97
+ if (e.pointerType !== 'mouse') return;
98
+
99
+ if (!state.isPointerActive) return;
100
+
101
+ const dx = e.clientX - state.startX;
102
+ const dy = e.clientY - state.startY;
103
+
104
+ if (!state.isDragging && Math.hypot(dx, dy) > dragThreshold) {
105
+ state.isDragging = true;
106
+ e.preventDefault();
107
+ }
108
+
109
+ if (!state.isDragging) return;
110
+
111
+ const moveDx = e.clientX - state.prevX;
112
+ const moveDy = e.clientY - state.prevY;
113
+
114
+ element.scrollLeft -= moveDx;
115
+ element.scrollTop -= moveDy;
116
+
117
+ state.velocityX = moveDx;
118
+ state.velocityY = moveDy;
119
+
120
+ state.prevX = e.clientX;
121
+ state.prevY = e.clientY;
122
+ };
123
+
124
+ const onPointerUpOrCancel = () => {
125
+ const wasDragging = state.isDragging;
126
+
127
+ state.isDragging = false;
128
+ state.isPointerActive = false;
129
+
130
+ state.prevX = 0;
131
+ state.prevY = 0;
132
+ state.startX = 0;
133
+ state.startY = 0;
134
+
135
+ if (!wasDragging) {
136
+ stopMomentum();
137
+ return;
138
+ }
139
+
140
+ if (Math.hypot(state.velocityX, state.velocityY) < minVelocity) {
141
+ stopMomentum();
142
+ return;
143
+ }
144
+
145
+ state.rafId = requestAnimationFrame(momentumStep);
146
+ };
147
+
148
+ element.addEventListener('pointerdown', onPointerDown);
149
+ element.addEventListener('pointermove', onPointerMove);
150
+ window.addEventListener('pointerup', onPointerUpOrCancel);
151
+ window.addEventListener('pointercancel', onPointerUpOrCancel);
152
+
153
+ return () => {
154
+ element.removeEventListener('pointerdown', onPointerDown);
155
+ element.removeEventListener('pointermove', onPointerMove);
156
+ window.removeEventListener('pointerup', onPointerUpOrCancel);
157
+ window.removeEventListener('pointercancel', onPointerUpOrCancel);
158
+
159
+ stopMomentum();
160
+ };
161
+ }, [element, options]);
162
+ }