about summary refs log tree commit diff
path: root/src/components/hooks/useFullscreen.ts
blob: cbe3e581c23cb14d0fa347c1e7892ea98b0f955d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import {
  useCallback,
  useEffect,
  useRef,
  useState,
  useSyncExternalStore,
} from 'react'

import {isFirefox, isSafari} from '#/lib/browser'
import {isWeb} from '#/platform/detection'

function fullscreenSubscribe(onChange: () => void) {
  document.addEventListener('fullscreenchange', onChange)
  return () => document.removeEventListener('fullscreenchange', onChange)
}

export function useFullscreen(ref?: React.RefObject<HTMLElement | null>) {
  if (!isWeb) throw new Error("'useFullscreen' is a web-only hook")
  const isFullscreen = useSyncExternalStore(fullscreenSubscribe, () =>
    Boolean(document.fullscreenElement),
  )
  const scrollYRef = useRef<null | number>(null)
  const [prevIsFullscreen, setPrevIsFullscreen] = useState(isFullscreen)

  const toggleFullscreen = useCallback(() => {
    if (isFullscreen) {
      document.exitFullscreen()
    } else {
      if (!ref) throw new Error('No ref provided')
      if (!ref.current) return
      scrollYRef.current = window.scrollY
      ref.current.requestFullscreen()
    }
  }, [isFullscreen, ref])

  useEffect(() => {
    if (prevIsFullscreen === isFullscreen) return
    setPrevIsFullscreen(isFullscreen)

    // Chrome has an issue where it doesn't scroll back to the top after exiting fullscreen
    // Let's play it safe and do it if not FF or Safari, since anything else will probably be chromium
    if (prevIsFullscreen && !isFirefox && !isSafari) {
      setTimeout(() => {
        if (scrollYRef.current !== null) {
          window.scrollTo(0, scrollYRef.current)
          scrollYRef.current = null
        }
      }, 100)
    }
  }, [isFullscreen, prevIsFullscreen])

  return [isFullscreen, toggleFullscreen] as const
}