@xipkg/calls-compactview 0.1.53 → 0.1.55

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.d.ts CHANGED
@@ -6,9 +6,11 @@ type CompactViewPropsT = {
6
6
  };
7
7
  declare const CompactView: ({ children }: CompactViewPropsT) => react.JSX.Element;
8
8
 
9
- declare const CompactCall: ({ saveUserChoices, withOutShadows }: {
10
- saveUserChoices?: boolean | undefined;
11
- withOutShadows?: boolean | undefined;
9
+ declare const CompactCall: ({ saveUserChoices, withOutShadows, freeDrag, }: {
10
+ saveUserChoices?: boolean;
11
+ withOutShadows?: boolean;
12
+ /** Панель тащится целиком в любую точку, в том числе на телефоне */
13
+ freeDrag?: boolean;
12
14
  }) => react.JSX.Element;
13
15
 
14
16
  interface CompactNavigationControlsProps {
@@ -48,6 +50,10 @@ type PiPProviderProps = {
48
50
  */
49
51
  declare function PiPProvider({ children }: PiPProviderProps): react.JSX.Element;
50
52
 
53
+ /**
54
+ * Максимальная высота контейнера compact call (видео + нижняя панель).
55
+ * На доске учитывает отступы тулбара из CSS-переменных `--board-chrome-*`.
56
+ */
51
57
  declare function useCompactAvailableHeight(isOnBoardPage: boolean): number;
52
58
 
53
59
  declare const useCompactNavigation: () => {
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  import '@xipkg/calls-ui/grid.css';
2
- import { createContext, useMemo, useState, useEffect, useRef, useCallback, useContext, useLayoutEffect } from 'react';
2
+ import { createContext, useState, useEffect, useMemo, useRef, useCallback, useContext, useLayoutEffect } from 'react';
3
3
  import { useDraggable, PointerSensor, useSensors, useSensor, DndContext, TouchSensor, DragOverlay, useDndMonitor, useDroppable } from '@dnd-kit/core';
4
4
  import { useRoom, useCallsNavigation, useCalls } from '@xipkg/calls-providers';
5
5
  import { restrictToWindowEdges } from '@dnd-kit/modifiers';
6
6
  import { CSS } from '@dnd-kit/utilities';
7
- import { useTracks, usePersistentUserChoices, useLocalParticipant, useTrackToggle, useAudioWaveform } from '@livekit/components-react';
7
+ import { useTracks, usePersistentUserChoices, useLocalParticipant, useTrackToggle, useAudioWaveform, ScreenShareIcon } from '@livekit/components-react';
8
8
  import { Track, RemoteTrackPublication } from 'livekit-client';
9
9
  import { applyPinsFirst, useCallStore } from '@xipkg/calls-store';
10
10
  import { useScreenShareCleanup, useSortedTracks, useClassroomPins, useVideoBlur, useDeviceMenuGroups, useSyncModeToOthers, useParticipantJoinSync, useParticipantSounds } from '@xipkg/calls-hooks';
@@ -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, ParticipantPinToggle, tileOverlayButtonClassName, PermissionsDialog, TrackMutedIndicator, ParticipantName, RaisedHandIndicator } from '@xipkg/calls-ui';
18
+ import { ParticipantTile, DevicesBar, LessonRecordButton, ScreenShareButton, DisconnectButton, ScreenShareStageToggle, ParticipantPinToggle, tileOverlayButtonClassName, PermissionsDialog, TrackMutedIndicator, ParticipantName, RaisedHandIndicator, ScreenShareZoom, VideoTrack } 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';
@@ -120,21 +120,91 @@ function getPipHeightForMode(mode, participantCount) {
120
120
  return getPipWindowHeight(mode, 1);
121
121
  }
122
122
 
123
- // src/hooks/useCompactAvailableHeight.ts
124
- function calcAvailableHeight(isOnBoardPage) {
125
- if (typeof window === "undefined") return 400;
126
- const bottomOffset = isOnBoardPage ? COMPACT_BOTTOM_OFFSET_BOARD_PX : BOTTOM_OFFSET_DEFAULT_PX;
127
- return window.innerHeight - HEADER_HEIGHT_PX - bottomOffset;
123
+ // src/boardChromeInsets.ts
124
+ var TOP_VAR = "--board-chrome-top";
125
+ var RIGHT_VAR = "--board-chrome-right";
126
+ var BOTTOM_VAR = "--board-chrome-bottom";
127
+ var LEFT_VAR = "--board-chrome-left";
128
+ var EDGE_PX = 16;
129
+ var CHAT_TOP_PX = 128;
130
+ var CHAT_LEFT_EXTRA_PX = 72;
131
+ function readPx(name) {
132
+ if (typeof document === "undefined") return null;
133
+ const raw = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
134
+ if (!raw) return null;
135
+ const value = Number.parseFloat(raw);
136
+ return Number.isFinite(value) ? value : null;
137
+ }
138
+ function defaultBoardChromeInsets(isBoardPage) {
139
+ return {
140
+ top: HEADER_HEIGHT_PX,
141
+ right: EDGE_PX,
142
+ bottom: isBoardPage ? COMPACT_BOTTOM_OFFSET_BOARD_PX : BOTTOM_OFFSET_DEFAULT_PX,
143
+ left: EDGE_PX
144
+ };
145
+ }
146
+ function readBoardChromeInsets(isBoardPage) {
147
+ const fallback = defaultBoardChromeInsets(isBoardPage);
148
+ return {
149
+ top: readPx(TOP_VAR) ?? fallback.top,
150
+ right: readPx(RIGHT_VAR) ?? fallback.right,
151
+ bottom: readPx(BOTTOM_VAR) ?? fallback.bottom,
152
+ left: readPx(LEFT_VAR) ?? fallback.left
153
+ };
154
+ }
155
+ function sameInsets(left, right) {
156
+ return left.top === right.top && left.right === right.right && left.bottom === right.bottom && left.left === right.left;
157
+ }
158
+ function useBoardChromeInsets(isBoardPage) {
159
+ const [insets, setInsets] = useState(() => readBoardChromeInsets(isBoardPage));
160
+ useLayoutEffect(() => {
161
+ const update = () => {
162
+ const next = readBoardChromeInsets(isBoardPage);
163
+ setInsets((current) => sameInsets(current, next) ? current : next);
164
+ };
165
+ update();
166
+ const observer = new MutationObserver(update);
167
+ observer.observe(document.documentElement, {
168
+ attributes: true,
169
+ attributeFilter: ["style"]
170
+ });
171
+ return () => observer.disconnect();
172
+ }, [isBoardPage]);
173
+ return insets;
128
174
  }
175
+ function cornerStyle(corner, insets) {
176
+ switch (corner) {
177
+ case "top-left":
178
+ return { top: insets.top, left: insets.left };
179
+ case "top-right":
180
+ return { top: insets.top, right: insets.right };
181
+ case "bottom-left":
182
+ return { bottom: insets.bottom, left: insets.left };
183
+ case "bottom-right":
184
+ return { bottom: insets.bottom, right: insets.right };
185
+ }
186
+ }
187
+ function chatPositionStyle(corner, insets) {
188
+ const oppositeLeft = corner === "top-right" || corner === "bottom-right";
189
+ const top = CHAT_TOP_PX + Math.max(0, insets.top - HEADER_HEIGHT_PX);
190
+ return oppositeLeft ? { top, bottom: insets.bottom, left: insets.left + CHAT_LEFT_EXTRA_PX } : { top, bottom: insets.bottom, right: insets.right };
191
+ }
192
+
193
+ // src/hooks/useCompactAvailableHeight.ts
129
194
  function useCompactAvailableHeight(isOnBoardPage) {
130
- const initialHeight = useMemo(() => calcAvailableHeight(isOnBoardPage), [isOnBoardPage]);
131
- const [availableHeight, setAvailableHeight] = useState(initialHeight);
195
+ const insets = useBoardChromeInsets(isOnBoardPage);
196
+ const [availableHeight, setAvailableHeight] = useState(() => {
197
+ if (typeof window === "undefined") return 400;
198
+ return window.innerHeight - insets.top - insets.bottom;
199
+ });
132
200
  useEffect(() => {
133
- setAvailableHeight(initialHeight);
134
- const onResize = () => setAvailableHeight(calcAvailableHeight(isOnBoardPage));
135
- window.addEventListener("resize", onResize);
136
- return () => window.removeEventListener("resize", onResize);
137
- }, [isOnBoardPage, initialHeight]);
201
+ const update = () => {
202
+ setAvailableHeight(window.innerHeight - insets.top - insets.bottom);
203
+ };
204
+ update();
205
+ window.addEventListener("resize", update);
206
+ return () => window.removeEventListener("resize", update);
207
+ }, [insets.top, insets.bottom]);
138
208
  return availableHeight;
139
209
  }
140
210
  var useCompactNavigation = () => {
@@ -162,8 +232,13 @@ var useCompactNavigation = () => {
162
232
  const baseSorted = useSortedTracks(participants, 1);
163
233
  const { pins } = useClassroomPins();
164
234
  const sorted = useMemo(() => applyPinsFirst(baseSorted, pins), [baseSorted, pins]);
165
- const currentParticipant = sorted[currentParticipantIndex] || null;
166
- const totalParticipants = sorted.length;
235
+ const screenShareStageIdentity = useCallStore((state) => state.screenShareStageIdentity);
236
+ const visibleSorted = useMemo(() => {
237
+ if (!screenShareStageIdentity) return sorted;
238
+ return sorted.filter((trackRef) => trackRef.source !== Track.Source.ScreenShare);
239
+ }, [screenShareStageIdentity, sorted]);
240
+ const currentParticipant = visibleSorted[currentParticipantIndex] || null;
241
+ const totalParticipants = visibleSorted.length;
167
242
  const canGoNext = currentParticipantIndex < totalParticipants - 1;
168
243
  const canGoPrev = currentParticipantIndex > 0;
169
244
  const goToNext = () => {
@@ -192,9 +267,13 @@ var useCompactNavigation = () => {
192
267
  setCurrentParticipantIndex(0);
193
268
  }
194
269
  }, [pinsKey, pins.length]);
270
+ useEffect(() => {
271
+ if (!screenShareStageIdentity) return;
272
+ setCurrentParticipantIndex(0);
273
+ }, [screenShareStageIdentity]);
195
274
  return {
196
275
  currentParticipant,
197
- participants: sorted,
276
+ participants: visibleSorted,
198
277
  currentIndex: currentParticipantIndex,
199
278
  totalParticipants,
200
279
  canGoNext,
@@ -484,7 +563,8 @@ function CompactCallCollapsedBar({
484
563
  participant,
485
564
  audioTrack,
486
565
  onExpand,
487
- className
566
+ className,
567
+ style
488
568
  }) {
489
569
  const { t } = useTranslation("calls");
490
570
  const { bars } = useAudioWaveform(audioTrack ?? void 0, {
@@ -495,6 +575,7 @@ function CompactCallCollapsedBar({
495
575
  return /* @__PURE__ */ jsxs(
496
576
  "div",
497
577
  {
578
+ style,
498
579
  className: cn(
499
580
  "bg-background-surface border-border-default flex items-center gap-2 rounded-2xl border px-2 py-2 shadow-lg",
500
581
  className
@@ -751,6 +832,7 @@ function CompactCallVideoArea({
751
832
  }
752
833
  ) }) : emptyState,
753
834
  showPinPiPOverlay && /* @__PURE__ */ jsxs("div", { className: "absolute top-2 right-2 z-10 flex items-center gap-1", children: [
835
+ overlayTrackRef.source === Track.Source.ScreenShare ? /* @__PURE__ */ jsx(ScreenShareStageToggle, { trackRef: overlayTrackRef, inline: true }) : null,
754
836
  /* @__PURE__ */ jsx(ParticipantPinToggle, { trackRef: overlayTrackRef, inline: true }),
755
837
  showPiPButton && /* @__PURE__ */ jsxs(Tooltip, { children: [
756
838
  /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
@@ -804,6 +886,7 @@ function CompactCallBottomBar({
804
886
  isTutor
805
887
  }) {
806
888
  const { t } = useTranslation("calls");
889
+ const screenShareStage = useCallStore((state) => state.screenShareStageIdentity);
807
890
  const nextViewMode = getNextCompactViewMode(compactViewMode);
808
891
  const viewModeToggleMeta = {
809
892
  expanded: { Icon: Users, label: t("compactView.mode.expanded") },
@@ -863,7 +946,7 @@ function CompactCallBottomBar({
863
946
  /* @__PURE__ */ jsx(RaiseHandButton, { className: "h-8 w-8 min-w-8" })
864
947
  ] }),
865
948
  /* @__PURE__ */ jsxs("div", { className: cn(barCn, "ml-1"), children: [
866
- isTutor ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [
949
+ !screenShareStage && (isTutor ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [
867
950
  /* @__PURE__ */ jsxs(Tooltip, { children: [
868
951
  /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
869
952
  Button,
@@ -907,18 +990,22 @@ function CompactCallBottomBar({
907
990
  }
908
991
  ) }),
909
992
  /* @__PURE__ */ jsx(TooltipContent, { children: t("compactView.returnToConference") })
910
- ] }),
993
+ ] })),
911
994
  /* @__PURE__ */ jsx(DisconnectButton, { className: "h-[32px] w-[32px] rounded-xl" })
912
995
  ] })
913
996
  ] });
914
997
  }
915
- var CompactCall = ({ saveUserChoices = true, withOutShadows = false }) => {
998
+ var CompactCall = ({
999
+ saveUserChoices = true,
1000
+ withOutShadows = false,
1001
+ freeDrag = false
1002
+ }) => {
916
1003
  const isMobile = usePhoneLayout();
917
1004
  const { attributes, listeners, setNodeRef, transform } = useDraggable({
918
- id: "draggable-call",
919
- disabled: isMobile
1005
+ id: freeDrag ? "screen-share-stage-compact" : "draggable-call",
1006
+ disabled: freeDrag ? false : isMobile
920
1007
  });
921
- const style = isMobile ? void 0 : { transform: CSS.Translate.toString(transform) };
1008
+ const style = !freeDrag && isMobile ? void 0 : { transform: CSS.Translate.toString(transform) };
922
1009
  const { saveAudioInputEnabled, saveVideoInputEnabled } = usePersistentUserChoices({
923
1010
  preventSave: !saveUserChoices
924
1011
  });
@@ -1051,11 +1138,12 @@ var CompactCall = ({ saveUserChoices = true, withOutShadows = false }) => {
1051
1138
  "div",
1052
1139
  {
1053
1140
  ref: setNodeRef,
1141
+ ...freeDrag ? listeners : {},
1054
1142
  style: {
1055
1143
  ...style,
1056
- ...isMobile ? {} : { width: COMPACT_PANEL_WIDTH_PX, touchAction: "none" }
1144
+ ...isMobile && !freeDrag ? {} : { width: COMPACT_PANEL_WIDTH_PX, touchAction: "none" }
1057
1145
  },
1058
- className: `compact-call-container flex flex-col ${isMobile ? "w-full" : "touch-none [&_video]:touch-none"}`,
1146
+ className: `compact-call-container flex flex-col ${isMobile && !freeDrag ? "w-full" : "touch-none [&_video]:touch-none"} ${freeDrag ? "cursor-grab active:cursor-grabbing" : ""}`,
1059
1147
  children: [
1060
1148
  /* @__PURE__ */ jsx(
1061
1149
  CompactCallVideoArea,
@@ -1064,8 +1152,8 @@ var CompactCall = ({ saveUserChoices = true, withOutShadows = false }) => {
1064
1152
  isCollapsed,
1065
1153
  onCollapsedChange: setIsCollapsed,
1066
1154
  withOutShadows,
1067
- dragAttributes: attributes,
1068
- dragListeners: listeners,
1155
+ dragAttributes: freeDrag ? void 0 : attributes,
1156
+ dragListeners: freeDrag ? void 0 : listeners,
1069
1157
  compactViewMode,
1070
1158
  currentParticipant,
1071
1159
  currentAudioTrack: currentAudioTrack ?? null,
@@ -1119,11 +1207,177 @@ var CompactCall = ({ saveUserChoices = true, withOutShadows = false }) => {
1119
1207
  }
1120
1208
  );
1121
1209
  };
1122
- var OPPOSITE_CORNER = {
1123
- "top-left": "top-right",
1124
- "top-right": "top-left",
1125
- "bottom-left": "bottom-right",
1126
- "bottom-right": "bottom-left"
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);
1215
+ return {
1216
+ x: Math.min(Math.max(EDGE_PX2, x), maxX),
1217
+ y: Math.min(Math.max(EDGE_PX2, y), maxY)
1218
+ };
1219
+ }
1220
+ var ScreenShareStage = ({ identity }) => {
1221
+ const { t } = useTranslation("calls");
1222
+ const updateStore = useCallStore((state) => state.updateStore);
1223
+ const closePiP = usePiP()?.closePiP;
1224
+ const overlayRef = useRef(null);
1225
+ const panelRef = useRef(null);
1226
+ const [position, setPosition] = useState({ x: EDGE_PX2, y: EDGE_PX2 });
1227
+ const tracks = useTracks([{ source: Track.Source.ScreenShare, withPlaceholder: false }], {
1228
+ onlySubscribed: false
1229
+ });
1230
+ const track = tracks.find((item) => item.participant.identity === identity) ?? null;
1231
+ const publication = track?.publication;
1232
+ const hasTrack = Boolean(publication);
1233
+ const sensors = useSensors(
1234
+ useSensor(CompactPointerSensor, { activationConstraint: { distance: 8 } }),
1235
+ useSensor(CompactTouchSensor, { activationConstraint: { distance: 8 } })
1236
+ );
1237
+ useEffect(() => {
1238
+ closePiP?.();
1239
+ }, [closePiP]);
1240
+ useEffect(() => {
1241
+ if (!(publication instanceof RemoteTrackPublication) || publication.isSubscribed) return;
1242
+ publication.setSubscribed(true);
1243
+ }, [publication]);
1244
+ useEffect(() => {
1245
+ if (hasTrack) return void 0;
1246
+ const timeoutId = window.setTimeout(() => {
1247
+ if (useCallStore.getState().screenShareStageIdentity === identity) {
1248
+ useCallStore.getState().updateStore("screenShareStageIdentity", null);
1249
+ }
1250
+ }, STAGE_GONE_MS);
1251
+ return () => window.clearTimeout(timeoutId);
1252
+ }, [hasTrack, identity]);
1253
+ useEffect(() => {
1254
+ const onKeyDown = (event) => {
1255
+ if (event.key !== "Escape" || event.defaultPrevented) return;
1256
+ event.preventDefault();
1257
+ updateStore("screenShareStageIdentity", null);
1258
+ };
1259
+ window.addEventListener("keydown", onKeyDown);
1260
+ return () => window.removeEventListener("keydown", onKeyDown);
1261
+ }, [updateStore]);
1262
+ useEffect(() => {
1263
+ const portalNode = overlayRef.current;
1264
+ if (!portalNode) return void 0;
1265
+ const hidden = [];
1266
+ Array.from(document.body.children).forEach((node) => {
1267
+ if (!(node instanceof HTMLElement)) return;
1268
+ if (node === portalNode || node.contains(portalNode)) return;
1269
+ if (node.hasAttribute("data-sonner-toaster")) return;
1270
+ if (node.hasAttribute("inert")) return;
1271
+ node.setAttribute("inert", "");
1272
+ hidden.push(node);
1273
+ });
1274
+ const { body, documentElement } = document;
1275
+ const previousBodyOverflow = body.style.overflow;
1276
+ const previousHtmlOverflow = documentElement.style.overflow;
1277
+ body.style.overflow = "hidden";
1278
+ documentElement.style.overflow = "hidden";
1279
+ portalNode?.focus();
1280
+ return () => {
1281
+ hidden.forEach((node) => node.removeAttribute("inert"));
1282
+ body.style.overflow = previousBodyOverflow;
1283
+ documentElement.style.overflow = previousHtmlOverflow;
1284
+ };
1285
+ }, []);
1286
+ useLayoutEffect(() => {
1287
+ const node = panelRef.current;
1288
+ if (!node) return void 0;
1289
+ const clamp = () => {
1290
+ const current = panelRef.current;
1291
+ if (!current) return;
1292
+ 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;
1296
+ });
1297
+ };
1298
+ clamp();
1299
+ const observer = new ResizeObserver(clamp);
1300
+ observer.observe(node);
1301
+ window.addEventListener("resize", clamp);
1302
+ return () => {
1303
+ observer.disconnect();
1304
+ window.removeEventListener("resize", clamp);
1305
+ };
1306
+ }, []);
1307
+ const handleDragEnd = (event) => {
1308
+ const node = panelRef.current;
1309
+ setPosition((prev) => {
1310
+ const next = clampStagePosition(
1311
+ prev.x + event.delta.x,
1312
+ prev.y + event.delta.y,
1313
+ node?.offsetWidth ?? COMPACT_PANEL_WIDTH_PX,
1314
+ node?.offsetHeight ?? 320
1315
+ );
1316
+ if (next.x === prev.x && next.y === prev.y) return prev;
1317
+ return next;
1318
+ });
1319
+ };
1320
+ if (typeof document === "undefined") return null;
1321
+ const sharerName = track?.participant.name;
1322
+ return createPortal(
1323
+ /* @__PURE__ */ jsxs(
1324
+ "div",
1325
+ {
1326
+ ref: overlayRef,
1327
+ role: "dialog",
1328
+ "aria-modal": "true",
1329
+ "aria-label": t("participant.stageTitle"),
1330
+ tabIndex: -1,
1331
+ className: "screen-share-stage fixed inset-0 z-[2100] bg-black outline-none",
1332
+ children: [
1333
+ /* @__PURE__ */ jsxs("div", { className: "relative h-full w-full", children: [
1334
+ track && publication ? /* @__PURE__ */ jsx(ScreenShareZoom, { trackRef: track, className: "rounded-none", children: /* @__PURE__ */ jsx(
1335
+ VideoTrack,
1336
+ {
1337
+ trackRef: track,
1338
+ manageSubscription: false,
1339
+ className: "h-full w-full bg-black object-contain"
1340
+ }
1341
+ ) }) : 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
+ ] })
1356
+ ] }),
1357
+ /* @__PURE__ */ jsx(
1358
+ DndContext,
1359
+ {
1360
+ sensors,
1361
+ onDragEnd: handleDragEnd,
1362
+ modifiers: [restrictToWindowEdges],
1363
+ autoScroll: false,
1364
+ children: /* @__PURE__ */ jsx(
1365
+ "div",
1366
+ {
1367
+ ref: panelRef,
1368
+ className: "absolute z-30 w-max",
1369
+ style: { left: position.x, top: position.y },
1370
+ children: /* @__PURE__ */ jsx(CompactCall, { freeDrag: true })
1371
+ }
1372
+ )
1373
+ }
1374
+ ),
1375
+ /* @__PURE__ */ jsx(Chat, { compactPositionStyle: { top: 56, right: 16, bottom: 16, zIndex: 20 } })
1376
+ ]
1377
+ }
1378
+ ),
1379
+ document.body
1380
+ );
1127
1381
  };
1128
1382
  var useCompactDragActive = () => {
1129
1383
  const [isDragging, setIsDragging] = useState(false);
@@ -1142,7 +1396,7 @@ var useCompactDragActive = () => {
1142
1396
  });
1143
1397
  return isDragging;
1144
1398
  };
1145
- var DroppableCorner = ({ id, className }) => {
1399
+ var DroppableCorner = ({ id, style }) => {
1146
1400
  const { setNodeRef } = useDroppable({
1147
1401
  id
1148
1402
  });
@@ -1151,17 +1405,18 @@ var DroppableCorner = ({ id, className }) => {
1151
1405
  "div",
1152
1406
  {
1153
1407
  ref: setNodeRef,
1154
- className: `pointer-events-none absolute z-100 size-1/3 ${className} transition-all duration-200 ${isDragging ? "bg-status-info-background/20 ring-border-selected rounded-lg ring-2" : ""}`
1408
+ style,
1409
+ className: `pointer-events-none absolute z-20 size-1/3 transition-all duration-200 ${isDragging ? "bg-status-info-background/20 ring-border-selected rounded-lg ring-2" : ""}`
1155
1410
  }
1156
1411
  );
1157
1412
  };
1158
- var DroppableAreas = () => {
1413
+ var DroppableAreas = ({ insets }) => {
1159
1414
  const isDragging = useCompactDragActive();
1160
1415
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1161
- /* @__PURE__ */ jsx(DroppableCorner, { id: "top-left", className: "top-16 left-4" }),
1162
- /* @__PURE__ */ jsx(DroppableCorner, { id: "top-right", className: "top-16 right-4" }),
1163
- /* @__PURE__ */ jsx(DroppableCorner, { id: "bottom-left", className: "bottom-18 left-4" }),
1164
- /* @__PURE__ */ jsx(DroppableCorner, { id: "bottom-right", className: "right-4 bottom-18" }),
1416
+ /* @__PURE__ */ jsx(DroppableCorner, { id: "top-left", style: cornerStyle("top-left", insets) }),
1417
+ /* @__PURE__ */ jsx(DroppableCorner, { id: "top-right", style: cornerStyle("top-right", insets) }),
1418
+ /* @__PURE__ */ jsx(DroppableCorner, { id: "bottom-left", style: cornerStyle("bottom-left", insets) }),
1419
+ /* @__PURE__ */ jsx(DroppableCorner, { id: "bottom-right", style: cornerStyle("bottom-right", insets) }),
1165
1420
  /* @__PURE__ */ jsx(DragOverlay, { style: { pointerEvents: "none" }, children: isDragging ? /* @__PURE__ */ jsx(CompactCall, {}) : null })
1166
1421
  ] });
1167
1422
  };
@@ -1201,26 +1456,9 @@ var Compact = ({ children, hideOverlay = false }) => {
1201
1456
  }
1202
1457
  };
1203
1458
  const isBoardPage = navigation.pathnameIncludes("/board");
1204
- const bottomOffset = isBoardPage ? "bottom-[72px]" : "bottom-4";
1459
+ const insets = useBoardChromeInsets(isBoardPage);
1205
1460
  const mobileContentMarginTop = hideOverlay || !isMobile ? 0 : (headerHeight > 0 ? headerHeight : COMPACT_MOBILE_OVERLAY_FALLBACK_PX) + COMPACT_MOBILE_CONTENT_GAP_PX;
1206
- const getCornerPosition = (corner) => {
1207
- switch (corner) {
1208
- case "top-left":
1209
- return "top-16 left-4";
1210
- case "top-right":
1211
- return "top-16 right-4";
1212
- case "bottom-left":
1213
- return `${bottomOffset} left-4`;
1214
- case "bottom-right":
1215
- return `${bottomOffset} right-4`;
1216
- }
1217
- };
1218
- const getChatPositionClasses = (corner) => {
1219
- const opposite = OPPOSITE_CORNER[corner];
1220
- const horizontal = opposite.includes("left") ? "left-22" : "right-4";
1221
- return `top-32 ${bottomOffset} ${horizontal}`;
1222
- };
1223
- const container = /* @__PURE__ */ jsx("div", { className: `relative flex h-dvh flex-col bg-transparent`, children: isMobile ? /* @__PURE__ */ jsxs(Fragment, { children: [
1461
+ const container = /* @__PURE__ */ jsx("div", { className: `relative flex h-dvh flex-col bg-transparent`, "data-board-chrome-aware": "", children: isMobile ? /* @__PURE__ */ jsxs(Fragment, { children: [
1224
1462
  !hideOverlay && /* @__PURE__ */ jsx("div", { ref: headerRef, className: "fixed top-2 right-0 left-0 z-10 w-full px-2", children: /* @__PURE__ */ jsx(CompactCall, { withOutShadows: true }) }),
1225
1463
  /* @__PURE__ */ jsx(
1226
1464
  "div",
@@ -1232,12 +1470,13 @@ var Compact = ({ children, hideOverlay = false }) => {
1232
1470
  )
1233
1471
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1234
1472
  !hideOverlay && /* @__PURE__ */ jsxs(Fragment, { children: [
1235
- /* @__PURE__ */ jsx(DroppableAreas, {}),
1236
- /* @__PURE__ */ jsx(Chat, { compactPositionClassName: getChatPositionClasses(activeCorner) }),
1473
+ /* @__PURE__ */ jsx(DroppableAreas, { insets }),
1474
+ /* @__PURE__ */ jsx(Chat, { compactPositionStyle: chatPositionStyle(activeCorner, insets) }),
1237
1475
  /* @__PURE__ */ jsx(
1238
1476
  "div",
1239
1477
  {
1240
- className: `absolute z-100 ${getCornerPosition(activeCorner)} transition-all duration-500 ease-out`,
1478
+ className: "absolute z-20 transition-all duration-500 ease-out",
1479
+ style: cornerStyle(activeCorner, insets),
1241
1480
  children: /* @__PURE__ */ jsx(CompactCall, {})
1242
1481
  }
1243
1482
  )
@@ -1248,11 +1487,19 @@ var Compact = ({ children, hideOverlay = false }) => {
1248
1487
  };
1249
1488
  var CompactViewInner = ({ children }) => {
1250
1489
  const { mode, token } = useCallStore();
1490
+ const screenShareStageIdentity = useCallStore((state) => state.screenShareStageIdentity);
1491
+ const isStarted = useCallStore((state) => state.isStarted);
1251
1492
  const { room } = useRoom();
1252
1493
  const pip = usePiP();
1253
1494
  const isPiPActive = !!pip?.isPiPActive;
1254
1495
  useParticipantJoinSync();
1255
1496
  useParticipantSounds();
1497
+ useEffect(() => {
1498
+ if (!screenShareStageIdentity) return;
1499
+ if (!room || !token || !isStarted) {
1500
+ useCallStore.getState().updateStore("screenShareStageIdentity", null);
1501
+ }
1502
+ }, [isStarted, room, screenShareStageIdentity, token]);
1256
1503
  const navigation = useCallsNavigation();
1257
1504
  const { updateStore: updateChatStore } = useChatStore();
1258
1505
  useEffect(() => {
@@ -1277,14 +1524,17 @@ var CompactViewInner = ({ children }) => {
1277
1524
  /* @__PURE__ */ jsx(PermissionsDialog, {})
1278
1525
  ] });
1279
1526
  }
1527
+ const stageLayer = screenShareStageIdentity && room && token && isStarted ? /* @__PURE__ */ jsx(ScreenShareStage, { identity: screenShareStageIdentity }, screenShareStageIdentity) : null;
1280
1528
  if (mode === "full") {
1281
1529
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1282
1530
  contentWrapper,
1531
+ stageLayer,
1283
1532
  /* @__PURE__ */ jsx(PermissionsDialog, {})
1284
1533
  ] });
1285
1534
  }
1286
1535
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1287
- /* @__PURE__ */ jsx(Compact, { hideOverlay: isPiPActive, children }),
1536
+ /* @__PURE__ */ jsx(Compact, { hideOverlay: isPiPActive || Boolean(screenShareStageIdentity), children }),
1537
+ stageLayer,
1288
1538
  /* @__PURE__ */ jsx(PermissionsDialog, {})
1289
1539
  ] });
1290
1540
  };