useFullscreen
A hook to manage fullscreen state
Loading...
Installation
npx shadcn@latest add @hooks/use-fullscreenpnpm dlx shadcn@latest add @hooks/use-fullscreenyarn dlx shadcn@latest add @hooks/use-fullscreenbun x shadcn@latest add @hooks/use-fullscreenCopy and paste the following code into your project.
import { useEffectWithTarget } from '@/registry/hooks/use-effect-with-target'
import { useLatest } from '@/registry/hooks/use-latest'
import { getTargetElement } from '@/registry/lib/create-effect-with-target'
import type { BasicTarget } from '@/registry/lib/create-effect-with-target'
type noop = (...p: any) => void
export type Target = BasicTarget<HTMLElement | Element | Window | Document>
interface Options<T extends Target = Target> {
target?: T
capture?: boolean
once?: boolean
passive?: boolean
enable?: boolean
}
function useEventListener<K extends keyof HTMLElementEventMap>(
eventName: K,
handler: (ev: HTMLElementEventMap[K]) => void,
options?: Options<HTMLElement>,
): void
function useEventListener<K extends keyof ElementEventMap>(
eventName: K,
handler: (ev: ElementEventMap[K]) => void,
options?: Options<Element>,
): void
function useEventListener<K extends keyof DocumentEventMap>(
eventName: K,
handler: (ev: DocumentEventMap[K]) => void,
options?: Options<Document>,
): void
function useEventListener<K extends keyof WindowEventMap>(
eventName: K,
handler: (ev: WindowEventMap[K]) => void,
options?: Options<Window>,
): void
function useEventListener(
eventName: string | string[],
handler: (event: Event) => void,
options?: Options<Window>,
): void
function useEventListener(
eventName: string | string[],
handler: noop,
options: Options,
): void
function useEventListener(
eventName: string | string[],
handler: noop,
options: Options = {},
) {
const { enable = true } = options
const handlerRef = useLatest(handler)
useEffectWithTarget(
() => {
if (!enable) {
return
}
const targetElement = getTargetElement(options.target, window)
if (!targetElement?.addEventListener) {
return
}
const eventListener = (event: Event) => {
return handlerRef.current(event)
}
const eventNameArray = Array.isArray(eventName) ? eventName : [eventName]
eventNameArray.forEach((event) => {
targetElement.addEventListener(event, eventListener, {
capture: options.capture,
once: options.once,
passive: options.passive,
})
})
return () => {
eventNameArray.forEach((event) => {
targetElement.removeEventListener(event, eventListener, {
capture: options.capture,
})
})
}
},
[eventName, options.capture, options.once, options.passive, enable],
options.target,
)
}
export { useEventListener }import { useEffect, useLayoutEffect } from 'react'
import { isBrowser } from '@/registry/lib/is-browser'
/**
* Custom hook that uses either `useLayoutEffect` or `useEffect` based on the environment (client-side or server-side).
* @param {Function} effect - The effect function to be executed.
* @param {Array<any>} [dependencies] - An array of dependencies for the effect (optional).
* @public
* @see [Documentation](https://usehooks-ts.com/react-hook/use-isomorphic-layout-effect)
* @example
* ```tsx
* useIsomorphicLayoutEffect(() => {
* // Code to be executed during the layout phase on the client side
* }, [dependency1, dependency2]);
* ```
*/
export const useIsomorphicLayoutEffect = isBrowser ? useLayoutEffect : useEffectimport { useEffect } from 'react'
import { useLatest } from '@/registry/hooks/use-latest'
export function useUnmount(fn: () => void) {
const fnRef = useLatest(fn)
useEffect(
() => () => {
fnRef.current()
},
[],
)
}import { useRef } from 'react'
import { useIsomorphicLayoutEffect } from '@/registry/hooks/use-isomorphic-layout-effect'
export function useLatest<T>(value: T) {
const ref = useRef(value)
useIsomorphicLayoutEffect(() => {
ref.current = value
})
return ref
}import { useMemo, useRef } from 'react'
type noop = (this: any, ...args: any[]) => any
type PickFunction<T extends noop> = (
this: ThisParameterType<T>,
...args: Parameters<T>
) => ReturnType<T>
export function useMemoizedFn<T extends noop>(fn: T) {
const fnRef = useRef<T>(fn)
// why not write `fnRef.current = fn`?
// https://github.com/alibaba/hooks/issues/728
fnRef.current = useMemo<T>(() => fn, [fn])
const memoizedFn = useRef<PickFunction<T>>(undefined)
if (!memoizedFn.current) {
memoizedFn.current = function (this, ...args) {
return fnRef.current.apply(this, args)
}
}
return memoizedFn.current
}import { useRef, useState } from 'react'
import { useEffectWithTarget } from '@/registry/hooks/use-effect-with-target'
import { useEventListener } from '@/registry/hooks/use-event-listener'
import { useIsomorphicLayoutEffect } from '@/registry/hooks/use-isomorphic-layout-effect'
import { useMemoizedFn } from '@/registry/hooks/use-memoized-fn'
import { useUnmount } from '@/registry/hooks/use-unmount'
import { getTargetElement as getTargetElementUtil } from '@/registry/lib/create-effect-with-target'
import { isBrowser } from '@/registry/lib/is-browser'
import type { BasicTarget } from '@/registry/lib/create-effect-with-target'
export interface UseFullscreenOptions {
/**
* Automatically exit fullscreen when component is unmounted
*
* @default false
*/
autoExit?: boolean
}
const eventHandlers = [
'fullscreenchange',
'webkitfullscreenchange',
'webkitendfullscreen',
'mozfullscreenchange',
'MSFullscreenChange',
] as const
type RequestMethod =
| 'requestFullscreen'
| 'webkitRequestFullscreen'
| 'webkitEnterFullscreen'
| 'webkitEnterFullScreen'
| 'webkitRequestFullScreen'
| 'mozRequestFullScreen'
| 'msRequestFullscreen'
type ExitMethod =
| 'exitFullscreen'
| 'webkitExitFullscreen'
| 'webkitExitFullScreen'
| 'webkitCancelFullScreen'
| 'mozCancelFullScreen'
| 'msExitFullscreen'
type FullscreenEnabledProperty =
| 'fullScreen'
| 'webkitIsFullScreen'
| 'webkitDisplayingFullscreen'
| 'mozFullScreen'
| 'msFullscreenElement'
type FullscreenElementProperty =
| 'fullscreenElement'
| 'webkitFullscreenElement'
| 'mozFullScreenElement'
| 'msFullscreenElement'
function getTargetElement(target: BasicTarget<any>) {
return getTargetElementUtil(target, document.documentElement)
}
function getProperties(target: BasicTarget<any>) {
const targetElement = getTargetElement(target)
const getRequestMethod = () => {
const methods: RequestMethod[] = [
'requestFullscreen',
'webkitRequestFullscreen',
'webkitEnterFullscreen',
'webkitEnterFullScreen',
'webkitRequestFullScreen',
'mozRequestFullScreen',
'msRequestFullscreen',
]
return methods.find(
(method) =>
(targetElement && method in targetElement) ||
(document && method in document),
)
}
const getExitMethod = () => {
const methods: ExitMethod[] = [
'exitFullscreen',
'webkitExitFullscreen',
'webkitExitFullScreen',
'webkitCancelFullScreen',
'mozCancelFullScreen',
'msExitFullscreen',
]
return methods.find(
(method) =>
(targetElement && method in targetElement) ||
(document && method in document),
)
}
const getFullscreenEnabledProperty = () => {
const properties: FullscreenEnabledProperty[] = [
'fullScreen',
'webkitIsFullScreen',
'webkitDisplayingFullscreen',
'mozFullScreen',
'msFullscreenElement',
]
return properties.find(
(property) =>
(document && property in document) ||
(targetElement && property in targetElement),
)
}
const getFullscreenElementProperty = () => {
const properties: FullscreenElementProperty[] = [
'fullscreenElement',
'webkitFullscreenElement',
'mozFullScreenElement',
'msFullscreenElement',
]
return properties.find(
(property) =>
(document && property in document) ||
(targetElement && property in targetElement),
)
}
return {
requestMethod: getRequestMethod(),
exitMethod: getExitMethod(),
fullscreenEnabledProperty: getFullscreenEnabledProperty(),
fullscreenElementProperty: getFullscreenElementProperty(),
}
}
function getIsSupported(
target: BasicTarget<any>,
properties: ReturnType<typeof getProperties>,
) {
const targetElement = getTargetElement(target)
const { requestMethod, exitMethod, fullscreenEnabledProperty } = properties
return !!(
targetElement &&
document &&
requestMethod !== undefined &&
exitMethod !== undefined &&
fullscreenEnabledProperty !== undefined
)
}
/**
* Reactive Fullscreen API.
*
* @param target - The target element to make fullscreen. If not provided, uses document.documentElement
* @param options - Configuration options
*/
export function useFullscreen(
target?: BasicTarget<any>,
options: UseFullscreenOptions = {},
) {
const { autoExit = false } = options
const properties = useRef<{
requestMethod: RequestMethod | undefined
exitMethod: ExitMethod | undefined
fullscreenEnabledProperty: FullscreenEnabledProperty | undefined
fullscreenElementProperty: FullscreenElementProperty | undefined
}>({
requestMethod: undefined,
exitMethod: undefined,
fullscreenEnabledProperty: undefined,
fullscreenElementProperty: undefined,
})
const [isSupported, setIsSupported] = useState(() => {
if (!isBrowser) return false
return getIsSupported(target, getProperties(target))
})
const [isFullscreen, setIsFullscreen] = useState(false)
const exit = useMemoizedFn(async () => {
const { exitMethod } = properties.current
if (!isSupported || !isFullscreen) return
const element = getTargetElement(target)
const doc = document as any
if (exitMethod) {
if (doc[exitMethod] != null) {
await doc[exitMethod]()
} else if (element && (element as any)[exitMethod] != null) {
// Fallback for Safari iOS
await (element as any)[exitMethod]()
}
}
setIsFullscreen(false)
})
useEffectWithTarget(
() => {
if (!isBrowser) {
return
}
properties.current = getProperties(target)
setIsSupported(getIsSupported(target, properties.current))
},
[],
target,
)
const isCurrentElementFullScreen = useMemoizedFn((): boolean => {
const { fullscreenElementProperty } = properties.current
if (!fullscreenElementProperty || !isBrowser) return false
const element = getTargetElement(target)
return document[fullscreenElementProperty as keyof Document] === element
})
const isElementFullScreen = useMemoizedFn((): boolean => {
const { fullscreenEnabledProperty } = properties.current
if (!fullscreenEnabledProperty || !isBrowser) return false
const element = getTargetElement(target)
const doc = document as any
if (doc[fullscreenEnabledProperty] != null) {
return Boolean(doc[fullscreenEnabledProperty])
}
// Fallback for WebKit and iOS Safari browsers
if (element && (element as any)[fullscreenEnabledProperty] != null) {
return Boolean((element as any)[fullscreenEnabledProperty])
}
return false
})
const enter = useMemoizedFn(async () => {
const { requestMethod } = properties.current
if (!isSupported || isFullscreen) return
if (isElementFullScreen()) {
await exit()
}
const element = getTargetElement(target)
if (requestMethod && element && (element as any)[requestMethod] != null) {
await (element as any)[requestMethod]()
setIsFullscreen(true)
}
})
const toggle = useMemoizedFn(async () => {
await (isFullscreen ? exit() : enter())
})
const handlerCallback = useMemoizedFn(() => {
const isElementFullScreenValue = isElementFullScreen()
if (
!isElementFullScreenValue ||
(isElementFullScreenValue && isCurrentElementFullScreen())
) {
setIsFullscreen(isElementFullScreenValue)
}
})
const listenerOptions = { capture: false, passive: true }
// Listen to fullscreen change events on document
useEventListener(eventHandlers as any, handlerCallback, {
target: () => document,
...listenerOptions,
})
// Listen to fullscreen change events on target element
useEventListener(eventHandlers as any, handlerCallback, {
target: () => getTargetElement(target),
...listenerOptions,
})
// Check initial state on mount
useIsomorphicLayoutEffect(() => {
if (isBrowser) {
handlerCallback()
}
}, [])
useUnmount(() => {
if (autoExit) exit()
})
return {
isSupported,
isFullscreen,
enter,
exit,
toggle,
}
}
export type UseFullscreenReturn = ReturnType<typeof useFullscreen>API
export interface UseFullscreenOptions {
/**
* Automatically exit fullscreen when component is unmounted
*
* @default false
*/
autoExit?: boolean
}
/**
* A hook to manage fullscreen state
* @param target - The target element to make fullscreen. If not provided, uses document.documentElement
* @param options - Configuration options
*/
export function useFullscreen(
target?: BasicTarget<HTMLElement | Element>,
options?: UseFullscreenOptions,
): {
isSupported: boolean
isFullscreen: boolean
enter: PickFunction<() => Promise<void>>
exit: PickFunction<() => Promise<void>>
toggle: PickFunction<() => Promise<void>>
}Credits
Last updated on