@xipkg/calls-compactview 0.1.55 → 0.1.56

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/dist/index.mjs CHANGED
@@ -15,7 +15,7 @@ import { Button } from '@xipkg/button';
15
15
  import { ArrowLeft, ArrowRight, Account, SoundTwo, Users, External, ChevronUp, WhiteBoard, Maximize, MicrophoneOff, RedLine, ChevronBottom, ArrowUp } from '@xipkg/icons';
16
16
  import { cn } from '@xipkg/utils';
17
17
  import { useTranslation } from 'react-i18next';
18
- import { ParticipantTile, DevicesBar, LessonRecordButton, ScreenShareButton, DisconnectButton, ScreenShareStageToggle, ParticipantPinToggle, tileOverlayButtonClassName, PermissionsDialog, TrackMutedIndicator, ParticipantName, RaisedHandIndicator, ScreenShareZoom, VideoTrack } from '@xipkg/calls-ui';
18
+ import { ParticipantTile, DevicesBar, LessonRecordButton, ScreenShareButton, DisconnectButton, ScreenShareStageToggle, ParticipantPinToggle, tileOverlayButtonClassName, PermissionsDialog, TrackMutedIndicator, ParticipantName, RaisedHandIndicator, VideoTrack, ScreenShareZoom } from '@xipkg/calls-ui';
19
19
  import { Tooltip, TooltipTrigger, TooltipContent } from '@xipkg/tooltip';
20
20
  import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuItem } from '@xipkg/dropdown';
21
21
  import { useChatStore, Chat, ChatButton } from '@xipkg/calls-chat';
@@ -631,50 +631,70 @@ function CompactNavigationControls({
631
631
  onPrev,
632
632
  onNext,
633
633
  currentIndex,
634
- totalParticipants
634
+ totalParticipants,
635
+ persistent = false
635
636
  }) {
636
637
  const { t } = useTranslation("calls");
638
+ const hoverOnly = persistent ? "" : "sm:opacity-0 sm:group-hover:opacity-100";
637
639
  if (totalParticipants <= 1) {
638
640
  return null;
639
641
  }
640
642
  return /* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute inset-0", children: [
641
- /* @__PURE__ */ jsx("div", { className: "pointer-events-auto absolute top-2 left-1/2 -translate-x-1/2", children: /* @__PURE__ */ jsx("div", { className: "bg-background-subtle/80 flex gap-1 rounded-full px-2 py-1 backdrop-blur-sm transition-opacity duration-200 sm:opacity-0 sm:group-hover:opacity-100", children: Array.from({ length: totalParticipants }, (_, index) => /* @__PURE__ */ jsx(
642
- "button",
643
- {
644
- type: "button",
645
- onClick: () => {
646
- },
647
- className: `h-1.5 w-1.5 rounded-full transition-colors ${index === currentIndex ? "bg-text-primary" : "bg-text-primary/40 hover:bg-text-primary/70"}`,
648
- "aria-label": t("compactView.participant", { index: index + 1 })
649
- },
650
- index
651
- )) }) }),
652
- /* @__PURE__ */ jsx("div", { className: "pointer-events-auto absolute top-1/2 left-2 -translate-y-1/2 transition-opacity duration-200 sm:opacity-0 sm:group-hover:opacity-100", children: /* @__PURE__ */ jsxs(
653
- "button",
643
+ /* @__PURE__ */ jsx("div", { className: "pointer-events-auto absolute top-2 left-1/2 -translate-x-1/2", children: /* @__PURE__ */ jsx(
644
+ "div",
654
645
  {
655
- type: "button",
656
- disabled: !canPrev,
657
- onClick: onPrev,
658
- className: "bg-background-subtle/80 hover:bg-background-subtle fill-icon-primary z-10 flex items-center justify-center rounded-full p-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
659
- children: [
660
- /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" }),
661
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("compactView.prevParticipant") })
662
- ]
646
+ className: `bg-background-subtle/80 flex gap-1 rounded-full px-2 py-1 backdrop-blur-sm transition-opacity duration-200 ${hoverOnly}`,
647
+ children: Array.from({ length: totalParticipants }, (_, index) => /* @__PURE__ */ jsx(
648
+ "button",
649
+ {
650
+ type: "button",
651
+ onClick: () => {
652
+ },
653
+ className: `h-1.5 w-1.5 rounded-full transition-colors ${index === currentIndex ? "bg-text-primary" : "bg-text-primary/40 hover:bg-text-primary/70"}`,
654
+ "aria-label": t("compactView.participant", { index: index + 1 })
655
+ },
656
+ index
657
+ ))
663
658
  }
664
659
  ) }),
665
- /* @__PURE__ */ jsx("div", { className: "pointer-events-auto absolute top-1/2 right-2 -translate-y-1/2 transition-opacity duration-200 sm:opacity-0 sm:group-hover:opacity-100", children: /* @__PURE__ */ jsxs(
666
- "button",
660
+ /* @__PURE__ */ jsx(
661
+ "div",
667
662
  {
668
- type: "button",
669
- disabled: !canNext,
670
- onClick: onNext,
671
- className: "bg-background-subtle/80 hover:bg-background-subtle fill-icon-primary z-10 flex items-center justify-center rounded-full p-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
672
- children: [
673
- /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" }),
674
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("compactView.nextParticipant") })
675
- ]
663
+ className: `pointer-events-auto absolute top-1/2 left-2 -translate-y-1/2 transition-opacity duration-200 ${hoverOnly}`,
664
+ children: /* @__PURE__ */ jsxs(
665
+ "button",
666
+ {
667
+ type: "button",
668
+ disabled: !canPrev,
669
+ onClick: onPrev,
670
+ className: "bg-background-subtle/80 hover:bg-background-subtle fill-icon-primary z-10 flex items-center justify-center rounded-full p-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
671
+ children: [
672
+ /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" }),
673
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("compactView.prevParticipant") })
674
+ ]
675
+ }
676
+ )
676
677
  }
677
- ) })
678
+ ),
679
+ /* @__PURE__ */ jsx(
680
+ "div",
681
+ {
682
+ className: `pointer-events-auto absolute top-1/2 right-2 -translate-y-1/2 transition-opacity duration-200 ${hoverOnly}`,
683
+ children: /* @__PURE__ */ jsxs(
684
+ "button",
685
+ {
686
+ type: "button",
687
+ disabled: !canNext,
688
+ onClick: onNext,
689
+ className: "bg-background-subtle/80 hover:bg-background-subtle fill-icon-primary z-10 flex items-center justify-center rounded-full p-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
690
+ children: [
691
+ /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" }),
692
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("compactView.nextParticipant") })
693
+ ]
694
+ }
695
+ )
696
+ }
697
+ )
678
698
  ] });
679
699
  }
680
700
  function CompactMultiViewControls({
@@ -729,7 +749,9 @@ function CompactCallVideoArea({
729
749
  multiCanNext,
730
750
  onMultiPrev,
731
751
  onMultiNext,
732
- onAudioExpand
752
+ onAudioExpand,
753
+ tileWidth,
754
+ tileHeight
733
755
  }) {
734
756
  const { t } = useTranslation("calls");
735
757
  const pip = usePiP();
@@ -775,9 +797,9 @@ function CompactCallVideoArea({
775
797
  !isMobile && compactViewMode === "expanded" && "h-auto"
776
798
  ),
777
799
  style: isMobile ? void 0 : {
778
- width: COMPACT_PANEL_WIDTH_PX,
800
+ width: tileWidth != null ? "100%" : COMPACT_PANEL_WIDTH_PX,
779
801
  touchAction: "none",
780
- ...compactViewMode === "basic" ? { height: TILE_HEIGHT_16_9_PX } : {}
802
+ ...compactViewMode === "basic" ? { height: tileHeight ?? TILE_HEIGHT_16_9_PX } : {}
781
803
  },
782
804
  children: [
783
805
  isMobile ? currentParticipant ? /* @__PURE__ */ jsx("div", { className: "relative h-full min-h-0 w-full min-w-0", children: /* @__PURE__ */ jsx(
@@ -867,7 +889,8 @@ function CompactCallVideoArea({
867
889
  onPrev,
868
890
  onNext,
869
891
  currentIndex,
870
- totalParticipants
892
+ totalParticipants,
893
+ persistent: tileHeight != null
871
894
  }
872
895
  )
873
896
  ]
@@ -883,7 +906,8 @@ function CompactCallBottomBar({
883
906
  showBackToBoardButton,
884
907
  onBackToBoard,
885
908
  onMaximize,
886
- isTutor
909
+ isTutor,
910
+ stageCompact = false
887
911
  }) {
888
912
  const { t } = useTranslation("calls");
889
913
  const screenShareStage = useCallStore((state) => state.screenShareStageIdentity);
@@ -898,109 +922,120 @@ function CompactCallBottomBar({
898
922
  "bg-background-surface border-border-default flex items-center justify-center rounded-2xl border p-1",
899
923
  withOutShadows ? "" : "shadow-lg"
900
924
  );
901
- return /* @__PURE__ */ jsxs("div", { className: "flex h-[40px] shrink-0 flex-row pb-[max(0px,env(safe-area-inset-bottom))]", children: [
902
- /* @__PURE__ */ jsx("div", { className: barCn, children: /* @__PURE__ */ jsx(
903
- DevicesBar,
904
- {
905
- className: "h-[32px] w-[32px]",
906
- microTrack: devices.microTrack,
907
- microEnabled: devices.microEnabled,
908
- microTrackToggle: devices.microTrackToggle,
909
- videoTrack: devices.videoTrack,
910
- videoEnabled: devices.videoEnabled,
911
- videoTrackToggle: devices.videoTrackToggle
912
- }
913
- ) }),
914
- !isMobile && /* @__PURE__ */ jsx("div", { className: cn(barCn, "ml-1"), children: /* @__PURE__ */ jsxs(Tooltip, { children: [
915
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
916
- Button,
917
- {
918
- size: "icon",
919
- variant: "none",
920
- onClick: onViewModeToggle,
921
- className: "hover:bg-background-page text-text-primary h-8 w-8 rounded-xl p-0",
922
- "aria-label": viewModeToggleMeta.label,
923
- children: /* @__PURE__ */ jsx(ViewModeIcon, { className: "h-6 w-6" })
924
- }
925
- ) }),
926
- /* @__PURE__ */ jsx(TooltipContent, { children: viewModeToggleMeta.label })
927
- ] }) }),
928
- showBackToBoardButton && /* @__PURE__ */ jsx("div", { className: cn(barCn, "ml-1"), children: /* @__PURE__ */ jsxs(Tooltip, { children: [
929
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
930
- Button,
931
- {
932
- size: "icon",
933
- variant: "primary",
934
- className: "h-8 w-8 rounded-xl p-0",
935
- onClick: onBackToBoard,
936
- "aria-label": t("compactView.toBoardAria"),
937
- children: /* @__PURE__ */ jsx(WhiteBoard, { className: "fill-action-primary-text h-6 w-6" })
938
- }
939
- ) }),
940
- /* @__PURE__ */ jsx(TooltipContent, { children: t("compactView.toBoardTooltip") })
941
- ] }) }),
942
- /* @__PURE__ */ jsxs("div", { className: cn(barCn, "ml-auto"), children: [
943
- /* @__PURE__ */ jsx(LessonRecordButton, { className: "h-8 w-8" }),
944
- /* @__PURE__ */ jsx(ScreenShareButton, { className: "h-8 w-8" }),
945
- /* @__PURE__ */ jsx(ChatButton, { className: "h-8 w-8 min-w-8 rounded-xl" }),
946
- /* @__PURE__ */ jsx(RaiseHandButton, { className: "h-8 w-8 min-w-8" })
947
- ] }),
948
- /* @__PURE__ */ jsxs("div", { className: cn(barCn, "ml-1"), children: [
949
- !screenShareStage && (isTutor ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [
950
- /* @__PURE__ */ jsxs(Tooltip, { children: [
951
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
925
+ return /* @__PURE__ */ jsxs(
926
+ "div",
927
+ {
928
+ className: cn(
929
+ "flex h-10 shrink-0 flex-row",
930
+ stageCompact ? "max-w-full [scrollbar-width:none] overflow-x-auto [&::-webkit-scrollbar]:hidden [&>*]:shrink-0" : "pb-[max(0px,env(safe-area-inset-bottom))]"
931
+ ),
932
+ children: [
933
+ /* @__PURE__ */ jsx("div", { className: barCn, children: /* @__PURE__ */ jsx(
934
+ DevicesBar,
935
+ {
936
+ className: "h-[32px] w-[32px]",
937
+ microTrack: devices.microTrack,
938
+ microEnabled: devices.microEnabled,
939
+ microTrackToggle: devices.microTrackToggle,
940
+ videoTrack: devices.videoTrack,
941
+ videoEnabled: devices.videoEnabled,
942
+ videoTrackToggle: devices.videoTrackToggle
943
+ }
944
+ ) }),
945
+ !isMobile && /* @__PURE__ */ jsx("div", { className: cn(barCn, "ml-1"), children: /* @__PURE__ */ jsxs(Tooltip, { children: [
946
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
952
947
  Button,
953
948
  {
954
949
  size: "icon",
955
950
  variant: "none",
956
- className: "hover:bg-background-page text-text-primary relative m-0 h-8 w-8 rounded-xl p-0",
957
- children: /* @__PURE__ */ jsx(Maximize, { className: "fill-icon-primary" })
951
+ onClick: onViewModeToggle,
952
+ className: "hover:bg-background-page text-text-primary h-8 w-8 rounded-xl p-0",
953
+ "aria-label": viewModeToggleMeta.label,
954
+ children: /* @__PURE__ */ jsx(ViewModeIcon, { className: "h-6 w-6" })
958
955
  }
959
- ) }) }),
960
- /* @__PURE__ */ jsx(TooltipContent, { children: t("compactView.returnToConference") })
961
- ] }),
962
- /* @__PURE__ */ jsxs(DropdownMenuContent, { side: "top", align: "end", className: "z-1000 min-w-[200px]", children: [
963
- /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-text-secondary text-sm", children: t("compactView.returnMenuLabel") }),
964
- /* @__PURE__ */ jsx(
965
- DropdownMenuItem,
966
- {
967
- onClick: () => onMaximize(false),
968
- className: "text-text-primary cursor-pointer text-sm",
969
- children: t("compactView.returnOnlyMe")
970
- }
971
- ),
972
- /* @__PURE__ */ jsx(
973
- DropdownMenuItem,
956
+ ) }),
957
+ /* @__PURE__ */ jsx(TooltipContent, { children: viewModeToggleMeta.label })
958
+ ] }) }),
959
+ showBackToBoardButton && /* @__PURE__ */ jsx("div", { className: cn(barCn, "ml-1"), children: /* @__PURE__ */ jsxs(Tooltip, { children: [
960
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
961
+ Button,
974
962
  {
975
- onClick: () => onMaximize(true),
976
- className: "text-text-primary cursor-pointer text-sm",
977
- children: t("compactView.returnEveryone")
963
+ size: "icon",
964
+ variant: "primary",
965
+ className: "h-8 w-8 rounded-xl p-0",
966
+ onClick: onBackToBoard,
967
+ "aria-label": t("compactView.toBoardAria"),
968
+ children: /* @__PURE__ */ jsx(WhiteBoard, { className: "fill-action-primary-text h-6 w-6" })
978
969
  }
979
- )
970
+ ) }),
971
+ /* @__PURE__ */ jsx(TooltipContent, { children: t("compactView.toBoardTooltip") })
972
+ ] }) }),
973
+ /* @__PURE__ */ jsxs("div", { className: cn(barCn, "ml-auto"), children: [
974
+ /* @__PURE__ */ jsx(LessonRecordButton, { className: "h-8 w-8" }),
975
+ /* @__PURE__ */ jsx(ScreenShareButton, { className: "h-8 w-8" }),
976
+ /* @__PURE__ */ jsx(ChatButton, { className: "h-8 w-8 min-w-8 rounded-xl" }),
977
+ /* @__PURE__ */ jsx(RaiseHandButton, { className: "h-8 w-8 min-w-8" })
978
+ ] }),
979
+ /* @__PURE__ */ jsxs("div", { className: cn(barCn, "ml-1"), children: [
980
+ !screenShareStage && (isTutor ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [
981
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
982
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
983
+ Button,
984
+ {
985
+ size: "icon",
986
+ variant: "none",
987
+ className: "hover:bg-background-page text-text-primary relative m-0 h-8 w-8 rounded-xl p-0",
988
+ children: /* @__PURE__ */ jsx(Maximize, { className: "fill-icon-primary" })
989
+ }
990
+ ) }) }),
991
+ /* @__PURE__ */ jsx(TooltipContent, { children: t("compactView.returnToConference") })
992
+ ] }),
993
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { side: "top", align: "end", className: "z-1000 min-w-[200px]", children: [
994
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-text-secondary text-sm", children: t("compactView.returnMenuLabel") }),
995
+ /* @__PURE__ */ jsx(
996
+ DropdownMenuItem,
997
+ {
998
+ onClick: () => onMaximize(false),
999
+ className: "text-text-primary cursor-pointer text-sm",
1000
+ children: t("compactView.returnOnlyMe")
1001
+ }
1002
+ ),
1003
+ /* @__PURE__ */ jsx(
1004
+ DropdownMenuItem,
1005
+ {
1006
+ onClick: () => onMaximize(true),
1007
+ className: "text-text-primary cursor-pointer text-sm",
1008
+ children: t("compactView.returnEveryone")
1009
+ }
1010
+ )
1011
+ ] })
1012
+ ] }) : /* @__PURE__ */ jsxs(Tooltip, { children: [
1013
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
1014
+ Button,
1015
+ {
1016
+ size: "icon",
1017
+ variant: "none",
1018
+ onClick: () => onMaximize(false),
1019
+ className: "hover:bg-background-page text-text-primary relative m-0 h-8 w-8 rounded-xl p-0",
1020
+ children: /* @__PURE__ */ jsx(Maximize, { className: "fill-icon-primary" })
1021
+ }
1022
+ ) }),
1023
+ /* @__PURE__ */ jsx(TooltipContent, { children: t("compactView.returnToConference") })
1024
+ ] })),
1025
+ /* @__PURE__ */ jsx(DisconnectButton, { className: "h-[32px] w-[32px] rounded-xl" })
980
1026
  ] })
981
- ] }) : /* @__PURE__ */ jsxs(Tooltip, { children: [
982
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
983
- Button,
984
- {
985
- size: "icon",
986
- variant: "none",
987
- onClick: () => onMaximize(false),
988
- className: "hover:bg-background-page text-text-primary relative m-0 h-8 w-8 rounded-xl p-0",
989
- children: /* @__PURE__ */ jsx(Maximize, { className: "fill-icon-primary" })
990
- }
991
- ) }),
992
- /* @__PURE__ */ jsx(TooltipContent, { children: t("compactView.returnToConference") })
993
- ] })),
994
- /* @__PURE__ */ jsx(DisconnectButton, { className: "h-[32px] w-[32px] rounded-xl" })
995
- ] })
996
- ] });
1027
+ ]
1028
+ }
1029
+ );
997
1030
  }
998
1031
  var CompactCall = ({
999
1032
  saveUserChoices = true,
1000
1033
  withOutShadows = false,
1001
- freeDrag = false
1034
+ freeDrag = false,
1035
+ stageFrame = null
1002
1036
  }) => {
1003
1037
  const isMobile = usePhoneLayout();
1038
+ const lockCompactModes = stageFrame?.lockCompactModes === true;
1004
1039
  const { attributes, listeners, setNodeRef, transform } = useDraggable({
1005
1040
  id: freeDrag ? "screen-share-stage-compact" : "draggable-call",
1006
1041
  disabled: freeDrag ? false : isMobile
@@ -1141,20 +1176,26 @@ var CompactCall = ({
1141
1176
  ...freeDrag ? listeners : {},
1142
1177
  style: {
1143
1178
  ...style,
1144
- ...isMobile && !freeDrag ? {} : { width: COMPACT_PANEL_WIDTH_PX, touchAction: "none" }
1179
+ ...isMobile && !freeDrag ? {} : {
1180
+ width: stageFrame?.width ?? COMPACT_PANEL_WIDTH_PX,
1181
+ touchAction: "none",
1182
+ maxWidth: "100%"
1183
+ }
1145
1184
  },
1146
1185
  className: `compact-call-container flex flex-col ${isMobile && !freeDrag ? "w-full" : "touch-none [&_video]:touch-none"} ${freeDrag ? "cursor-grab active:cursor-grabbing" : ""}`,
1147
1186
  children: [
1148
1187
  /* @__PURE__ */ jsx(
1149
1188
  CompactCallVideoArea,
1150
1189
  {
1151
- isMobile,
1152
1190
  isCollapsed,
1153
1191
  onCollapsedChange: setIsCollapsed,
1154
1192
  withOutShadows,
1155
1193
  dragAttributes: freeDrag ? void 0 : attributes,
1156
1194
  dragListeners: freeDrag ? void 0 : listeners,
1157
- compactViewMode,
1195
+ compactViewMode: lockCompactModes ? "basic" : compactViewMode,
1196
+ tileWidth: stageFrame?.width,
1197
+ tileHeight: stageFrame?.tileHeight,
1198
+ isMobile: stageFrame ? false : isMobile,
1158
1199
  currentParticipant,
1159
1200
  currentAudioTrack: currentAudioTrack ?? null,
1160
1201
  totalParticipants,
@@ -1194,7 +1235,8 @@ var CompactCall = ({
1194
1235
  deviceGroups: cameraGroups
1195
1236
  }
1196
1237
  },
1197
- isMobile,
1238
+ isMobile: lockCompactModes || isMobile && stageFrame == null,
1239
+ stageCompact: lockCompactModes,
1198
1240
  compactViewMode,
1199
1241
  onViewModeToggle: () => setCompactViewMode(getNextCompactViewMode(compactViewMode)),
1200
1242
  showBackToBoardButton: !!showBackToBoardButton,
@@ -1207,23 +1249,115 @@ var CompactCall = ({
1207
1249
  }
1208
1250
  );
1209
1251
  };
1210
- var EDGE_PX2 = 16;
1211
- var STAGE_GONE_MS = 500;
1212
- function clampStagePosition(x, y, width, height) {
1213
- const maxX = Math.max(EDGE_PX2, window.innerWidth - width - EDGE_PX2);
1214
- const maxY = Math.max(EDGE_PX2, window.innerHeight - height - EDGE_PX2);
1252
+
1253
+ // src/ui/stageLayout.ts
1254
+ var EDGE_PX2 = 12;
1255
+ var CONTROLS_PX = 48;
1256
+ var EMPTY_INSETS = { top: 0, right: 0, bottom: 0, left: 0 };
1257
+ function viewportBox() {
1258
+ const viewport = window.visualViewport;
1215
1259
  return {
1216
- x: Math.min(Math.max(EDGE_PX2, x), maxX),
1217
- y: Math.min(Math.max(EDGE_PX2, y), maxY)
1260
+ width: viewport?.width ?? window.innerWidth,
1261
+ height: viewport?.height ?? window.innerHeight,
1262
+ offsetLeft: viewport?.offsetLeft ?? 0,
1263
+ offsetTop: viewport?.offsetTop ?? 0
1218
1264
  };
1219
1265
  }
1266
+ function readSafeInsets() {
1267
+ if (typeof document === "undefined") return EMPTY_INSETS;
1268
+ const probe = document.createElement("div");
1269
+ probe.style.cssText = "position:fixed;visibility:hidden;pointer-events:none;padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)";
1270
+ document.body.appendChild(probe);
1271
+ const style = getComputedStyle(probe);
1272
+ const insets = {
1273
+ top: Number.parseFloat(style.paddingTop) || 0,
1274
+ right: Number.parseFloat(style.paddingRight) || 0,
1275
+ bottom: Number.parseFloat(style.paddingBottom) || 0,
1276
+ left: Number.parseFloat(style.paddingLeft) || 0
1277
+ };
1278
+ probe.remove();
1279
+ return insets;
1280
+ }
1281
+ function computeStageFrame() {
1282
+ const box = viewportBox();
1283
+ const insets = readSafeInsets();
1284
+ const landscape = box.width > box.height;
1285
+ const handheld = box.width <= 720 || box.height <= 500;
1286
+ if (!handheld) {
1287
+ const width2 = 400;
1288
+ return {
1289
+ handheld: false,
1290
+ landscape,
1291
+ lockCompactModes: false,
1292
+ width: width2,
1293
+ tileHeight: Math.round(width2 * 9 / 16),
1294
+ insets
1295
+ };
1296
+ }
1297
+ const maxWidth = Math.max(220, box.width - insets.left - insets.right - EDGE_PX2 * 2);
1298
+ const width = Math.min(400, maxWidth);
1299
+ const naturalTile = Math.round(width * 9 / 16);
1300
+ const maxPanel = box.height * 0.36;
1301
+ const tileHeight = landscape ? naturalTile : Math.max(84, Math.min(naturalTile, Math.floor(maxPanel - CONTROLS_PX)));
1302
+ return { handheld: true, landscape, lockCompactModes: !landscape, width, tileHeight, insets };
1303
+ }
1304
+ function sameStageFrame(left, right) {
1305
+ return left.handheld === right.handheld && left.landscape === right.landscape && left.lockCompactModes === right.lockCompactModes && left.width === right.width && left.tileHeight === right.tileHeight && left.insets.top === right.insets.top && left.insets.right === right.insets.right && left.insets.bottom === right.insets.bottom && left.insets.left === right.insets.left;
1306
+ }
1307
+ function clampStagePosition(x, y, width, height, insets) {
1308
+ const box = viewportBox();
1309
+ const minX = box.offsetLeft + insets.left + 8;
1310
+ const minY = box.offsetTop + insets.top + 8;
1311
+ const maxX = Math.max(minX, box.offsetLeft + box.width - insets.right - width - 8);
1312
+ const maxY = Math.max(minY, box.offsetTop + box.height - insets.bottom - height - 8);
1313
+ return {
1314
+ x: Math.min(Math.max(minX, x), maxX),
1315
+ y: Math.min(Math.max(minY, y), maxY)
1316
+ };
1317
+ }
1318
+ function defaultStagePosition(frame) {
1319
+ const box = viewportBox();
1320
+ const panelHeight = frame.tileHeight + CONTROLS_PX + 8;
1321
+ if (!frame.handheld) {
1322
+ return clampStagePosition(
1323
+ EDGE_PX2 + frame.insets.left,
1324
+ EDGE_PX2 + frame.insets.top,
1325
+ frame.width,
1326
+ panelHeight,
1327
+ frame.insets
1328
+ );
1329
+ }
1330
+ const y = box.offsetTop + box.height - frame.insets.bottom - EDGE_PX2 - panelHeight;
1331
+ const x = frame.landscape ? box.offsetLeft + frame.insets.left + EDGE_PX2 : box.offsetLeft + (box.width - frame.width) / 2;
1332
+ return clampStagePosition(x, y, frame.width, panelHeight, frame.insets);
1333
+ }
1334
+ function stagePanelHeight(frame) {
1335
+ return frame.tileHeight + CONTROLS_PX + 8;
1336
+ }
1337
+ var STAGE_GONE_MS = 500;
1338
+ function initialStageFrame() {
1339
+ if (typeof window === "undefined") {
1340
+ return {
1341
+ handheld: false,
1342
+ landscape: false,
1343
+ lockCompactModes: false,
1344
+ width: 400,
1345
+ tileHeight: 225,
1346
+ insets: { top: 0, right: 0, bottom: 0, left: 0 }
1347
+ };
1348
+ }
1349
+ return computeStageFrame();
1350
+ }
1220
1351
  var ScreenShareStage = ({ identity }) => {
1221
1352
  const { t } = useTranslation("calls");
1222
1353
  const updateStore = useCallStore((state) => state.updateStore);
1223
1354
  const closePiP = usePiP()?.closePiP;
1224
1355
  const overlayRef = useRef(null);
1225
1356
  const panelRef = useRef(null);
1226
- const [position, setPosition] = useState({ x: EDGE_PX2, y: EDGE_PX2 });
1357
+ const movedRef = useRef(false);
1358
+ const landscapeRef = useRef(null);
1359
+ const [frame, setFrame] = useState(initialStageFrame);
1360
+ const [position, setPosition] = useState(() => defaultStagePosition(initialStageFrame()));
1227
1361
  const tracks = useTracks([{ source: Track.Source.ScreenShare, withPlaceholder: false }], {
1228
1362
  onlySubscribed: false
1229
1363
  });
@@ -1284,34 +1418,58 @@ var ScreenShareStage = ({ identity }) => {
1284
1418
  };
1285
1419
  }, []);
1286
1420
  useLayoutEffect(() => {
1287
- const node = panelRef.current;
1288
- if (!node) return void 0;
1289
- const clamp = () => {
1290
- const current = panelRef.current;
1291
- if (!current) return;
1421
+ const sync = () => {
1422
+ const next = computeStageFrame();
1423
+ setFrame((prev) => sameStageFrame(prev, next) ? prev : next);
1424
+ const flipped = landscapeRef.current !== null && landscapeRef.current !== next.landscape;
1425
+ landscapeRef.current = next.landscape;
1426
+ if (flipped) movedRef.current = false;
1292
1427
  setPosition((prev) => {
1293
- const next = clampStagePosition(prev.x, prev.y, current.offsetWidth, current.offsetHeight);
1294
- if (next.x === prev.x && next.y === prev.y) return prev;
1295
- return next;
1428
+ if (!movedRef.current) {
1429
+ const placed = defaultStagePosition(next);
1430
+ if (placed.x === prev.x && placed.y === prev.y) return prev;
1431
+ return placed;
1432
+ }
1433
+ const node2 = panelRef.current;
1434
+ const clamped = clampStagePosition(
1435
+ prev.x,
1436
+ prev.y,
1437
+ node2?.offsetWidth ?? next.width,
1438
+ node2?.offsetHeight ?? stagePanelHeight(next),
1439
+ next.insets
1440
+ );
1441
+ if (clamped.x === prev.x && clamped.y === prev.y) return prev;
1442
+ return clamped;
1296
1443
  });
1297
1444
  };
1298
- clamp();
1299
- const observer = new ResizeObserver(clamp);
1300
- observer.observe(node);
1301
- window.addEventListener("resize", clamp);
1445
+ sync();
1446
+ const viewport = window.visualViewport;
1447
+ window.addEventListener("resize", sync);
1448
+ window.addEventListener("orientationchange", sync);
1449
+ viewport?.addEventListener("resize", sync);
1450
+ viewport?.addEventListener("scroll", sync);
1451
+ const node = panelRef.current;
1452
+ const observer = new ResizeObserver(sync);
1453
+ if (node) observer.observe(node);
1302
1454
  return () => {
1455
+ window.removeEventListener("resize", sync);
1456
+ window.removeEventListener("orientationchange", sync);
1457
+ viewport?.removeEventListener("resize", sync);
1458
+ viewport?.removeEventListener("scroll", sync);
1303
1459
  observer.disconnect();
1304
- window.removeEventListener("resize", clamp);
1305
1460
  };
1306
1461
  }, []);
1307
1462
  const handleDragEnd = (event) => {
1463
+ movedRef.current = true;
1308
1464
  const node = panelRef.current;
1465
+ const current = computeStageFrame();
1309
1466
  setPosition((prev) => {
1310
1467
  const next = clampStagePosition(
1311
1468
  prev.x + event.delta.x,
1312
1469
  prev.y + event.delta.y,
1313
- node?.offsetWidth ?? COMPACT_PANEL_WIDTH_PX,
1314
- node?.offsetHeight ?? 320
1470
+ node?.offsetWidth ?? current.width,
1471
+ node?.offsetHeight ?? stagePanelHeight(current),
1472
+ current.insets
1315
1473
  );
1316
1474
  if (next.x === prev.x && next.y === prev.y) return prev;
1317
1475
  return next;
@@ -1331,7 +1489,14 @@ var ScreenShareStage = ({ identity }) => {
1331
1489
  className: "screen-share-stage fixed inset-0 z-[2100] bg-black outline-none",
1332
1490
  children: [
1333
1491
  /* @__PURE__ */ jsxs("div", { className: "relative h-full w-full", children: [
1334
- track && publication ? /* @__PURE__ */ jsx(ScreenShareZoom, { trackRef: track, className: "rounded-none", children: /* @__PURE__ */ jsx(
1492
+ track && publication ? frame.handheld ? /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black", children: /* @__PURE__ */ jsx(
1493
+ VideoTrack,
1494
+ {
1495
+ trackRef: track,
1496
+ manageSubscription: false,
1497
+ className: "h-full w-full bg-black object-contain"
1498
+ }
1499
+ ) }) : /* @__PURE__ */ jsx(ScreenShareZoom, { trackRef: track, className: "rounded-none", children: /* @__PURE__ */ jsx(
1335
1500
  VideoTrack,
1336
1501
  {
1337
1502
  trackRef: track,
@@ -1339,20 +1504,43 @@ var ScreenShareStage = ({ identity }) => {
1339
1504
  className: "h-full w-full bg-black object-contain"
1340
1505
  }
1341
1506
  ) }) : null,
1342
- track ? /* @__PURE__ */ jsxs("div", { className: "bg-background-surface/80 pointer-events-none absolute bottom-4 left-4 z-20 flex h-6 items-center gap-1.5 rounded-lg px-1.5 py-1 backdrop-blur", children: [
1343
- /* @__PURE__ */ jsx(ScreenShareIcon, { style: { marginRight: "0.25rem" } }),
1344
- /* @__PURE__ */ jsxs("span", { className: "text-xs-base-size text-text-primary leading-[16px]", children: [
1345
- t("participant.screenSharePrefix"),
1346
- sharerName ? /* @__PURE__ */ jsxs(Fragment, { children: [
1347
- "\xA0",
1348
- sharerName
1349
- ] }) : null
1350
- ] })
1351
- ] }) : null,
1352
- /* @__PURE__ */ jsxs("div", { className: "absolute top-3 right-3 z-40 flex items-center gap-1", children: [
1353
- /* @__PURE__ */ jsx(ScreenShareStageToggle, { trackRef: track ?? void 0, inline: true }),
1354
- track ? /* @__PURE__ */ jsx(ParticipantPinToggle, { trackRef: track, inline: true }) : null
1355
- ] })
1507
+ track ? /* @__PURE__ */ jsxs(
1508
+ "div",
1509
+ {
1510
+ className: "bg-background-surface/80 pointer-events-none absolute z-20 flex h-6 max-w-[calc(100%-96px)] items-center gap-1.5 rounded-lg px-1.5 py-1 backdrop-blur",
1511
+ style: frame.handheld ? {
1512
+ top: "max(12px, env(safe-area-inset-top))",
1513
+ left: "max(12px, env(safe-area-inset-left))"
1514
+ } : {
1515
+ bottom: "max(16px, env(safe-area-inset-bottom))",
1516
+ left: "max(16px, env(safe-area-inset-left))"
1517
+ },
1518
+ children: [
1519
+ /* @__PURE__ */ jsx(ScreenShareIcon, { style: { marginRight: "0.25rem" } }),
1520
+ /* @__PURE__ */ jsxs("span", { className: "text-xs-base-size text-text-primary truncate leading-[16px]", children: [
1521
+ t("participant.screenSharePrefix"),
1522
+ sharerName ? /* @__PURE__ */ jsxs(Fragment, { children: [
1523
+ "\xA0",
1524
+ sharerName
1525
+ ] }) : null
1526
+ ] })
1527
+ ]
1528
+ }
1529
+ ) : null,
1530
+ /* @__PURE__ */ jsxs(
1531
+ "div",
1532
+ {
1533
+ className: "absolute z-40 flex items-center gap-1",
1534
+ style: {
1535
+ top: "max(12px, env(safe-area-inset-top))",
1536
+ right: "max(12px, env(safe-area-inset-right))"
1537
+ },
1538
+ children: [
1539
+ /* @__PURE__ */ jsx(ScreenShareStageToggle, { trackRef: track ?? void 0, inline: true }),
1540
+ track ? /* @__PURE__ */ jsx(ParticipantPinToggle, { trackRef: track, inline: true }) : null
1541
+ ]
1542
+ }
1543
+ )
1356
1544
  ] }),
1357
1545
  /* @__PURE__ */ jsx(
1358
1546
  DndContext,
@@ -1367,12 +1555,28 @@ var ScreenShareStage = ({ identity }) => {
1367
1555
  ref: panelRef,
1368
1556
  className: "absolute z-30 w-max",
1369
1557
  style: { left: position.x, top: position.y },
1370
- children: /* @__PURE__ */ jsx(CompactCall, { freeDrag: true })
1558
+ children: /* @__PURE__ */ jsx(
1559
+ CompactCall,
1560
+ {
1561
+ freeDrag: true,
1562
+ stageFrame: frame.handheld ? {
1563
+ width: frame.width,
1564
+ tileHeight: frame.tileHeight,
1565
+ lockCompactModes: frame.lockCompactModes
1566
+ } : null
1567
+ }
1568
+ )
1371
1569
  }
1372
1570
  )
1373
1571
  }
1374
1572
  ),
1375
- /* @__PURE__ */ jsx(Chat, { compactPositionStyle: { top: 56, right: 16, bottom: 16, zIndex: 20 } })
1573
+ /* @__PURE__ */ jsx(
1574
+ Chat,
1575
+ {
1576
+ forceSheet: frame.handheld,
1577
+ compactPositionStyle: frame.handheld ? void 0 : { top: 56, right: 16, bottom: 16, zIndex: 20 }
1578
+ }
1579
+ )
1376
1580
  ]
1377
1581
  }
1378
1582
  ),