import React from 'react'; import { createPortal } from 'react-dom'; interface DesktopSpeedMenuProps { showSpeedMenu: boolean; playbackRate: number; speeds: number[]; onSpeedChange: (speed: number) => void; onToggleSpeedMenu: () => void; onMouseEnter: () => void; onMouseLeave: () => void; containerRef: React.RefObject; isRotated?: boolean; } export function DesktopSpeedMenu({ showSpeedMenu, playbackRate, speeds, onSpeedChange, onToggleSpeedMenu, onMouseEnter, onMouseLeave, containerRef, isRotated = false }: DesktopSpeedMenuProps) { const buttonRef = React.useRef(null); const menuRef = React.useRef(null); const [menuPosition, setMenuPosition] = React.useState({ top: 0, left: 0, maxHeight: 'none', openUpward: false }); const [isFullscreen, setIsFullscreen] = React.useState(false); React.useEffect(() => { const updateFullscreen = () => { // Check both native fullscreen and window fullscreen (CSS-based) const nativeFullscreen = !!document.fullscreenElement; const windowFullscreen = containerRef.current?.closest('.is-web-fullscreen') !== null; setIsFullscreen(nativeFullscreen || windowFullscreen); }; document.addEventListener('fullscreenchange', updateFullscreen); // Also check periodically for window fullscreen changes (CSS class based) const interval = setInterval(updateFullscreen, 500); updateFullscreen(); return () => { document.removeEventListener('fullscreenchange', updateFullscreen); clearInterval(interval); }; }, [containerRef]); // Dual Positioning Strategy const calculateMenuPosition = React.useCallback(() => { if (!buttonRef.current || !containerRef.current) return; if (!isRotated) { // Normal Mode: Non-rotated (Portrait on Mobile) // Center the menu on screen for better visibility and touch access setMenuPosition({ top: window.innerHeight / 2, left: window.innerWidth / 2, maxHeight: '80vh', openUpward: false }); } else { // Rotated Mode: Fullscreen/Landscape forced // Use Container Coordinates (offset loop) and Portal to Container // This ensures rotation transforms apply correctly to the menu let top = 0; let left = 0; let el: HTMLElement | null = buttonRef.current; while (el && el !== containerRef.current) { top += el.offsetTop; left += el.offsetLeft; el = el.offsetParent as HTMLElement; } const buttonHeight = buttonRef.current.offsetHeight; const buttonWidth = buttonRef.current.offsetWidth; const containerHeight = containerRef.current.offsetHeight; const spaceBelow = containerHeight - (top + buttonHeight) - 20; const spaceAbove = top - 20; const estimatedMenuHeight = 250; const actualMenuHeight = menuRef.current?.offsetHeight || estimatedMenuHeight; const openUpward = spaceBelow < Math.min(actualMenuHeight, 200) && spaceAbove > spaceBelow; const maxHeight = openUpward ? Math.min(spaceAbove, actualMenuHeight) : Math.min(spaceBelow, containerHeight * 0.7); if (openUpward) { setMenuPosition({ top: top - 10, left: left + buttonWidth, maxHeight: `${maxHeight}px`, openUpward: true }); } else { setMenuPosition({ top: top + buttonHeight + 10, left: left + buttonWidth, maxHeight: `${maxHeight}px`, openUpward: false }); } } }, [containerRef, isRotated]); // Auto-close menu on scroll React.useEffect(() => { if (!showSpeedMenu) return; const handleScroll = () => { if (showSpeedMenu) { onToggleSpeedMenu(); } }; window.addEventListener('scroll', handleScroll, { passive: true }); return () => window.removeEventListener('scroll', handleScroll); }, [showSpeedMenu, onToggleSpeedMenu]); React.useEffect(() => { if (showSpeedMenu) { calculateMenuPosition(); const timer = setTimeout(calculateMenuPosition, 50); return () => clearTimeout(timer); } }, [showSpeedMenu, calculateMenuPosition, isRotated]); const handleToggle = () => { if (!showSpeedMenu) { calculateMenuPosition(); } onToggleSpeedMenu(); }; const MenuContent = (
e.stopPropagation()} onTouchStart={(e) => e.stopPropagation()} > {speeds.map((speed) => ( ))}
); return (
{/* Speed Menu (Portal) - Portal to container to inherit rotation but avoid overflow clipping if container has it? Actually, the container usually has overflow-hidden. If we portal to containerRef, it is inside the container div. If the container div has overflow-hidden, the menu will be clipped. BUT DesktopVideoPlayer structure:
(relative, no overflow hidden?)
(video wrapper) So containerRef itself (outer wrapper) seems to NOT have overflow-hidden in my memory? Checking DesktopVideoPlayer.tsx: className={`kvideo-container relative aspect-video ...`} It does NOT have overflow-hidden. The inner div does. So portaling to containerRef is SAFE and CORRECT. */} {/* Speed Menu (Portal) */} {showSpeedMenu && typeof document !== 'undefined' && createPortal(MenuContent, (isRotated && containerRef.current) ? containerRef.current : document.body)}
); }