@stream-io/video-react-sdk 1.28.2 → 1.29.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.
@@ -57,8 +57,13 @@ export type SpeakerLayoutProps = {
57
57
  * Whether the layout is muted. Defaults to `false`.
58
58
  */
59
59
  muted?: boolean;
60
+ /**
61
+ * Whether to enable drag-to-scroll functionality on the participants bar.
62
+ * @default false
63
+ */
64
+ enableDragToScroll?: boolean;
60
65
  } & Pick<ParticipantViewProps, 'VideoPlaceholder' | 'PictureInPicturePlaceholder'>;
61
66
  export declare const SpeakerLayout: {
62
- ({ ParticipantViewUIBar, ParticipantViewUISpotlight, VideoPlaceholder, PictureInPicturePlaceholder, participantsBarPosition, participantsBarLimit, mirrorLocalParticipantVideo, excludeLocalParticipant, filterParticipants, pageArrowsVisible, muted, }: SpeakerLayoutProps): import("react/jsx-runtime").JSX.Element | null;
67
+ ({ ParticipantViewUIBar, ParticipantViewUISpotlight, VideoPlaceholder, PictureInPicturePlaceholder, participantsBarPosition, participantsBarLimit, mirrorLocalParticipantVideo, excludeLocalParticipant, filterParticipants, pageArrowsVisible, muted, enableDragToScroll, }: SpeakerLayoutProps): import("react/jsx-runtime").JSX.Element | null;
63
68
  displayName: string;
64
69
  };
@@ -4,3 +4,4 @@ export * from './useScrollPosition';
4
4
  export * from './useRequestPermission';
5
5
  export * from './useDeviceList';
6
6
  export * from './useModeration';
7
+ export * from './useDragToScroll';
@@ -0,0 +1,18 @@
1
+ interface DragToScrollOptions {
2
+ decay?: number;
3
+ minVelocity?: number;
4
+ dragThreshold?: number;
5
+ enabled?: boolean;
6
+ }
7
+ /**
8
+ * Enables drag-to-scroll functionality with momentum scrolling on a scrollable element.
9
+ *
10
+ * This hook allows users to click and drag to scroll an element, with momentum scrolling
11
+ * that continues after the drag ends. The drag only activates after moving beyond a threshold
12
+ * distance, which prevents accidental drags from clicks.
13
+ *
14
+ * @param element - The HTML element to enable drag to scroll on.
15
+ * @param options - Options for customizing the drag-to-scroll behavior.
16
+ */
17
+ export declare function useDragToScroll(element: HTMLElement | null, options?: DragToScrollOptions): void;
18
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-react-sdk",
3
- "version": "1.28.2",
3
+ "version": "1.29.1",
4
4
  "main": "./dist/index.cjs.js",
5
5
  "module": "./dist/index.es.js",
6
6
  "types": "./dist/index.d.ts",
@@ -31,9 +31,9 @@
31
31
  ],
32
32
  "dependencies": {
33
33
  "@floating-ui/react": "^0.27.6",
34
- "@stream-io/video-client": "1.38.2",
34
+ "@stream-io/video-client": "1.39.1",
35
35
  "@stream-io/video-filters-web": "0.6.0",
36
- "@stream-io/video-react-bindings": "1.12.2",
36
+ "@stream-io/video-react-bindings": "1.12.4",
37
37
  "chart.js": "^4.4.4",
38
38
  "clsx": "^2.0.0",
39
39
  "react-chartjs-2": "^5.3.0"
@@ -46,8 +46,8 @@
46
46
  "@rollup/plugin-json": "^6.1.0",
47
47
  "@rollup/plugin-replace": "^6.0.2",
48
48
  "@rollup/plugin-typescript": "^12.1.4",
49
- "@stream-io/audio-filters-web": "^0.6.1",
50
- "@stream-io/video-styling": "^1.9.0",
49
+ "@stream-io/audio-filters-web": "^0.7.0",
50
+ "@stream-io/video-styling": "^1.9.1",
51
51
  "@types/react": "~19.1.17",
52
52
  "@types/react-dom": "~19.1.11",
53
53
  "react": "19.1.0",
@@ -10,6 +10,7 @@ import {
10
10
  } from '../ParticipantView';
11
11
  import { IconButton } from '../../../components';
12
12
  import {
13
+ useDragToScroll,
13
14
  useHorizontalScrollPosition,
14
15
  useVerticalScrollPosition,
15
16
  } from '../../../hooks';
@@ -88,6 +89,12 @@ export type SpeakerLayoutProps = {
88
89
  * Whether the layout is muted. Defaults to `false`.
89
90
  */
90
91
  muted?: boolean;
92
+
93
+ /**
94
+ * Whether to enable drag-to-scroll functionality on the participants bar.
95
+ * @default false
96
+ */
97
+ enableDragToScroll?: boolean;
91
98
  } & Pick<
92
99
  ParticipantViewProps,
93
100
  'VideoPlaceholder' | 'PictureInPicturePlaceholder'
@@ -109,6 +116,7 @@ export const SpeakerLayout = ({
109
116
  filterParticipants,
110
117
  pageArrowsVisible = true,
111
118
  muted,
119
+ enableDragToScroll = false,
112
120
  }: SpeakerLayoutProps) => {
113
121
  const call = useCall();
114
122
  const { useParticipants } = useCallStateHooks();
@@ -146,6 +154,9 @@ export const SpeakerLayout = ({
146
154
 
147
155
  const isOneOnOneCall = allParticipants.length === 2;
148
156
  useSpeakerLayoutSortPreset(call, isOneOnOneCall);
157
+ useDragToScroll(participantsBarWrapperElement, {
158
+ enabled: enableDragToScroll,
159
+ });
149
160
 
150
161
  let participantsWithAppliedLimit = otherParticipants;
151
162
 
@@ -4,3 +4,4 @@ export * from './useScrollPosition';
4
4
  export * from './useRequestPermission';
5
5
  export * from './useDeviceList';
6
6
  export * from './useModeration';
7
+ export * from './useDragToScroll';
@@ -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
+ }