From 6cbedbd2ebca06ec52e761ece77c2d55de0f4643 Mon Sep 17 00:00:00 2001 From: Cyril Date: Mon, 13 Jul 2026 17:11:59 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F(frontend)=20refactor=20scree?= =?UTF-8?q?n=20share=20zoom=20pan=20with=20useMove=20and=20imperative=20DO?= =?UTF-8?q?M=20updates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit use react-aria useMove for pan, zoom/pan refs for DOM updates, --- .../components/ScreenShareZoomControls.tsx | 134 +++--- .../components/ScreenShareZoomableVideo.tsx | 34 +- .../rooms/livekit/hooks/useScreenShareZoom.ts | 391 +++++++++++------- .../rooms/livekit/utils/screenShareZoom.ts | 154 +++++++ .../src/features/shortcuts/catalog.ts | 29 +- .../src/features/shortcuts/formatLabels.ts | 5 +- .../shortcuts/hooks/useShortcutFormatting.ts | 10 + src/frontend/src/locales/de/rooms.json | 12 +- src/frontend/src/locales/en/rooms.json | 12 +- src/frontend/src/locales/fr/rooms.json | 12 +- src/frontend/src/locales/nl/rooms.json | 12 +- 11 files changed, 567 insertions(+), 238 deletions(-) create mode 100644 src/frontend/src/features/rooms/livekit/utils/screenShareZoom.ts diff --git a/src/frontend/src/features/rooms/livekit/components/ScreenShareZoomControls.tsx b/src/frontend/src/features/rooms/livekit/components/ScreenShareZoomControls.tsx index 3a04238f..7cb35142 100644 --- a/src/frontend/src/features/rooms/livekit/components/ScreenShareZoomControls.tsx +++ b/src/frontend/src/features/rooms/livekit/components/ScreenShareZoomControls.tsx @@ -38,12 +38,13 @@ export const ScreenShareZoomControls = ({ const { t } = useTranslation('rooms', { keyPrefix: 'screenShareZoom' }) const announce = useScreenReaderAnnounce() + const zoomInButtonRef = useRef(null) + const hadFocusInCollapsibleRef = useRef(false) + const [isFullscreen, setIsFullscreen] = useState(false) // Tracks whether this tile's container triggered fullscreen (vs another share's). const wasThisTileFullscreen = useRef(false) - const [isFullscreenAvailable] = useState( - () => typeof document !== 'undefined' && document.fullscreenEnabled - ) + const isFullscreenAvailable = document.fullscreenEnabled // Covers Esc and browser UI exits, not just the toolbar button. // Only this tile's instance announces to avoid duplicates with multiple shares. @@ -65,6 +66,15 @@ export const ScreenShareZoomControls = ({ return () => document.removeEventListener('fullscreenchange', onChange) }, [announce, t, containerRef]) + // Back at 100 % the collapsible controls are disabled and hidden, which drops + // keyboard focus on the body. Hand it to the zoom in button instead, the only + // control of that group still reachable. + useEffect(() => { + if (isZoomed || !hadFocusInCollapsibleRef.current) return + hadFocusInCollapsibleRef.current = false + zoomInButtonRef.current?.focus() + }, [isZoomed]) + const toggleFullScreen = useCallback(async () => { try { if (document.fullscreenElement === containerRef.current) { @@ -78,7 +88,7 @@ export const ScreenShareZoomControls = ({ } }, [containerRef]) - const wheelShortcut = t(isMacintosh() ? 'wheelShortcutMac' : 'wheelShortcut') + const wheelShortcutVisual = isMacintosh() ? '⌘+scroll' : 'Ctrl+scroll' return (
{t(isMacintosh() ? 'wheelShortcutHintMac' : 'wheelShortcutHint')} - {isZoomed && ( - <> - - - {/* Visual only - zoom level is announced via useScreenReaderAnnounce. */} - - - )} + {/* Animated wrapper: collapses to 0 when not zoomed. padding/margin + trick keeps overflow:hidden from clipping focus rings. */} +
{ + hadFocusInCollapsibleRef.current = true + }} + onBlur={(e) => { + // Disabling a focused button blurs it with no relatedTarget, so the + // flag must survive that case for the effect above to rescue focus. + if (e.relatedTarget) hadFocusInCollapsibleRef.current = false + }} + > + + + {/* Visual only - zoom level is announced via useScreenReaderAnnounce. */} + +