glam/packages/hooks/src/useFullscreen.ts
2025-12-21 00:01:54 +01:00

123 lines
3.8 KiB
TypeScript

import { useState, useCallback, useEffect, type RefObject } from 'react';
interface UseFullscreenReturn {
/** Whether the element is currently fullscreen */
isFullscreen: boolean;
/** Enter fullscreen mode */
enterFullscreen: () => Promise<void>;
/** Exit fullscreen mode */
exitFullscreen: () => Promise<void>;
/** Toggle fullscreen mode */
toggleFullscreen: () => void;
}
/**
* Hook to manage fullscreen mode for a container element.
* Uses the Fullscreen API with fallback styling.
*
* @param containerRef - Ref to the element to make fullscreen
* @returns Object with fullscreen state and control functions
*
* @example
* ```tsx
* const containerRef = useRef<HTMLDivElement>(null);
* const { isFullscreen, toggleFullscreen } = useFullscreen(containerRef);
*
* return (
* <div ref={containerRef} className={isFullscreen ? 'fullscreen' : ''}>
* <button onClick={toggleFullscreen}>
* {isFullscreen ? 'Exit' : 'Enter'} Fullscreen
* </button>
* <div>Content here</div>
* </div>
* );
* ```
*/
export function useFullscreen(containerRef: RefObject<HTMLElement | null>): UseFullscreenReturn {
const [isFullscreen, setIsFullscreen] = useState(false);
const enterFullscreen = useCallback(async () => {
const container = containerRef.current;
if (!container) return;
try {
if (container.requestFullscreen) {
await container.requestFullscreen();
} else if ((container as any).webkitRequestFullscreen) {
await (container as any).webkitRequestFullscreen();
} else if ((container as any).msRequestFullscreen) {
await (container as any).msRequestFullscreen();
}
} catch (err) {
console.warn('Fullscreen API not available, using CSS fallback');
// CSS fallback handled by isFullscreen state
setIsFullscreen(true);
}
}, [containerRef]);
const exitFullscreen = useCallback(async () => {
try {
if (document.exitFullscreen) {
await document.exitFullscreen();
} else if ((document as any).webkitExitFullscreen) {
await (document as any).webkitExitFullscreen();
} else if ((document as any).msExitFullscreen) {
await (document as any).msExitFullscreen();
}
} catch (err) {
// CSS fallback
setIsFullscreen(false);
}
}, []);
const toggleFullscreen = useCallback(() => {
if (isFullscreen) {
exitFullscreen();
} else {
enterFullscreen();
}
}, [isFullscreen, enterFullscreen, exitFullscreen]);
// Listen for fullscreen change events
useEffect(() => {
const handleFullscreenChange = () => {
const isCurrentlyFullscreen = !!(
document.fullscreenElement ||
(document as any).webkitFullscreenElement ||
(document as any).msFullscreenElement
);
setIsFullscreen(isCurrentlyFullscreen);
};
document.addEventListener('fullscreenchange', handleFullscreenChange);
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
document.addEventListener('MSFullscreenChange', handleFullscreenChange);
return () => {
document.removeEventListener('fullscreenchange', handleFullscreenChange);
document.removeEventListener('webkitfullscreenchange', handleFullscreenChange);
document.removeEventListener('MSFullscreenChange', handleFullscreenChange);
};
}, []);
// Handle Escape key for CSS fallback mode
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape' && isFullscreen && !document.fullscreenElement) {
setIsFullscreen(false);
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [isFullscreen]);
return {
isFullscreen,
enterFullscreen,
exitFullscreen,
toggleFullscreen,
};
}
export default useFullscreen;