{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "modal-surface",
  "title": "ModalSurfaceProvider",
  "description": "Sets the surface of every Dialog, AlertDialog and Sheet that names none. Mount it once at an app's root with surface=\"plain\".",
  "files": [
    {
      "path": "lib/modal-surface.tsx",
      "content": "'use client';\n\nimport { createContext, type ReactNode, useContext } from 'react';\n\ntype ModalSurface = 'grain' | 'plain';\n\n/*\n * The surface of a Dialog, AlertDialog or Sheet that names none: grain on the\n * website, the default. An app mounts the provider once at its root, so every\n * modal on its screens is plain without a surface prop on each one.\n */\nconst ModalSurfaceContext = createContext<ModalSurface>('grain');\n\nfunction ModalSurfaceProvider({\n  surface,\n  children,\n}: {\n  surface: ModalSurface;\n  children: ReactNode;\n}) {\n  return <ModalSurfaceContext.Provider value={surface}>{children}</ModalSurfaceContext.Provider>;\n}\n\n/** The caller's surface, else the provider's, else grain. */\nfunction useModalSurface(surface?: ModalSurface | null) {\n  const fallback = useContext(ModalSurfaceContext);\n  return surface ?? fallback;\n}\n\nexport { type ModalSurface, ModalSurfaceProvider, useModalSurface };\n",
      "type": "registry:lib"
    },
    {
      "path": "lib/scroll-focus.ts",
      "content": "'use client';\n\nimport { type Ref, type RefCallback, useCallback } from 'react';\n\nconst CANDIDATES =\n  'a[href], button, input, select, textarea, summary, iframe, [tabindex], [contenteditable]';\n\n/* The attributes that can add or remove a Tab stop, or hide what holds one. */\nconst FOCUS_ATTRIBUTES = [\n  'disabled',\n  'tabindex',\n  'hidden',\n  'inert',\n  'type',\n  'href',\n  'contenteditable',\n  'class',\n  'style',\n];\n\n/* Reached by Tab: enabled, not a hidden input, not taken out of the order, and drawn. */\nfunction tabbable(element: HTMLElement) {\n  if (element.matches(':disabled, input[type=hidden], [contenteditable=false]')) return false;\n  if (element.tabIndex < 0 || element.closest('[inert]')) return false;\n  return element.checkVisibility\n    ? element.checkVisibility({ visibilityProperty: true })\n    : element.getClientRects().length > 0;\n}\n\nfunction holdsTabStop(node: HTMLElement) {\n  return [...node.querySelectorAll<HTMLElement>(CANDIDATES)].some(tabbable);\n}\n\n/*\n * Sets the caller's ref and returns its teardown: the cleanup a React 19\n * callback ref returns, else a call with null.\n */\nfunction attach<T>(ref: Ref<T> | undefined, node: T): () => void {\n  if (typeof ref === 'function') {\n    const cleanup = ref(node);\n    return typeof cleanup === 'function' ? cleanup : () => ref(null);\n  }\n  if (ref) ref.current = node;\n  return () => {\n    if (ref) ref.current = null;\n  };\n}\n\n/* Gives the node tabindex 0 while it overflows with no Tab stop inside; returns the teardown. */\nfunction watchTabStop(node: HTMLElement) {\n  let set = false;\n  // Its own size, and its children's: content that grows inside a capped area.\n  const resize = new ResizeObserver(() => update());\n  const update = () => {\n    for (const child of node.children) resize.observe(child);\n    const stuck = node.scrollHeight > node.clientHeight && !holdsTabStop(node);\n    if (stuck === set) return;\n    set = stuck;\n    if (stuck) node.tabIndex = 0;\n    else node.removeAttribute('tabindex');\n  };\n  resize.observe(node);\n  update();\n  const content = new MutationObserver(update);\n  content.observe(node, {\n    childList: true,\n    characterData: true,\n    subtree: true,\n    attributeFilter: FOCUS_ATTRIBUTES,\n  });\n  return () => {\n    resize.disconnect();\n    content.disconnect();\n    if (set) node.removeAttribute('tabindex');\n  };\n}\n\n/*\n * A scroll area with no Tab stop in it takes one while it overflows, so a\n * keyboard can scroll it. Chromium and Firefox do this themselves; Safari\n * does not. A scroll area that holds a field or a button keeps the page's\n * focus order: focusing one scrolls it into view. Hidden inputs and controls\n * that are disabled, hidden, inert or out of the Tab order do not count. A\n * scroll area given its own tabIndex keeps it. Pass the caller's own ref, if\n * any: the returned ref sets it too, and runs its cleanup.\n */\nfunction useScrollFocus<T extends HTMLElement>(ref?: Ref<T>): RefCallback<T> {\n  return useCallback(\n    (node: T | null) => {\n      if (!node) return;\n      const detach = attach(ref, node);\n      const unwatch = node.hasAttribute('tabindex') ? null : watchTabStop(node);\n      return () => {\n        unwatch?.();\n        detach();\n      };\n    },\n    [ref],\n  );\n}\n\nexport { useScrollFocus };\n",
      "type": "registry:lib"
    }
  ],
  "type": "registry:lib"
}