@whereby.com/browser-sdk 3.4.3 → 3.5.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.
@@ -146,7 +146,7 @@ const selectRoomConnectionState = createSelector(selectChatMessages, selectCloud
146
146
  return state;
147
147
  });
148
148
 
149
- const browserSdkVersion = "3.4.3";
149
+ const browserSdkVersion = "3.5.0";
150
150
 
151
151
  const defaultRoomConnectionOptions = {
152
152
  localMediaOptions: {
@@ -543,7 +543,40 @@ function renderGridVideoCells({ content, isConstrained, stageLayout, withRounded
543
543
  });
544
544
  return gridVideoCells;
545
545
  }
546
- function VideoStageLayout({ debug = false, featureRoundedCornersOff = false, gridContent = [], isConstrained = false, layoutOverflowBackdropFrame = makeFrame(), layoutVideoStage: stageLayout, presentationGridContent = [], subgridContent = [], }) {
546
+ function renderFloatingVideoCell({ content, containerFrame, stageLayout, withRoundedCorners, }) {
547
+ var _a;
548
+ if (!stageLayout.floatingContent) {
549
+ return null;
550
+ }
551
+ const cell = stageLayout.floatingContent;
552
+ const origin = { top: cell.origin.top, left: cell.origin.left };
553
+ const style = {
554
+ width: Math.round(cell.bounds.width),
555
+ height: Math.round(cell.bounds.height),
556
+ transform: `translate3d(${Math.round(containerFrame.origin.left + origin.left)}px, ${Math.round(containerFrame.origin.top + origin.top)}px, 0)`,
557
+ position: "fixed",
558
+ top: cell.bounds.height / 2,
559
+ left: 0,
560
+ zIndex: 40,
561
+ };
562
+ const clientId = ((_a = content.props.client) === null || _a === void 0 ? void 0 : _a.id) || "floating";
563
+ const childWithProps = React.cloneElement(content, {
564
+ isSmallCell: cell.isSmallCell,
565
+ isZoomedByDefault: false,
566
+ canZoom: false,
567
+ key: clientId,
568
+ isDraggable: false,
569
+ });
570
+ return renderVideoCell({
571
+ cell,
572
+ child: childWithProps,
573
+ clientId,
574
+ style,
575
+ withRoundedCorners,
576
+ withShadow: false,
577
+ });
578
+ }
579
+ function VideoStageLayout({ containerFrame, debug = false, featureRoundedCornersOff = false, floatingContent, gridContent = [], isConstrained = false, layoutOverflowBackdropFrame = makeFrame(), layoutVideoStage: stageLayout, presentationGridContent = [], subgridContent = [], }) {
547
580
  const withRoundedCorners = !featureRoundedCornersOff && !isConstrained;
548
581
  const cells = [];
549
582
  if (gridContent.length) {
@@ -564,6 +597,14 @@ function VideoStageLayout({ debug = false, featureRoundedCornersOff = false, gri
564
597
  withShadow: !isConstrained,
565
598
  }));
566
599
  }
600
+ if (floatingContent) {
601
+ cells.push(renderFloatingVideoCell({
602
+ content: floatingContent,
603
+ containerFrame,
604
+ stageLayout,
605
+ withRoundedCorners: !featureRoundedCornersOff,
606
+ }));
607
+ }
567
608
  if (subgridContent.length) {
568
609
  cells.push(...renderSubgridVideoCells({
569
610
  content: subgridContent,
@@ -640,7 +681,7 @@ function getCellBounds({ width, height, rows, cols, gridGap, aspectRatio, }) {
640
681
  extraVerticalPadding: totalVerticalCorrection / 2,
641
682
  };
642
683
  }
643
- function calculateLayout$2({ width, height, cellCount, gridGap, cellAspectRatios = [NORMAL_AR], paddings = makeBox(), }) {
684
+ function calculateLayout$3({ width, height, cellCount, gridGap, cellAspectRatios = [NORMAL_AR], paddings = makeBox(), }) {
644
685
  if (!cellCount) {
645
686
  return {
646
687
  cellCount,
@@ -700,7 +741,7 @@ function calculateLayout$2({ width, height, cellCount, gridGap, cellAspectRatios
700
741
  paddings,
701
742
  };
702
743
  }
703
- function getCellPropsAtIndexForLayout$1({ index, layout, }) {
744
+ function getCellPropsAtIndexForLayout$2({ index, layout, }) {
704
745
  const { cellWidth, cellHeight, rows, cols, cellCount, gridGap } = layout;
705
746
  const top = Math.floor(index / cols);
706
747
  const left = Math.floor(index % cols);
@@ -713,6 +754,147 @@ function getCellPropsAtIndexForLayout$1({ index, layout, }) {
713
754
  };
714
755
  }
715
756
 
757
+ const ROWS = "rows";
758
+ const COLUMNS = "columns";
759
+ function getPortraitLayoutGridForCount(count) {
760
+ return ({
761
+ 1: { type: ROWS, lines: [1] },
762
+ 2: { type: ROWS, lines: [1, 1] },
763
+ 3: { type: ROWS, lines: [2, 1] },
764
+ 4: { type: ROWS, lines: [2, 2] },
765
+ 5: { type: ROWS, lines: [2, 2, 1] },
766
+ 6: { type: ROWS, lines: [2, 2, 2] },
767
+ 7: { type: ROWS, lines: [3, 2, 2] },
768
+ 8: { type: ROWS, lines: [2, 2, 2, 2] },
769
+ 9: { type: ROWS, lines: [3, 2, 2, 2] },
770
+ 10: { type: ROWS, lines: [3, 3, 2, 2] },
771
+ 11: { type: ROWS, lines: [3, 3, 3, 2] },
772
+ 12: { type: ROWS, lines: [3, 3, 3, 3] },
773
+ 13: { type: ROWS, lines: [2, 2, 3, 3, 3] },
774
+ 14: { type: ROWS, lines: [2, 3, 3, 3, 3] },
775
+ 15: { type: ROWS, lines: [3, 3, 3, 3, 3] },
776
+ 16: { type: ROWS, lines: [2, 2, 3, 3, 3, 3] },
777
+ 17: { type: ROWS, lines: [2, 3, 3, 3, 3, 3] },
778
+ 18: { type: ROWS, lines: [3, 3, 3, 3, 3, 3] },
779
+ 19: { type: ROWS, lines: [2, 2, 3, 3, 3, 3, 3] },
780
+ 20: { type: ROWS, lines: [2, 3, 3, 3, 3, 3, 3] },
781
+ 21: { type: ROWS, lines: [3, 3, 3, 3, 3, 3, 3] },
782
+ 22: { type: ROWS, lines: [2, 2, 3, 3, 3, 3, 3, 3] },
783
+ 23: { type: ROWS, lines: [2, 3, 3, 3, 3, 3, 3, 3] },
784
+ 24: { type: ROWS, lines: [3, 3, 3, 3, 3, 3, 3, 3] },
785
+ }[count] || { type: ROWS, lines: [3, 3, 3, 3, 3, 3, 3, 3] });
786
+ }
787
+ function getLandscapeLayoutGridForCount(count) {
788
+ return ({
789
+ 1: { type: COLUMNS, lines: [1] },
790
+ 2: { type: COLUMNS, lines: [1, 1] },
791
+ 3: { type: COLUMNS, lines: [2, 1] },
792
+ 4: { type: COLUMNS, lines: [2, 2] },
793
+ 5: { type: ROWS, lines: [3, 2], fractions: [0.45, 0.55] },
794
+ 6: { type: COLUMNS, lines: [2, 2, 2] },
795
+ 7: { type: ROWS, lines: [4, 3], fractions: [0.45, 0.55] },
796
+ 8: { type: COLUMNS, lines: [2, 2, 2, 2] },
797
+ 9: { type: COLUMNS, lines: [3, 2, 2, 2] },
798
+ 10: { type: COLUMNS, lines: [3, 3, 2, 2] },
799
+ 11: { type: COLUMNS, lines: [3, 3, 3, 2] },
800
+ 12: { type: COLUMNS, lines: [3, 3, 3, 3] },
801
+ 13: { type: COLUMNS, lines: [4, 3, 3, 3] },
802
+ 14: { type: COLUMNS, lines: [4, 4, 3, 3] },
803
+ 15: { type: COLUMNS, lines: [3, 3, 3, 3, 3] },
804
+ 16: { type: COLUMNS, lines: [4, 3, 3, 3, 3] },
805
+ 17: { type: COLUMNS, lines: [4, 4, 3, 3, 3] },
806
+ 18: { type: COLUMNS, lines: [4, 4, 4, 3, 3] },
807
+ 19: { type: COLUMNS, lines: [4, 4, 4, 4, 3] },
808
+ 20: { type: COLUMNS, lines: [4, 4, 4, 4, 4] },
809
+ 21: { type: COLUMNS, lines: [5, 4, 4, 4, 4] },
810
+ 22: { type: COLUMNS, lines: [5, 5, 4, 4, 4] },
811
+ 23: { type: COLUMNS, lines: [5, 5, 5, 4, 4] },
812
+ 24: { type: COLUMNS, lines: [5, 5, 5, 5, 4] },
813
+ }[count] || { type: COLUMNS, lines: [5, 5, 5, 5, 5] });
814
+ }
815
+ function calcCellDimensions({ layoutGrid, dim, secDim, index, count, gridGap, }) {
816
+ const adjustedDim = dim - gridGap * (count - 1);
817
+ const cellDim = adjustedDim * (layoutGrid.fractions ? layoutGrid.fractions[index] : 1 / count);
818
+ const secCount = layoutGrid.lines[index];
819
+ const adjustedSecDim = secDim - gridGap * (secCount - 1);
820
+ const secCellDim = adjustedSecDim / secCount;
821
+ return [cellDim, secCellDim];
822
+ }
823
+ function partitionRectBy({ isHorizontal, layoutGrid, width, height, gridGap, }) {
824
+ const cellRects = [];
825
+ const origin = { x: 0, y: 0 };
826
+ const cellOrigin = { x: 0, y: 0 };
827
+ const count = layoutGrid.lines.length;
828
+ const dim = isHorizontal ? height : width;
829
+ const secDim = isHorizontal ? width : height;
830
+ for (let index = 0; index < count; index++) {
831
+ const cellDims = calcCellDimensions({ layoutGrid, dim, secDim, index, count, gridGap });
832
+ const width = isHorizontal ? cellDims[1] : cellDims[0];
833
+ const height = isHorizontal ? cellDims[0] : cellDims[1];
834
+ const cellBounds = { width, height };
835
+ const linesCount = layoutGrid.lines[index];
836
+ for (let lineIndex = 0; lineIndex < linesCount; lineIndex++) {
837
+ cellRects.push({ origin: Object.assign({}, cellOrigin), bounds: cellBounds });
838
+ if (isHorizontal) {
839
+ cellOrigin.x += cellBounds.width + gridGap;
840
+ }
841
+ else {
842
+ cellOrigin.y += cellBounds.height + gridGap;
843
+ }
844
+ }
845
+ if (isHorizontal) {
846
+ cellOrigin.y += cellBounds.height + gridGap;
847
+ cellOrigin.x = origin.x;
848
+ }
849
+ else {
850
+ cellOrigin.x += cellBounds.width + gridGap;
851
+ cellOrigin.y = origin.y;
852
+ }
853
+ }
854
+ return cellRects;
855
+ }
856
+ function partitionRectByRows({ layoutGrid, width, height, gridGap, }) {
857
+ return partitionRectBy({ isHorizontal: true, layoutGrid, width, height, gridGap });
858
+ }
859
+ function partitionRectByColumns({ layoutGrid, width, height, gridGap, }) {
860
+ return partitionRectBy({ isHorizontal: false, layoutGrid, width, height, gridGap });
861
+ }
862
+ function partitionRect({ layoutGrid, width, height, gridGap, }) {
863
+ switch (layoutGrid.type) {
864
+ case ROWS:
865
+ return partitionRectByRows({ layoutGrid, width, height, gridGap });
866
+ case COLUMNS:
867
+ return partitionRectByColumns({ layoutGrid, width, height, gridGap });
868
+ }
869
+ }
870
+ function calculateLayout$2({ width, height, cellCount, gridGap = 0, paddings = makeBox(), }) {
871
+ const contentWidth = width - (paddings.left + paddings.right);
872
+ const contentHeight = height - (paddings.top + paddings.bottom);
873
+ const isPortrait = Math.round((contentWidth / contentHeight) * 10) / 10 <= 1.0;
874
+ const layoutGrid = isPortrait
875
+ ? getPortraitLayoutGridForCount(cellCount)
876
+ : getLandscapeLayoutGridForCount(cellCount);
877
+ const cellRects = partitionRect({ layoutGrid, width: contentWidth, height: contentHeight, gridGap }) || [];
878
+ return {
879
+ cellRects,
880
+ cellCount,
881
+ gridGap,
882
+ extraHorizontalPadding: 0,
883
+ extraVerticalPadding: 0,
884
+ paddings,
885
+ };
886
+ }
887
+ function getCellPropsAtIndexForLayout$1({ index, layout }) {
888
+ const { cellRects } = layout;
889
+ const frame = cellRects[index] || makeFrame();
890
+ return {
891
+ top: frame.origin.y,
892
+ left: frame.origin.x,
893
+ width: frame.bounds.width,
894
+ height: frame.bounds.height,
895
+ };
896
+ }
897
+
716
898
  function getCenterPadding({ index, isPortrait, rows, cols, cellBounds, cellCount }) {
717
899
  const canFit = rows * cols;
718
900
  const leftOver = canFit - cellCount;
@@ -1082,38 +1264,73 @@ function calculateGridLayout({ containerBounds, paddings = makeBox(), videos, is
1082
1264
  const { width, height } = containerBounds;
1083
1265
  const cappedWidth = maxGridWidth ? Math.min(width, maxGridWidth) : width;
1084
1266
  const cellCount = videos.length;
1267
+ let gridLayout = null;
1085
1268
  let videoCells = null;
1086
- const cellAspectRatios = videos.map((video) => video.aspectRatio || 1);
1087
- const minGridBounds = getMinGridBounds({ cellCount });
1088
- const gridLayout = calculateLayout$2({
1089
- width: cappedWidth,
1090
- height,
1091
- cellCount,
1092
- gridGap,
1093
- cellAspectRatios,
1094
- paddings,
1095
- });
1096
- videoCells = videos.map((video, index) => {
1097
- const cellProps = getCellPropsAtIndexForLayout$1({ index, layout: gridLayout });
1098
- const isSmallCell = gridLayout.cellWidth < minGridBounds.width;
1099
- const shouldZoom = isConstrained || isSmallCell;
1100
- const aspectRatio = shouldZoom ? gridLayout.cellWidth / gridLayout.cellHeight : video.aspectRatio || 1;
1101
- return {
1102
- clientId: video.clientId,
1103
- isDraggable: video.isDraggable,
1104
- origin: makeOrigin({
1105
- top: cellProps.top,
1106
- left: cellProps.left,
1107
- }),
1108
- bounds: makeBounds({
1109
- width: cellProps.width,
1110
- height: cellProps.height,
1111
- }),
1112
- aspectRatio,
1113
- isSmallCell,
1114
- type: "video",
1115
- };
1116
- });
1269
+ if (isConstrained) {
1270
+ const constrainedGridLayout = calculateLayout$2({
1271
+ width,
1272
+ height,
1273
+ cellCount,
1274
+ gridGap,
1275
+ paddings,
1276
+ });
1277
+ const isSmallCell = cellCount > 4;
1278
+ videoCells = videos.map((video, index) => {
1279
+ const cellProps = getCellPropsAtIndexForLayout$1({ index, layout: constrainedGridLayout });
1280
+ return {
1281
+ clientId: video.clientId,
1282
+ isDraggable: video.isDraggable,
1283
+ origin: makeOrigin({
1284
+ top: cellProps.top,
1285
+ left: cellProps.left,
1286
+ }),
1287
+ bounds: makeBounds({
1288
+ width: cellProps.width,
1289
+ height: cellProps.height,
1290
+ }),
1291
+ aspectRatio: cellProps.width / cellProps.height,
1292
+ isSmallCell,
1293
+ type: "video",
1294
+ };
1295
+ });
1296
+ gridLayout = constrainedGridLayout;
1297
+ }
1298
+ else {
1299
+ const cellAspectRatios = videos.map((video) => video.aspectRatio || 1);
1300
+ const minGridBounds = getMinGridBounds({ cellCount });
1301
+ const centerGridLayout = calculateLayout$3({
1302
+ width: cappedWidth,
1303
+ height,
1304
+ cellCount,
1305
+ gridGap,
1306
+ cellAspectRatios,
1307
+ paddings,
1308
+ });
1309
+ videoCells = videos.map((video, index) => {
1310
+ const cellProps = getCellPropsAtIndexForLayout$2({ index, layout: centerGridLayout });
1311
+ const isSmallCell = centerGridLayout.cellWidth < minGridBounds.width;
1312
+ const shouldZoom = isConstrained || isSmallCell;
1313
+ const aspectRatio = shouldZoom
1314
+ ? centerGridLayout.cellWidth / centerGridLayout.cellHeight
1315
+ : video.aspectRatio || 1;
1316
+ return {
1317
+ clientId: video.clientId,
1318
+ isDraggable: video.isDraggable,
1319
+ origin: makeOrigin({
1320
+ top: cellProps.top,
1321
+ left: cellProps.left,
1322
+ }),
1323
+ bounds: makeBounds({
1324
+ width: cellProps.width,
1325
+ height: cellProps.height,
1326
+ }),
1327
+ aspectRatio,
1328
+ isSmallCell,
1329
+ type: "video",
1330
+ };
1331
+ });
1332
+ gridLayout = centerGridLayout;
1333
+ }
1117
1334
  return {
1118
1335
  videoCells,
1119
1336
  extraHorizontalPadding: width !== cappedWidth
@@ -1454,7 +1671,7 @@ function calculateLayout({ floatingVideo, frame, gridGap, isConstrained, isMaxim
1454
1671
  right: gridLayout.paddings.right + gridLayout.extraHorizontalPadding,
1455
1672
  }) }),
1456
1673
  subgrid: stageLayout.subgrid,
1457
- floatingContent: Object.assign(Object.assign({}, floatingLayout), floatingVideo),
1674
+ floatingContent: Object.assign(Object.assign(Object.assign({}, floatingLayout), floatingVideo), { origin: (floatingLayout === null || floatingLayout === void 0 ? void 0 : floatingLayout.origin) || makeOrigin(), bounds: (floatingLayout === null || floatingLayout === void 0 ? void 0 : floatingLayout.bounds) || makeBounds(), aspectRatio: (floatingVideo === null || floatingVideo === void 0 ? void 0 : floatingVideo.aspectRatio) || 1 }),
1458
1675
  };
1459
1676
  }
1460
1677
 
@@ -1474,8 +1691,9 @@ function makeVideoCellView({ aspectRatio, avatarSize, cellPaddings, client = und
1474
1691
 
1475
1692
  const STAGE_PARTICIPANT_LIMIT = 12;
1476
1693
  const ACTIVE_VIDEO_SUBGRID_TRIGGER = 12;
1694
+ const ACTIVE_VIDEOS_PHONE_LIMIT = 24;
1477
1695
 
1478
- function calculateSubgridViews({ clientViews, activeVideosSubgridTrigger, shouldShowSubgrid, spotlightedParticipants, maximizedParticipant, }) {
1696
+ function calculateSubgridViews({ clientViews, activeVideosSubgridTrigger, shouldShowSubgrid, spotlightedParticipants, maximizedParticipant, isPhoneResolution, }) {
1479
1697
  if (!shouldShowSubgrid) {
1480
1698
  return [];
1481
1699
  }
@@ -1492,6 +1710,16 @@ function calculateSubgridViews({ clientViews, activeVideosSubgridTrigger, should
1492
1710
  if (noVideoViews.length && hasPresentationStage) {
1493
1711
  return [...mutedVideos, ...noVideoViews];
1494
1712
  }
1713
+ if (isPhoneResolution && notSpotlighted.length > ACTIVE_VIDEOS_PHONE_LIMIT) {
1714
+ const sorted = [...unmutedVideos, ...mutedVideos];
1715
+ const inGrid = sorted.slice(0, ACTIVE_VIDEOS_PHONE_LIMIT);
1716
+ if (inGrid.length <= ACTIVE_VIDEOS_PHONE_LIMIT) {
1717
+ return [...sorted.filter((client) => !inGrid.includes(client)), ...noVideoViews];
1718
+ }
1719
+ else {
1720
+ return [...mutedVideos, ...noVideoViews];
1721
+ }
1722
+ }
1495
1723
  if (videoLimitReached && mutedVideos.length) {
1496
1724
  const sorted = [...unmutedVideos, ...mutedVideos];
1497
1725
  const inGrid = sorted.slice(0, activeVideosSubgridTrigger);
@@ -1504,10 +1732,19 @@ function calculateSubgridViews({ clientViews, activeVideosSubgridTrigger, should
1504
1732
  }
1505
1733
  return noVideoViews;
1506
1734
  }
1507
- function useGridParticipants({ activeVideosSubgridTrigger = ACTIVE_VIDEO_SUBGRID_TRIGGER, stageParticipantLimit = STAGE_PARTICIPANT_LIMIT, forceSubgrid = true, enableSubgrid = true, maximizedParticipant, } = {}) {
1735
+ function useGridParticipants({ activeVideosSubgridTrigger = ACTIVE_VIDEO_SUBGRID_TRIGGER, stageParticipantLimit = STAGE_PARTICIPANT_LIMIT, forceSubgrid = true, enableSubgrid = true, maximizedParticipant, floatingParticipant, isConstrained = false, } = {}) {
1508
1736
  const allClientViews = useAppSelector(selectAllClientViews);
1509
1737
  const spotlightedParticipants = useAppSelector(selectSpotlightedClientViews);
1510
1738
  const numParticipants = useAppSelector(selectNumParticipants);
1739
+ const floatingClientView = React.useMemo(() => {
1740
+ return floatingParticipant;
1741
+ }, [floatingParticipant]);
1742
+ const clientViewsNotFloating = React.useMemo(() => {
1743
+ if (floatingClientView) {
1744
+ return allClientViews.filter((c) => c.id !== floatingClientView.id);
1745
+ }
1746
+ return allClientViews;
1747
+ }, [allClientViews, floatingClientView]);
1511
1748
  const shouldShowSubgrid = React.useMemo(() => {
1512
1749
  if (!enableSubgrid) {
1513
1750
  return false;
@@ -1516,16 +1753,17 @@ function useGridParticipants({ activeVideosSubgridTrigger = ACTIVE_VIDEO_SUBGRID
1516
1753
  }, [forceSubgrid, numParticipants, stageParticipantLimit, enableSubgrid]);
1517
1754
  const clientViewsInSubgrid = React.useMemo(() => {
1518
1755
  return calculateSubgridViews({
1519
- clientViews: allClientViews,
1756
+ clientViews: clientViewsNotFloating,
1520
1757
  activeVideosSubgridTrigger,
1521
1758
  shouldShowSubgrid,
1522
1759
  spotlightedParticipants,
1523
1760
  maximizedParticipant,
1761
+ isPhoneResolution: isConstrained,
1524
1762
  });
1525
- }, [allClientViews, shouldShowSubgrid, activeVideosSubgridTrigger, spotlightedParticipants]);
1763
+ }, [clientViewsNotFloating, shouldShowSubgrid, activeVideosSubgridTrigger, spotlightedParticipants, isConstrained]);
1526
1764
  const clientViewsOnStage = React.useMemo(() => {
1527
- return allClientViews.filter((client) => !clientViewsInSubgrid.includes(client));
1528
- }, [allClientViews, clientViewsInSubgrid]);
1765
+ return clientViewsNotFloating.filter((client) => !clientViewsInSubgrid.includes(client));
1766
+ }, [clientViewsNotFloating, clientViewsInSubgrid]);
1529
1767
  const clientViewsInPresentationGrid = React.useMemo(() => {
1530
1768
  if (maximizedParticipant) {
1531
1769
  return [maximizedParticipant];
@@ -1536,23 +1774,40 @@ function useGridParticipants({ activeVideosSubgridTrigger = ACTIVE_VIDEO_SUBGRID
1536
1774
  return clientViewsOnStage.filter((client) => !clientViewsInPresentationGrid.includes(client));
1537
1775
  }, [clientViewsOnStage, clientViewsInPresentationGrid]);
1538
1776
  return {
1777
+ floatingClientView,
1539
1778
  clientViewsInGrid,
1540
1779
  clientViewsInPresentationGrid,
1541
1780
  clientViewsInSubgrid,
1542
1781
  };
1543
1782
  }
1544
1783
 
1545
- function useGrid({ activeVideosSubgridTrigger, forceSubgrid, stageParticipantLimit = STAGE_PARTICIPANT_LIMIT, gridGap = 8, videoGridGap = 8, enableSubgrid = true, } = {}) {
1784
+ function useGrid({ activeVideosSubgridTrigger, forceSubgrid, stageParticipantLimit = STAGE_PARTICIPANT_LIMIT, gridGap = 8, videoGridGap = 8, enableSubgrid = true, enableConstrainedGrid = true, } = {}) {
1546
1785
  const [containerBounds, setContainerBounds] = React.useState({ width: 0, height: 0 });
1786
+ const [isConstrained, setIsConstrained] = React.useState(false);
1547
1787
  const [clientAspectRatios, setClientAspectRatios] = React.useState({});
1548
1788
  const [maximizedParticipant, setMaximizedParticipant] = React.useState(null);
1549
- const { clientViewsInGrid, clientViewsInPresentationGrid, clientViewsInSubgrid } = useGridParticipants({
1789
+ const [floatingParticipant, setFloatingParticipant] = React.useState(null);
1790
+ const { clientViewsInGrid, clientViewsInPresentationGrid, clientViewsInSubgrid, floatingClientView } = useGridParticipants({
1550
1791
  activeVideosSubgridTrigger,
1551
1792
  forceSubgrid,
1552
1793
  stageParticipantLimit,
1553
1794
  enableSubgrid,
1554
1795
  maximizedParticipant,
1796
+ floatingParticipant,
1797
+ isConstrained: !!enableConstrainedGrid && !!isConstrained,
1555
1798
  });
1799
+ const cellViewsFloating = React.useMemo(() => {
1800
+ return floatingClientView
1801
+ ? [
1802
+ makeVideoCellView({
1803
+ client: floatingClientView,
1804
+ aspectRatio: clientAspectRatios[floatingClientView.id],
1805
+ avatarSize: 0,
1806
+ cellPaddings: { top: 0, right: 0 },
1807
+ }),
1808
+ ]
1809
+ : [];
1810
+ }, [floatingClientView, clientAspectRatios]);
1556
1811
  const cellViewsVideoGrid = React.useMemo(() => {
1557
1812
  return clientViewsInGrid.map((client) => {
1558
1813
  return makeVideoCellView({
@@ -1587,19 +1842,36 @@ function useGrid({ activeVideosSubgridTrigger, forceSubgrid, stageParticipantLim
1587
1842
  const containerFrame = React.useMemo(() => {
1588
1843
  return makeFrame(containerBounds);
1589
1844
  }, [containerBounds]);
1845
+ React.useEffect(() => {
1846
+ if (!enableConstrainedGrid) {
1847
+ return;
1848
+ }
1849
+ setIsConstrained(containerBounds.width < 500 || containerBounds.height < 500);
1850
+ }, [containerBounds, enableConstrainedGrid]);
1590
1851
  const videoStage = React.useMemo(() => {
1591
1852
  return calculateLayout({
1853
+ floatingVideo: cellViewsFloating[0],
1592
1854
  frame: containerFrame,
1593
1855
  gridGap,
1594
- isConstrained: false,
1856
+ isConstrained,
1595
1857
  roomBounds: containerFrame.bounds,
1596
1858
  videos: cellViewsVideoGrid,
1597
1859
  videoGridGap,
1598
1860
  presentationVideos: cellViewsInPresentationGrid,
1599
1861
  subgridVideos: cellViewsInSubgrid,
1600
1862
  });
1601
- }, [containerFrame, cellViewsVideoGrid, cellViewsInPresentationGrid, cellViewsInSubgrid, gridGap, videoGridGap]);
1863
+ }, [
1864
+ containerFrame,
1865
+ cellViewsFloating,
1866
+ cellViewsVideoGrid,
1867
+ cellViewsInPresentationGrid,
1868
+ cellViewsInSubgrid,
1869
+ gridGap,
1870
+ videoGridGap,
1871
+ ]);
1602
1872
  return {
1873
+ containerFrame,
1874
+ cellViewsFloating,
1603
1875
  cellViewsVideoGrid,
1604
1876
  cellViewsInPresentationGrid,
1605
1877
  cellViewsInSubgrid,
@@ -1609,6 +1881,9 @@ function useGrid({ activeVideosSubgridTrigger, forceSubgrid, stageParticipantLim
1609
1881
  setClientAspectRatios,
1610
1882
  maximizedParticipant,
1611
1883
  setMaximizedParticipant,
1884
+ floatingParticipant,
1885
+ setFloatingParticipant,
1886
+ isConstrained,
1612
1887
  };
1613
1888
  }
1614
1889
 
@@ -1717,11 +1992,12 @@ const ParticipantMenuTrigger = React.forwardRef((_a, ref) => {
1717
1992
  ParticipantMenuTrigger.displayName = PopoverTrigger.displayName;
1718
1993
  const ParticipantMenuItem = React.forwardRef((_a, ref) => {
1719
1994
  var { children, style, participantAction } = _a, props = __rest(_a, ["children", "style", "participantAction"]);
1720
- const { participant, setOpen, maximizedParticipant, setMaximizedParticipant } = useParticipantMenu();
1995
+ const { participant, setOpen, maximizedParticipant, setMaximizedParticipant, setFloatingParticipant, floatingParticipant, } = useParticipantMenu();
1721
1996
  const dispatch = useAppDispatch();
1722
1997
  const spotlightedParticipants = useAppSelector(selectSpotlightedClientViews);
1723
1998
  const isSpotlighted = spotlightedParticipants.find((p) => p.id === participant.id);
1724
1999
  const isMaximized = (maximizedParticipant === null || maximizedParticipant === void 0 ? void 0 : maximizedParticipant.id) === participant.id;
2000
+ const isFloating = (floatingParticipant === null || floatingParticipant === void 0 ? void 0 : floatingParticipant.id) === participant.id;
1725
2001
  let onClick;
1726
2002
  switch (participantAction) {
1727
2003
  case "maximize":
@@ -1746,6 +2022,17 @@ const ParticipantMenuItem = React.forwardRef((_a, ref) => {
1746
2022
  setOpen(false);
1747
2023
  };
1748
2024
  break;
2025
+ case "float":
2026
+ onClick = () => {
2027
+ if (isFloating) {
2028
+ setFloatingParticipant(null);
2029
+ }
2030
+ else {
2031
+ setFloatingParticipant(participant);
2032
+ }
2033
+ setOpen(false);
2034
+ };
2035
+ break;
1749
2036
  }
1750
2037
  return (React.createElement("button", Object.assign({ ref: ref, role: "menuitem", tabIndex: -1, onClick: onClick !== null && onClick !== void 0 ? onClick : props.onClick, style: Object.assign({ alignItems: "stretch", backgroundColor: "transparent", border: "none", cursor: "pointer", display: "flex", height: "40px", lineHeight: "40px", minWidth: "140px", padding: "0 12px", textAlign: "left", textDecoration: "none", whiteSpace: "nowrap", width: "100%" }, style) }, props), children));
1751
2038
  });
@@ -1769,11 +2056,22 @@ function SpotlightIcon(props) {
1769
2056
  React.createElement("path", { fillRule: "evenodd", clipRule: "evenodd", d: "m15.241.146691c-.1011-.1955882-.3809-.1955879-.482 0l-1.1063 2.139639c-.0258.04991-.0665.09058-.1164.11639l-2.1396 1.10629c-.1956.10113-.1956.38085 0 .48198l2.1396 1.10629c.0499.02581.0906.06648.1164.11639l1.1063 2.13964c.1011.19559.3809.19559.482 0l1.1063-2.13964c.0258-.04991.0665-.09058.1164-.11639l2.1396-1.10629c.1956-.10113.1956-.38085 0-.48198l-2.1396-1.10629c-.0499-.02581-.0906-.06648-.1164-.11639zm-13.741 2.900259v14.67495c0 .0511.00116.1018.00345.1523-.00229.0418-.00345.0837-.00345.1258 0 .5674.21005 1.1103.59339 1.6103.24265.3472.55042.6455.90566.8773 1.61225 1.213 4.36988 2.0124 7.50095 2.0124 4.3315 0 7.9484-1.53 8.8068-3.5681.0651-.1472.1148-.3024.1455-.4658.0315-.1532.0477-.3087.0477-.4661 0-.6032-.2374-1.1788-.668-1.7045-.1377-.1975-.2995-.4017-.4877-.6127l-14.22219-13.74809c-.28845-.27884-.67396-.43471-1.07516-.43471-.85436 0-1.54695.69259-1.54695 1.54695zm19.2646 4.59631c.0988-.19101.372-.19101.4708 0l.8546 1.65303c.0253.04874.065.08846.1137.11366l1.653.85465c.1911.0988.1911.372 0 .4708l-1.653.8546c-.0487.0253-.0884.065-.1137.1137l-.8546 1.653c-.0988.1911-.372.1911-.4708 0l-.8546-1.653c-.0253-.0487-.065-.0884-.1137-.1137l-1.653-.8546c-.1911-.0988-.1911-.372 0-.4708l1.653-.85465c.0487-.0252.0884-.06492.1137-.11366zm-3.2651 10.35684c0 .0769-.1144.6851-1.5305 1.3931-1.2964.6482-3.2274 1.1069-5.4695 1.1069s-4.17308-.4587-5.46952-1.1069c-1.41607-.708-1.53047-1.3162-1.53047-1.3931 0-.077.1144-.6851 1.53047-1.3932 1.29644-.6482 3.22742-1.1068 5.46952-1.1068s4.1731.4586 5.4695 1.1068c1.4161.7081 1.5305 1.3162 1.5305 1.3932z" })));
1770
2057
  }
1771
2058
 
2059
+ function PopOutIcon(props) {
2060
+ return (React.createElement("svg", Object.assign({ width: "100%", height: "100%", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, props),
2061
+ React.createElement("path", { clipRule: "evenodd", fillRule: "evenodd", d: "m2.25 3v4.5c0 .41421.33579.75.75.75h4.5c.41421 0 .75-.33579.75-.75v-4.5c0-.41421-.33579-.75-.75-.75h-4.5c-.41421 0-.75.33579-.75.75zm-2.25 4.5v-4.5c0-1.65685 1.34315-3 3-3h4.5c1.65685 0 3 1.34315 3 3v4.5c0 1.65685-1.34315 3-3 3h-4.5c-1.65685 0-3-1.34315-3-3zm2.25 9v4.5c0 .4142.33579.75.75.75h4.5c.41421 0 .75-.3358.75-.75v-4.5c0-.4142-.33579-.75-.75-.75h-4.5c-.41421 0-.75.3358-.75.75zm-2.25 4.5v-4.5c0-1.6569 1.34315-3 3-3h4.5c1.65685 0 3 1.3431 3 3v4.5c0 1.6569-1.34315 3-3 3h-4.5c-1.65685 0-3-1.3431-3-3zm15.75-13.5v-4.5c0-.41421.3358-.75.75-.75h4.5c.4142 0 .75.33579.75.75v4.5c0 .41421-.3358.75-.75.75h-4.5c-.4142 0-.75-.33579-.75-.75zm-2.25-4.5v4.5c0 1.65685 1.3431 3 3 3h4.5c1.6569 0 3-1.34315 3-3v-4.5c0-1.65685-1.3431-3-3-3h-4.5c-1.6569 0-3 1.34315-3 3zm1.875 19.5c0-.6213.5037-1.125 1.125-1.125h3.284l-5.5795-5.5795c-.4393-.4393-.4393-1.1517 0-1.591s1.1517-.4393 1.591 0l5.5795 5.5795v-3.284c0-.6213.5037-1.125 1.125-1.125s1.125.5037 1.125 1.125v6c0 .308-.1238.5872-.3244.7903-.0033.0034-.0067.0068-.0101.0102-.1067.1054-.2292.1851-.3599.2391-.1326.055-.2781.0854-.4306.0854h-6c-.6213 0-1.125-.5037-1.125-1.125z" })));
2062
+ }
2063
+
2064
+ function PopInIcon(props) {
2065
+ return (React.createElement("svg", Object.assign({ width: "100%", height: "100%", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, props),
2066
+ React.createElement("path", { clipRule: "evenodd", fillRule: "evenodd", d: "m2.25 3v4.5c0 .41421.33579.75.75.75h4.5c.41421 0 .75-.33579.75-.75v-4.5c0-.41421-.33579-.75-.75-.75h-4.5c-.41421 0-.75.33579-.75.75zm-2.25 4.5v-4.5c0-1.65685 1.34315-3 3-3h4.5c1.65685 0 3 1.34315 3 3v4.5c0 1.65685-1.34315 3-3 3h-4.5c-1.65685 0-3-1.34315-3-3zm2.25 9v4.5c0 .4142.33579.75.75.75h4.5c.41421 0 .75-.3358.75-.75v-4.5c0-.4142-.33579-.75-.75-.75h-4.5c-.41421 0-.75.3358-.75.75zm-2.25 4.5v-4.5c0-1.6569 1.34315-3 3-3h4.5c1.65685 0 3 1.3431 3 3v4.5c0 1.6569-1.34315 3-3 3h-4.5c-1.65685 0-3-1.3431-3-3zm15.75-13.5v-4.5c0-.41421.3358-.75.75-.75h4.5c.4142 0 .75.33579.75.75v4.5c0 .41421-.3358.75-.75.75h-4.5c-.4142 0-.75-.33579-.75-.75zm-2.25-4.5v4.5c0 1.65685 1.3431 3 3 3h4.5c1.6569 0 3-1.34315 3-3v-4.5c0-1.65685-1.3431-3-3-3h-4.5c-1.6569 0-3 1.34315-3 3zm8.625 12c0 .6213-.5037 1.125-1.125 1.125h-3.284l5.5795 5.5795c.4393.4393.4393 1.1517 0 1.591s-1.1517.4393-1.591 0l-5.5795-5.5795v3.284c0 .6213-.5037 1.125-1.125 1.125s-1.125-.5037-1.125-1.125v-6c0-.3081.1238-.5872.3244-.7904.0034-.0034.0068-.0068.0102-.0102.1067-.1053.2292-.185.3598-.239.1326-.055.2781-.0854.4306-.0854h6c.6213 0 1.125.5037 1.125 1.125z" })));
2067
+ }
2068
+
1772
2069
  function DefaultParticipantMenu({ participant }) {
1773
2070
  const spotlightedParticipants = useAppSelector(selectSpotlightedClientViews);
1774
2071
  const isSpotlighted = spotlightedParticipants.find((p) => p.id === participant.id);
1775
- const { isHovered, maximizedParticipant } = useGridCell();
2072
+ const { isHovered, maximizedParticipant, floatingParticipant } = useGridCell();
1776
2073
  const isMaximized = (maximizedParticipant === null || maximizedParticipant === void 0 ? void 0 : maximizedParticipant.id) === participant.id;
2074
+ const isFloating = (floatingParticipant === null || floatingParticipant === void 0 ? void 0 : floatingParticipant.id) === participant.id;
1777
2075
  if (!isHovered) {
1778
2076
  return null;
1779
2077
  }
@@ -1788,6 +2086,15 @@ function DefaultParticipantMenu({ participant }) {
1788
2086
  } },
1789
2087
  React.createElement(EllipsisIcon, { height: 20, width: 20, transform: "rotate(90)" })),
1790
2088
  React.createElement(ParticipantMenuContent, null,
2089
+ participant.isLocalClient ? (React.createElement(ParticipantMenuItem, { participantAction: "float", style: {
2090
+ display: "flex",
2091
+ alignItems: "center",
2092
+ gap: "10px",
2093
+ } }, isFloating ? (React.createElement(React.Fragment, null,
2094
+ React.createElement(PopInIcon, { height: 16, width: 16 }),
2095
+ "Move to grid")) : (React.createElement(React.Fragment, null,
2096
+ React.createElement(PopOutIcon, { height: 16, width: 16 }),
2097
+ "Pop out")))) : null,
1791
2098
  React.createElement(ParticipantMenuItem, { participantAction: "maximize", style: {
1792
2099
  display: "flex",
1793
2100
  alignItems: "center",
@@ -1813,13 +2120,13 @@ const GridCell = React.forwardRef(({ className, participant, children }, ref) =>
1813
2120
  setIsHovered(false);
1814
2121
  }, []);
1815
2122
  return (React.createElement(GridCellContext.Provider, { value: { participant, isHovered } },
1816
- React.createElement("div", { ref: ref, className: className, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave }, children)));
2123
+ React.createElement("div", { ref: ref, className: className, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, style: { height: "100%", width: "100%" } }, children)));
1817
2124
  });
1818
2125
  GridCell.displayName = "GridCell";
1819
2126
  const GridVideoView = React.forwardRef((_a, ref) => {
1820
2127
  var { stream, style } = _a, rest = __rest(_a, ["stream", "style"]);
1821
2128
  const videoEl = React.useRef(null);
1822
- const { onSetClientAspectRatio, clientAspectRatios, participant } = useGridCell();
2129
+ const { onSetClientAspectRatio, clientAspectRatios, participant, isConstrained } = useGridCell();
1823
2130
  if (!participant)
1824
2131
  return null;
1825
2132
  const aspectRatio = clientAspectRatios[participant.id];
@@ -1836,11 +2143,11 @@ const GridVideoView = React.forwardRef((_a, ref) => {
1836
2143
  if (!s) {
1837
2144
  return null;
1838
2145
  }
1839
- return (React.createElement(VideoView, Object.assign({ ref: videoEl, style: Object.assign({ borderRadius: "8px" }, style) }, rest, { stream: s, onVideoResize: handleResize })));
2146
+ return (React.createElement(VideoView, Object.assign({ ref: videoEl, style: Object.assign(Object.assign({ borderRadius: isConstrained ? 0 : "8px" }, (isConstrained ? { objectFit: "cover" } : {})), style) }, rest, { stream: s, onVideoResize: handleResize })));
1840
2147
  });
1841
2148
  GridVideoView.displayName = "GridVideoView";
1842
2149
  function renderCellView({ cellView, enableParticipantMenu, render }) {
1843
- const participant = cellView.client;
2150
+ const participant = cellView === null || cellView === void 0 ? void 0 : cellView.client;
1844
2151
  if (!participant) {
1845
2152
  return undefined;
1846
2153
  }
@@ -1851,12 +2158,24 @@ function renderCellView({ cellView, enableParticipantMenu, render }) {
1851
2158
  enableParticipantMenu ? (React.createElement(DefaultParticipantMenu, { participant: participant })) : null)))) : (React.createElement(VideoMutedIndicator, { isSmallCell: false, displayName: (participant === null || participant === void 0 ? void 0 : participant.displayName) || "Guest", withRoundedCorners: true }))));
1852
2159
  }
1853
2160
  }
1854
- function Grid({ renderParticipant, stageParticipantLimit, gridGap, videoGridGap, enableSubgrid, enableParticipantMenu, }) {
2161
+ function Grid({ renderParticipant, renderFloatingParticipant, stageParticipantLimit, gridGap, videoGridGap, enableSubgrid, enableParticipantMenu, enableConstrainedGrid, }) {
1855
2162
  const gridRef = React.useRef(null);
1856
- const { cellViewsVideoGrid, cellViewsInPresentationGrid, cellViewsInSubgrid, clientAspectRatios, videoStage, setContainerBounds, setClientAspectRatios, maximizedParticipant, setMaximizedParticipant, } = useGrid({ activeVideosSubgridTrigger: 12, stageParticipantLimit, gridGap, videoGridGap, enableSubgrid });
2163
+ const { containerFrame, cellViewsFloating, cellViewsVideoGrid, cellViewsInPresentationGrid, cellViewsInSubgrid, clientAspectRatios, videoStage, setContainerBounds, setClientAspectRatios, maximizedParticipant, setMaximizedParticipant, isConstrained, floatingParticipant, setFloatingParticipant, } = useGrid({
2164
+ activeVideosSubgridTrigger: 12,
2165
+ stageParticipantLimit,
2166
+ gridGap,
2167
+ videoGridGap,
2168
+ enableSubgrid,
2169
+ enableConstrainedGrid,
2170
+ });
1857
2171
  const handleSetClientAspectRatio = React.useCallback(({ aspectRatio, clientId }) => {
1858
2172
  setClientAspectRatios((prev) => (Object.assign(Object.assign({}, prev), { [clientId]: aspectRatio })));
1859
2173
  }, [setClientAspectRatios]);
2174
+ const floatingContent = React.useMemo(() => {
2175
+ return renderCellView(Object.assign({ cellView: cellViewsFloating[0], enableParticipantMenu }, (renderFloatingParticipant
2176
+ ? { render: ({ participant }) => renderFloatingParticipant({ participant }) }
2177
+ : {})));
2178
+ }, [cellViewsFloating]);
1860
2179
  const presentationGridContent = React.useMemo(() => cellViewsInPresentationGrid.map((cellView) => renderCellView(Object.assign({ cellView,
1861
2180
  enableParticipantMenu }, (renderParticipant ? { render: ({ participant }) => renderParticipant({ participant }) } : {})))), [cellViewsInPresentationGrid]);
1862
2181
  const gridContent = React.useMemo(() => cellViewsVideoGrid.map((cellView) => renderCellView(Object.assign({ cellView,
@@ -1889,13 +2208,16 @@ function Grid({ renderParticipant, stageParticipantLimit, gridGap, videoGridGap,
1889
2208
  clientAspectRatios,
1890
2209
  maximizedParticipant,
1891
2210
  setMaximizedParticipant,
2211
+ floatingParticipant,
2212
+ setFloatingParticipant,
2213
+ isConstrained,
1892
2214
  } },
1893
2215
  React.createElement("div", { ref: gridRef, style: {
1894
2216
  width: "100%",
1895
2217
  height: "100%",
1896
2218
  position: "relative",
1897
2219
  } },
1898
- React.createElement(VideoStageLayout, { layoutVideoStage: videoStage, presentationGridContent: presentationGridContent, gridContent: gridContent, subgridContent: subgridContent }))));
2220
+ React.createElement(VideoStageLayout, { containerFrame: containerFrame, floatingContent: floatingContent, layoutVideoStage: videoStage, presentationGridContent: presentationGridContent, gridContent: gridContent, subgridContent: subgridContent }))));
1899
2221
  }
1900
2222
 
1901
2223
  export { GridCell, GridVideoView, ParticipantMenu, ParticipantMenuContent, ParticipantMenuItem, ParticipantMenuTrigger, Grid as VideoGrid, VideoView, Provider as WherebyProvider, useLocalMedia, useRoomConnection };