{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-fullscreen",
  "title": "useFullscreen",
  "description": "A hook that requests/exits native fullscreen on a single element via a callback ref, keeping a fullscreen boolean in sync (including Esc) through useSyncExternalStore.",
  "files": [
    {
      "path": "src/registry/hooks/use-fullscreen.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nexport interface UseFullscreenResult<T extends HTMLElement> {\n  /** Callback ref — attach it to the element you want to be able to fullscreen. */\n  ref: (node: T | null) => void\n  /** Whether the tracked element is currently the document's fullscreen element. */\n  fullscreen: boolean\n  /** Requests fullscreen on the tracked element. Never throws — resolves `false` on\n   *  any failure (no node attached, unsupported browser, or the browser rejected the\n   *  request because it wasn't called synchronously inside a user gesture). */\n  enter: () => Promise<boolean>\n  /** Exits fullscreen. Never throws — resolves `false` if nothing is fullscreen, the\n   *  browser is unsupported, or the exit request was rejected. */\n  exit: () => Promise<boolean>\n  /** `exit()` if the tracked element is fullscreen, otherwise `enter()`. */\n  toggle: () => Promise<boolean>\n  /** `document.fullscreenEnabled` — `false` during SSR and on unsupported browsers. */\n  supported: boolean\n}\n\n/**\n * 单个元素的全屏进出 + 状态订阅。用**callback ref**(不是 `useRef` + `useEffect`)\n * 记录当前要全屏的节点 —— 节点被条件渲染替换(换 key、换分支)时天然拿到\"现在\n * 到底在管哪个节点\"的真相,不需要额外的挂载 effect。\n *\n * `fullscreen` 用 `useSyncExternalStore` 订阅 `document` 的全局 `fullscreenchange`\n * 事件(不是每节点各建一个 observer —— 全屏状态本来就是 document 级别的单例):\n * `getSnapshot` 每次都从 ref 里读最新节点,和 `document.fullscreenElement` 比较,\n * 得到\"是不是**这个**元素在全屏\"。这样零 `useEffect` + `setState`,`fullscreenchange`\n * 无论因为调用方点了 `exit()`、点了别的元素的 `enter()`,还是用户按 Esc 系统级退出\n * 而触发,都会被同一条订阅捕获并同步——Esc 退出不需要任何特殊分支。\n *\n * `enter`/`exit` 把 `requestFullscreen`/`exitFullscreen` 的 Promise 包一层,永不\n * 抛出、失败一律 resolve `false`。失败常见原因:没有已挂载的节点、浏览器不支持、\n * 或者(最常踩)**没有在用户手势(click/keydown 的同步调用栈里)直接调用** ——\n * 浏览器的 Fullscreen API 要求 `requestFullscreen()` 必须同步发生在一次真实用户\n * 交互内,`await` 过别的 Promise、`setTimeout` 之后再调,请求会被浏览器直接拒绝。\n *\n * `supported` 读 `document.fullscreenEnabled`,SSR(`document` 不存在)时为 `false`。\n * 不做 `webkitRequestFullscreen` 一类前缀兼容——Fullscreen API 在所有现代浏览器\n * (含 Safari 16.4+)都已无前缀标准化,支持基线设在这里;需要覆盖更旧的 Safari /\n * 移动端 WebView,自行加前缀探测层。\n */\nconst subscribeNoop = () => () => {}\n\nexport function useFullscreen<T extends HTMLElement = HTMLElement>(): UseFullscreenResult<T> {\n  const nodeRef = React.useRef<T | null>(null)\n\n  const ref = React.useCallback((node: T | null) => {\n    nodeRef.current = node\n  }, [])\n\n  const subscribe = React.useCallback((callback: () => void) => {\n    if (typeof document === \"undefined\") return () => {}\n    document.addEventListener(\"fullscreenchange\", callback)\n    return () => document.removeEventListener(\"fullscreenchange\", callback)\n  }, [])\n\n  const getSnapshot = React.useCallback(() => {\n    const node = nodeRef.current\n    return node !== null && document.fullscreenElement === node\n  }, [])\n\n  const getServerSnapshot = React.useCallback(() => false, [])\n\n  const fullscreen = React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n\n  // 水合安全:渲染期直读 document.fullscreenEnabled 会让 SSR(false)与客户端\n  // 首帧(true)不一致,直接触发 hydration mismatch(实测必现)。走\n  // useSyncExternalStore:服务端与首次水合渲染都拿 false,随后 React 自行\n  // 无警告地按客户端快照重渲染。\n  const supported = React.useSyncExternalStore(\n    subscribeNoop,\n    () => document.fullscreenEnabled,\n    () => false,\n  )\n\n  const enter = React.useCallback(() => {\n    const node = nodeRef.current\n    if (!supported || !node || typeof node.requestFullscreen !== \"function\") {\n      return Promise.resolve(false)\n    }\n    return node.requestFullscreen().then(\n      () => true,\n      () => false,\n    )\n  }, [supported])\n\n  const exit = React.useCallback(() => {\n    if (!supported || typeof document.exitFullscreen !== \"function\" || !document.fullscreenElement) {\n      return Promise.resolve(false)\n    }\n    return document.exitFullscreen().then(\n      () => true,\n      () => false,\n    )\n  }, [supported])\n\n  const toggle = React.useCallback(() => {\n    return fullscreen ? exit() : enter()\n  }, [fullscreen, enter, exit])\n\n  return { ref, fullscreen, enter, exit, toggle, supported }\n}\n\nexport default useFullscreen\n",
      "type": "registry:hook"
    }
  ],
  "type": "registry:hook"
}