{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "sheet",
  "title": "Sheet",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/sheet/Sheet.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n\n  export interface SheetProps {\n    open?: boolean\n    /** When false the sheet is non-modal: content skips the focus trap,\n     *  body scroll-lock, and autofocus, and the overlay stops intercepting\n     *  pointer events so the page stays interactive (Radix `modal` parity). */\n    modal?: boolean\n    onOpenChange?: (open: boolean) => void\n    children?: Snippet\n  }\n</script>\n\n<script lang=\"ts\">\n  import { setSheetContext } from './context'\n\n  let { open = $bindable(false), modal = true, onOpenChange, children }: SheetProps = $props()\n\n  let titleId: string | undefined = $state(undefined)\n  let descriptionId: string | undefined = $state(undefined)\n\n  function setOpen(next: boolean) {\n    if (next === open) return\n    open = next\n    onOpenChange?.(next)\n  }\n\n  // Getters over $state keep every consumer reactive without runes in context.ts.\n  setSheetContext({\n    get open() {\n      return open\n    },\n    setOpen,\n    get titleId() {\n      return titleId\n    },\n    setTitleId: (id) => (titleId = id),\n    get descriptionId() {\n      return descriptionId\n    },\n    setDescriptionId: (id) => (descriptionId = id),\n    get modal() {\n      return modal\n    },\n  })\n</script>\n\n{@render children?.()}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/sheet/Sheet.svelte"
    },
    {
      "path": "packages/registry-svelte/components/sheet/SheetClose.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLButtonAttributes } from 'svelte/elements'\n\n  export interface SheetCloseProps extends HTMLButtonAttributes {\n    /** Render your own element that closes the sheet — spread `props` onto it. */\n    child?: Snippet<[{ props: Record<string, unknown> }]>\n    ref?: HTMLButtonElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { getSheetContext } from './context'\n\n  let {\n    class: className,\n    child,\n    children,\n    ref = $bindable(null),\n    onclick,\n    ...restProps\n  }: SheetCloseProps = $props()\n\n  const ctx = getSheetContext()\n\n  function handleClick(e: MouseEvent & { currentTarget: EventTarget & HTMLButtonElement }) {\n    ctx?.setOpen(false)\n    onclick?.(e)\n  }\n\n  const mergedProps = $derived({\n    'data-uipkge': '',\n    'data-slot': 'sheet-close',\n    class: cn(className),\n    onclick: handleClick,\n    ...restProps,\n  })\n</script>\n\n{#if child}\n  {@render child({ props: mergedProps })}\n{:else}\n  <button bind:this={ref} type=\"button\" {...mergedProps}>{@render children?.()}</button>\n{/if}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/sheet/SheetClose.svelte"
    },
    {
      "path": "packages/registry-svelte/components/sheet/SheetContent.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface SheetContentProps extends HTMLAttributes<HTMLDivElement> {\n    side?: 'top' | 'right' | 'bottom' | 'left'\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { X } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n  import { getSheetContext } from './context'\n  import SheetOverlay from './SheetOverlay.svelte'\n\n  let { class: className, side = 'right', children, ref = $bindable(null), ...restProps }: SheetContentProps = $props()\n\n  const ctx = getSheetContext()\n  const open = $derived(ctx?.open ?? false)\n\n  let panel: HTMLDivElement | null = $state(null)\n\n  $effect(() => {\n    ref = panel\n  })\n\n  // Hand-rolled dialog behavior (no headless dep yet — see the bits-ui\n  // proposal in the port report): Escape to close, body scroll lock,\n  // autofocus + focus trap while open, and focus restore on close.\n  $effect(() => {\n    if (!open || !ctx) return\n    const node = panel\n    if (!node || typeof document === 'undefined') return\n\n    const previousOverflow = document.body.style.overflow\n    document.body.style.overflow = 'hidden'\n    const previouslyFocused = document.activeElement as HTMLElement | null\n\n    const focusables = () =>\n      [...node.querySelectorAll<HTMLElement>('a[href], button, input, select, textarea, [tabindex]')].filter(\n        (el) => !el.hasAttribute('disabled') && el.tabIndex !== -1 && el.offsetParent !== null,\n      )\n\n    // Focus the first control (or the panel itself) so keyboard users land inside.\n    const first = focusables()[0]\n    if (first) first.focus()\n    else {\n      node.tabIndex = -1\n      node.focus({ preventScroll: true })\n    }\n\n    const onKeyDown = (e: KeyboardEvent) => {\n      if (e.key === 'Escape') {\n        e.preventDefault()\n        ctx.setOpen(false)\n        return\n      }\n      if (e.key !== 'Tab') return\n      const items = focusables()\n      if (items.length === 0) {\n        e.preventDefault()\n        return\n      }\n      const firstItem = items[0]!\n      const lastItem = items[items.length - 1]!\n      if (e.shiftKey && document.activeElement === firstItem) {\n        e.preventDefault()\n        lastItem.focus()\n      } else if (!e.shiftKey && document.activeElement === lastItem) {\n        e.preventDefault()\n        firstItem.focus()\n      }\n    }\n\n    document.addEventListener('keydown', onKeyDown)\n    return () => {\n      document.removeEventListener('keydown', onKeyDown)\n      document.body.style.overflow = previousOverflow\n      previouslyFocused?.focus?.({ preventScroll: true })\n    }\n  })\n</script>\n\n{#if open}\n  <SheetOverlay />\n  <div\n    bind:this={panel}\n    role=\"dialog\"\n    aria-modal=\"true\"\n    aria-labelledby={ctx?.titleId}\n    aria-describedby={ctx?.descriptionId}\n    data-uipkge=\"\"\n    data-slot=\"sheet-content\"\n    data-state=\"open\"\n    data-side={side}\n    class={cn(\n      'bg-background motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=open]:ease-emphasized motion-safe:data-[state=open]:blur-in-2 motion-safe:data-[state=closed]:blur-out-2 fixed z-50 flex flex-col gap-4 overflow-hidden shadow-lg transition ease-in-out motion-safe:data-[state=closed]:duration-200 motion-safe:data-[state=open]:duration-300',\n      side === 'right' &&\n        'motion-safe:data-[state=closed]:slide-out-to-right motion-safe:data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm',\n      side === 'left' &&\n        'motion-safe:data-[state=closed]:slide-out-to-left motion-safe:data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm',\n      side === 'top' &&\n        'motion-safe:data-[state=closed]:slide-out-to-top motion-safe:data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b',\n      side === 'bottom' &&\n        'motion-safe:data-[state=closed]:slide-out-to-bottom motion-safe:data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t',\n      className,\n    )}\n    {...restProps}\n  >\n    {@render children?.()}\n\n    <button\n      type=\"button\"\n      onclick={() => ctx?.setOpen(false)}\n      class=\"ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none\"\n    >\n      <X class=\"size-4\" aria-hidden=\"true\" />\n      <span class=\"sr-only\">Close</span>\n    </button>\n  </div>\n{/if}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/sheet/SheetContent.svelte"
    },
    {
      "path": "packages/registry-svelte/components/sheet/SheetDescription.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface SheetDescriptionProps extends HTMLAttributes<HTMLParagraphElement> {\n    ref?: HTMLParagraphElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { getSheetContext, nextSheetId } from './context'\n\n  let { class: className, id, children, ref = $bindable(null), ...restProps }: SheetDescriptionProps = $props()\n\n  const ctx = getSheetContext()\n  const descriptionId = $derived(id ?? nextSheetId('sheet-description'))\n  $effect(() => {\n    ctx?.setDescriptionId(descriptionId)\n    return () => ctx?.setDescriptionId(undefined)\n  })\n</script>\n\n<p\n  bind:this={ref}\n  id={descriptionId}\n  data-uipkge=\"\"\n  data-slot=\"sheet-description\"\n  class={cn('text-muted-foreground text-sm', className)}\n  {...restProps}\n>\n  {@render children?.()}\n</p>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/sheet/SheetDescription.svelte"
    },
    {
      "path": "packages/registry-svelte/components/sheet/SheetFooter.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface SheetFooterProps extends HTMLAttributes<HTMLDivElement> {\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n\n  let { class: className, children, ref = $bindable(null), ...restProps }: SheetFooterProps = $props()\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge=\"\"\n  data-slot=\"sheet-footer\"\n  class={cn('mt-auto flex flex-col gap-2 p-4', className)}\n  {...restProps}\n>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/sheet/SheetFooter.svelte"
    },
    {
      "path": "packages/registry-svelte/components/sheet/SheetHeader.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface SheetHeaderProps extends HTMLAttributes<HTMLDivElement> {\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n\n  let { class: className, children, ref = $bindable(null), ...restProps }: SheetHeaderProps = $props()\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge=\"\"\n  data-slot=\"sheet-header\"\n  class={cn('flex flex-col gap-1.5 p-4', className)}\n  {...restProps}\n>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/sheet/SheetHeader.svelte"
    },
    {
      "path": "packages/registry-svelte/components/sheet/SheetOverlay.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface SheetOverlayProps extends HTMLAttributes<HTMLDivElement> {\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { getSheetContext } from './context'\n  import { portal } from './portal'\n\n  let { class: className, ref = $bindable(null), onclick, ...restProps }: SheetOverlayProps = $props()\n\n  const ctx = getSheetContext()\n\n  function handleClick(e: MouseEvent & { currentTarget: EventTarget & HTMLDivElement }) {\n    ctx?.setOpen(false)\n    onclick?.(e)\n  }\n</script>\n\n<div\n  use:portal\n  bind:this={ref}\n  data-uipkge=\"\"\n  data-slot=\"sheet-overlay\"\n  data-state={ctx?.open ? 'open' : 'closed'}\n  class={cn(\n    'motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=open]:fade-in-0 bg-foreground/50 fixed inset-0 z-50',\n    ctx?.modal === false && 'pointer-events-none opacity-0',\n    className,\n  )}\n  onclick={handleClick}\n  {...restProps}\n></div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/sheet/SheetOverlay.svelte"
    },
    {
      "path": "packages/registry-svelte/components/sheet/SheetTitle.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface SheetTitleProps extends HTMLAttributes<HTMLHeadingElement> {\n    ref?: HTMLHeadingElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { getSheetContext, nextSheetId } from './context'\n\n  let { class: className, id, children, ref = $bindable(null), ...restProps }: SheetTitleProps = $props()\n\n  const ctx = getSheetContext()\n  const titleId = $derived(id ?? nextSheetId('sheet-title'))\n  $effect(() => {\n    ctx?.setTitleId(titleId)\n    return () => ctx?.setTitleId(undefined)\n  })\n</script>\n\n<h2\n  bind:this={ref}\n  id={titleId}\n  data-uipkge=\"\"\n  data-slot=\"sheet-title\"\n  class={cn('text-foreground font-semibold', className)}\n  {...restProps}\n>\n  {@render children?.()}\n</h2>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/sheet/SheetTitle.svelte"
    },
    {
      "path": "packages/registry-svelte/components/sheet/SheetTrigger.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLButtonAttributes } from 'svelte/elements'\n\n  export interface SheetTriggerProps extends HTMLButtonAttributes {\n    /** Render your own element that opens the sheet — spread `props` onto it. */\n    child?: Snippet<[{ props: Record<string, unknown> }]>\n    ref?: HTMLButtonElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { getSheetContext } from './context'\n\n  let {\n    class: className,\n    child,\n    children,\n    ref = $bindable(null),\n    onclick,\n    ...restProps\n  }: SheetTriggerProps = $props()\n\n  const ctx = getSheetContext()\n\n  function handleClick(e: MouseEvent & { currentTarget: EventTarget & HTMLButtonElement }) {\n    ctx?.setOpen(true)\n    onclick?.(e)\n  }\n\n  const mergedProps = $derived({\n    'data-uipkge': '',\n    'data-slot': 'sheet-trigger',\n    class: cn(className),\n    onclick: handleClick,\n    ...restProps,\n  })\n</script>\n\n{#if child}\n  {@render child({ props: mergedProps })}\n{:else}\n  <button bind:this={ref} type=\"button\" {...mergedProps}>{@render children?.()}</button>\n{/if}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/sheet/SheetTrigger.svelte"
    },
    {
      "path": "packages/registry-svelte/components/sheet/context.ts",
      "content": "import { getContext, hasContext, setContext } from 'svelte'\n\nconst SHEET_CONTEXT_KEY = Symbol('uipkge-sheet')\n\nexport interface SheetContextValue {\n  readonly open: boolean\n  setOpen: (open: boolean) => void\n  readonly titleId: string | undefined\n  setTitleId: (id: string | undefined) => void\n  readonly descriptionId: string | undefined\n  setDescriptionId: (id: string | undefined) => void\n  readonly modal: boolean\n}\n\nexport function setSheetContext(value: SheetContextValue) {\n  setContext(SHEET_CONTEXT_KEY, value)\n}\n\nexport function getSheetContext(): SheetContextValue | undefined {\n  if (!hasContext(SHEET_CONTEXT_KEY)) return undefined\n  return getContext<SheetContextValue>(SHEET_CONTEXT_KEY)\n}\n\nlet sheetIdCounter = 0\n\n/** Cheap unique id for aria-labelledby wiring. Module-scoped counter is enough per document. */\nexport function nextSheetId(prefix: string) {\n  sheetIdCounter += 1\n  return `uipkge-${prefix}-${sheetIdCounter}`\n}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/sheet/context.ts"
    },
    {
      "path": "packages/registry-svelte/components/sheet/index.ts",
      "content": "export { default as Sheet, type SheetProps } from './Sheet.svelte'\nexport { default as SheetClose, type SheetCloseProps } from './SheetClose.svelte'\nexport { default as SheetContent, type SheetContentProps } from './SheetContent.svelte'\nexport { default as SheetDescription, type SheetDescriptionProps } from './SheetDescription.svelte'\nexport { default as SheetFooter, type SheetFooterProps } from './SheetFooter.svelte'\nexport { default as SheetHeader, type SheetHeaderProps } from './SheetHeader.svelte'\nexport { default as SheetTitle, type SheetTitleProps } from './SheetTitle.svelte'\nexport { default as SheetTrigger, type SheetTriggerProps } from './SheetTrigger.svelte'\nexport { getSheetContext, setSheetContext, type SheetContextValue } from './context'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/sheet/index.ts"
    }
  ],
  "dependencies": [
    "@lucide/svelte"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Side-mounted modal that slides in from the top, right, bottom, or left edge. Use for filter panels, edit drawers, and mobile menus.",
  "categories": [
    "overlay"
  ]
}