@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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [1.29.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-1.29.0...@stream-io/video-react-sdk-1.29.1) (2025-12-18)
6
+
7
+ ### Dependency Updates
8
+
9
+ - `@stream-io/video-styling` updated to version `1.9.1`
10
+ - `@stream-io/video-client` updated to version `1.39.1`
11
+ - `@stream-io/video-react-bindings` updated to version `1.12.4`
12
+
13
+ ## [1.29.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-1.28.2...@stream-io/video-react-sdk-1.29.0) (2025-12-18)
14
+
15
+ ### Dependency Updates
16
+
17
+ - `@stream-io/audio-filters-web` updated to version `0.7.0`
18
+ - `@stream-io/video-client` updated to version `1.39.0`
19
+ - `@stream-io/video-react-bindings` updated to version `1.12.3`
20
+
21
+ ### Features
22
+
23
+ - **react:** Drag scroll on the participants list in the default layouts ([#2042](https://github.com/GetStream/stream-video-js/issues/2042)) ([b0f3f37](https://github.com/GetStream/stream-video-js/commit/b0f3f37ef45967625dca81af04ee5eb44df9d485))
24
+
5
25
  ## [1.28.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-1.28.1...@stream-io/video-react-sdk-1.28.2) (2025-12-11)
6
26
 
7
27
  ### Dependency Updates
package/dist/index.cjs.js CHANGED
@@ -594,6 +594,118 @@ const useModeration = (options) => {
594
594
  react.useEffect(() => disableBlur, [disableBlur]);
595
595
  };
596
596
 
597
+ /**
598
+ * Enables drag-to-scroll functionality with momentum scrolling on a scrollable element.
599
+ *
600
+ * This hook allows users to click and drag to scroll an element, with momentum scrolling
601
+ * that continues after the drag ends. The drag only activates after moving beyond a threshold
602
+ * distance, which prevents accidental drags from clicks.
603
+ *
604
+ * @param element - The HTML element to enable drag to scroll on.
605
+ * @param options - Options for customizing the drag-to-scroll behavior.
606
+ */
607
+ function useDragToScroll(element, options = {}) {
608
+ const stateRef = react.useRef({
609
+ isDragging: false,
610
+ isPointerActive: false,
611
+ prevX: 0,
612
+ prevY: 0,
613
+ velocityX: 0,
614
+ velocityY: 0,
615
+ rafId: 0,
616
+ startX: 0,
617
+ startY: 0,
618
+ });
619
+ react.useEffect(() => {
620
+ if (!element || !options.enabled)
621
+ return;
622
+ const { decay = 0.95, minVelocity = 0.5, dragThreshold = 5 } = options;
623
+ const state = stateRef.current;
624
+ const stopMomentum = () => {
625
+ if (state.rafId) {
626
+ cancelAnimationFrame(state.rafId);
627
+ state.rafId = 0;
628
+ }
629
+ state.velocityX = 0;
630
+ state.velocityY = 0;
631
+ };
632
+ const momentumStep = () => {
633
+ state.velocityX *= decay;
634
+ state.velocityY *= decay;
635
+ element.scrollLeft -= state.velocityX;
636
+ element.scrollTop -= state.velocityY;
637
+ if (Math.abs(state.velocityX) < minVelocity &&
638
+ Math.abs(state.velocityY) < minVelocity) {
639
+ state.rafId = 0;
640
+ return;
641
+ }
642
+ state.rafId = requestAnimationFrame(momentumStep);
643
+ };
644
+ const onPointerDown = (e) => {
645
+ if (e.pointerType !== 'mouse')
646
+ return;
647
+ stopMomentum();
648
+ state.isDragging = false;
649
+ state.isPointerActive = true;
650
+ state.prevX = e.clientX;
651
+ state.prevY = e.clientY;
652
+ state.startX = e.clientX;
653
+ state.startY = e.clientY;
654
+ };
655
+ const onPointerMove = (e) => {
656
+ if (e.pointerType !== 'mouse')
657
+ return;
658
+ if (!state.isPointerActive)
659
+ return;
660
+ const dx = e.clientX - state.startX;
661
+ const dy = e.clientY - state.startY;
662
+ if (!state.isDragging && Math.hypot(dx, dy) > dragThreshold) {
663
+ state.isDragging = true;
664
+ e.preventDefault();
665
+ }
666
+ if (!state.isDragging)
667
+ return;
668
+ const moveDx = e.clientX - state.prevX;
669
+ const moveDy = e.clientY - state.prevY;
670
+ element.scrollLeft -= moveDx;
671
+ element.scrollTop -= moveDy;
672
+ state.velocityX = moveDx;
673
+ state.velocityY = moveDy;
674
+ state.prevX = e.clientX;
675
+ state.prevY = e.clientY;
676
+ };
677
+ const onPointerUpOrCancel = () => {
678
+ const wasDragging = state.isDragging;
679
+ state.isDragging = false;
680
+ state.isPointerActive = false;
681
+ state.prevX = 0;
682
+ state.prevY = 0;
683
+ state.startX = 0;
684
+ state.startY = 0;
685
+ if (!wasDragging) {
686
+ stopMomentum();
687
+ return;
688
+ }
689
+ if (Math.hypot(state.velocityX, state.velocityY) < minVelocity) {
690
+ stopMomentum();
691
+ return;
692
+ }
693
+ state.rafId = requestAnimationFrame(momentumStep);
694
+ };
695
+ element.addEventListener('pointerdown', onPointerDown);
696
+ element.addEventListener('pointermove', onPointerMove);
697
+ window.addEventListener('pointerup', onPointerUpOrCancel);
698
+ window.addEventListener('pointercancel', onPointerUpOrCancel);
699
+ return () => {
700
+ element.removeEventListener('pointerdown', onPointerDown);
701
+ element.removeEventListener('pointermove', onPointerMove);
702
+ window.removeEventListener('pointerup', onPointerUpOrCancel);
703
+ window.removeEventListener('pointercancel', onPointerUpOrCancel);
704
+ stopMomentum();
705
+ };
706
+ }, [element, options]);
707
+ }
708
+
597
709
  exports.MenuVisualType = void 0;
598
710
  (function (MenuVisualType) {
599
711
  MenuVisualType["PORTAL"] = "portal";
@@ -1452,7 +1564,7 @@ const SpeakerTest = (props) => {
1452
1564
  const audioElementRef = react.useRef(null);
1453
1565
  const [isPlaying, setIsPlaying] = react.useState(false);
1454
1566
  const { t } = videoReactBindings.useI18n();
1455
- const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.28.2"}/assets/piano.mp3`, } = props;
1567
+ const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.29.1"}/assets/piano.mp3`, } = props;
1456
1568
  // Update audio output device when selection changes
1457
1569
  react.useEffect(() => {
1458
1570
  const audio = audioElementRef.current;
@@ -2945,7 +3057,7 @@ hostElement, limit) => {
2945
3057
  };
2946
3058
 
2947
3059
  const DefaultParticipantViewUIBar = () => (jsxRuntime.jsx(DefaultParticipantViewUI, { menuPlacement: "top-end" }));
2948
- const SpeakerLayout = ({ ParticipantViewUIBar = DefaultParticipantViewUIBar, ParticipantViewUISpotlight = DefaultParticipantViewUI, VideoPlaceholder, PictureInPicturePlaceholder, participantsBarPosition = 'bottom', participantsBarLimit, mirrorLocalParticipantVideo = true, excludeLocalParticipant = false, filterParticipants, pageArrowsVisible = true, muted, }) => {
3060
+ const SpeakerLayout = ({ ParticipantViewUIBar = DefaultParticipantViewUIBar, ParticipantViewUISpotlight = DefaultParticipantViewUI, VideoPlaceholder, PictureInPicturePlaceholder, participantsBarPosition = 'bottom', participantsBarLimit, mirrorLocalParticipantVideo = true, excludeLocalParticipant = false, filterParticipants, pageArrowsVisible = true, muted, enableDragToScroll = false, }) => {
2949
3061
  const call = videoReactBindings.useCall();
2950
3062
  const { useParticipants } = videoReactBindings.useCallStateHooks();
2951
3063
  const allParticipants = useParticipants();
@@ -2966,6 +3078,9 @@ const SpeakerLayout = ({ ParticipantViewUIBar = DefaultParticipantViewUIBar, Par
2966
3078
  }, [participantsBarWrapperElement, call]);
2967
3079
  const isOneOnOneCall = allParticipants.length === 2;
2968
3080
  useSpeakerLayoutSortPreset(call, isOneOnOneCall);
3081
+ useDragToScroll(participantsBarWrapperElement, {
3082
+ enabled: enableDragToScroll,
3083
+ });
2969
3084
  let participantsWithAppliedLimit = otherParticipants;
2970
3085
  const hardLimitToApply = isVertical
2971
3086
  ? hardLimit.vertical
@@ -3118,7 +3233,7 @@ const checkCanJoinEarly = (startsAt, joinAheadTimeSeconds) => {
3118
3233
  return Date.now() >= +startsAt - (joinAheadTimeSeconds ?? 0) * 1000;
3119
3234
  };
3120
3235
 
3121
- const [major, minor, patch] = ("1.28.2").split('.');
3236
+ const [major, minor, patch] = ("1.29.1").split('.');
3122
3237
  videoClient.setSdkInfo({
3123
3238
  type: videoClient.SfuModels.SdkType.REACT,
3124
3239
  major,