{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "dropdown-menu",
  "title": "Dropdown Menu",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/dropdown-menu/DropdownMenu.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n\n  export interface DropdownMenuProps {\n    /** Controlled open state. Use `bind:open` for two-way binding. */\n    open?: boolean\n    /** Called whenever the open state changes. */\n    onOpenChange?: (open: boolean) => void\n    children?: Snippet\n  }\n</script>\n\n<script lang=\"ts\">\n  import { focusMenuItem, setMenuContext } from './dropdown-menu-context'\n\n  let { open = $bindable(false), onOpenChange, children }: DropdownMenuProps = $props()\n\n  const uid = $props.id()\n  const ids = { trigger: `${uid}-trigger`, content: `${uid}-content` }\n\n  function setOpen(next: boolean) {\n    if (open === next) return\n    open = next\n    onOpenChange?.(next)\n  }\n\n  function closeAndFocusTrigger() {\n    setOpen(false)\n    document.getElementById(ids.trigger)?.focus()\n  }\n\n  setMenuContext({\n    ids,\n    isOpen: () => open,\n    setOpen,\n    closeAndFocusTrigger,\n  })\n\n  $effect(() => {\n    if (!open) return\n    function onPointerDown(e: PointerEvent) {\n      const target = e.target as Node | null\n      if (!target || !(target instanceof Element)) return\n      // Clicks inside the trigger, content, or any sub-content keep the menu open.\n      if (target.closest(`#${CSS.escape(ids.trigger)}, #${CSS.escape(ids.content)}, [data-slot=\"dropdown-menu-sub-content\"]`))\n        return\n      setOpen(false)\n    }\n    function onKeyDown(e: KeyboardEvent) {\n      if (e.key === 'Escape') closeAndFocusTrigger()\n      // Tabbing away dismisses; focus continues naturally.\n      if (e.key === 'Tab') setOpen(false)\n      // Arrowing from a focused trigger jumps into the menu (covers both the\n      // default button and `child` triggers like Button).\n      if ((e.key === 'ArrowDown' || e.key === 'ArrowUp') && document.activeElement?.id === ids.trigger) {\n        e.preventDefault()\n        focusMenuItem(ids.content, e.key === 'ArrowDown' ? 'first' : 'last')\n      }\n    }\n    // Capture phase so a trigger toggle click (bubble) doesn't instantly re-close.\n    document.addEventListener('pointerdown', onPointerDown, true)\n    document.addEventListener('keydown', onKeyDown)\n    return () => {\n      document.removeEventListener('pointerdown', onPointerDown, true)\n      document.removeEventListener('keydown', onKeyDown)\n    }\n  })\n</script>\n\n{@render children?.()}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/DropdownMenu.svelte"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/DropdownMenuCheckboxItem.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface DropdownMenuCheckboxItemProps extends HTMLAttributes<HTMLDivElement> {\n    /** Controlled checked state. Use `bind:checked` for two-way binding. */\n    checked?: boolean\n    /** Called with the next checked value. Toggling keeps the menu open. */\n    onCheckedChange?: (checked: boolean) => void\n    disabled?: boolean\n    /** Custom indicator icon (default: Check). */\n    indicatorIcon?: Snippet\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { Check } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n\n  let {\n    class: className,\n    checked = $bindable(false),\n    onCheckedChange,\n    disabled = false,\n    indicatorIcon,\n    children,\n    ref = $bindable(null),\n    onclick,\n    ...restProps\n  }: DropdownMenuCheckboxItemProps = $props()\n\n  function toggle(e: Event) {\n    if (disabled) return\n    const next = !checked\n    checked = next\n    onCheckedChange?.(next)\n  }\n</script>\n\n<div\n  bind:this={ref}\n  role=\"menuitemcheckbox\"\n  tabindex={disabled ? undefined : -1}\n  aria-checked={checked}\n  aria-disabled={disabled || undefined}\n  data-uipkge\n  data-slot=\"dropdown-menu-checkbox-item\"\n  data-state={checked ? 'checked' : 'unchecked'}\n  data-disabled={disabled ? '' : undefined}\n  class={cn(\n    'focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus-visible:ring-2 focus-visible:ring-inset data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\\'size-\\'])]:size-4',\n    className,\n  )}\n  onclick={(e) => {\n    toggle(e)\n    onclick?.(e)\n  }}\n  onkeydown={(e) => {\n    if (e.key === 'Enter' || e.key === ' ') {\n      e.preventDefault()\n      toggle(e)\n    }\n  }}\n  {...restProps}\n>\n  <span class=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n    {#if checked}\n      {#if indicatorIcon}\n        {@render indicatorIcon()}\n      {:else}\n        <Check class=\"size-4\" aria-hidden=\"true\" />\n      {/if}\n    {/if}\n  </span>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/DropdownMenuCheckboxItem.svelte"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/DropdownMenuContent.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface DropdownMenuContentProps extends HTMLAttributes<HTMLDivElement> {\n    side?: 'top' | 'right' | 'bottom' | 'left'\n    align?: 'start' | 'center' | 'end'\n    sideOffset?: number\n    alignOffset?: number\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { focusMenuItem, getMenuContext } from './dropdown-menu-context'\n  import { dropdownMenuContentVariants } from './dropdown-menu-content.variants'\n\n  let {\n    class: className,\n    side = 'bottom',\n    align = 'start',\n    sideOffset = 4,\n    alignOffset = 0,\n    children,\n    ref = $bindable(null),\n    onkeydown,\n    ...restProps\n  }: DropdownMenuContentProps = $props()\n\n  const menu = getMenuContext()\n  const open = $derived(menu.isOpen())\n\n  // Positioned `fixed` from the trigger rect (no portal needed). Hidden until\n  // the first measurement so the menu never flashes at the origin.\n  let style = $state('position: fixed; top: 0; left: 0; visibility: hidden;')\n\n  function position() {\n    const trigger = document.getElementById(menu.ids.trigger)\n    const content = document.getElementById(menu.ids.content)\n    if (!trigger || !content) return\n    const t = trigger.getBoundingClientRect()\n    const w = content.offsetWidth\n    const h = content.offsetHeight\n    let top = 0\n    let left = 0\n    if (side === 'bottom' || side === 'top') {\n      top = side === 'bottom' ? t.bottom + sideOffset : t.top - h - sideOffset\n      left =\n        align === 'start'\n          ? t.left + alignOffset\n          : align === 'end'\n            ? t.right - w - alignOffset\n            : t.left + t.width / 2 - w / 2\n    } else {\n      left = side === 'right' ? t.right + sideOffset : t.left - w - sideOffset\n      top =\n        align === 'start'\n          ? t.top + alignOffset\n          : align === 'end'\n            ? t.bottom - h - alignOffset\n            : t.top + t.height / 2 - h / 2\n    }\n    left = Math.max(8, Math.min(left, window.innerWidth - w - 8))\n    top = Math.max(8, Math.min(top, window.innerHeight - 16))\n    const origin =\n      side === 'bottom'\n        ? `top ${align === 'start' ? 'left' : align === 'end' ? 'right' : 'center'}`\n        : side === 'top'\n          ? `bottom ${align === 'start' ? 'left' : align === 'end' ? 'right' : 'center'}`\n          : side === 'right'\n            ? 'center left'\n            : 'center right'\n    // The variants file intentionally keeps the twin's custom-property names\n    // (see its comment); resolve them here from the measured placement.\n    style =\n      `position: fixed; top: ${top}px; left: ${left}px; ` +\n      `--reka-dropdown-menu-content-available-height: ${Math.max(80, window.innerHeight - top - 8)}px; ` +\n      `--reka-dropdown-menu-content-transform-origin: ${origin};`\n  }\n\n  $effect(() => {\n    if (!open) return\n    position()\n    window.addEventListener('resize', position)\n    // Capture phase: an ancestor scroll re-seats the menu under the trigger.\n    window.addEventListener('scroll', position, true)\n    return () => {\n      window.removeEventListener('resize', position)\n      window.removeEventListener('scroll', position, true)\n    }\n  })\n\n  function handleKeyDown(e: KeyboardEvent) {\n    if (e.key === 'ArrowDown') {\n      e.preventDefault()\n      focusMenuItem(menu.ids.content, 'next')\n    } else if (e.key === 'ArrowUp') {\n      e.preventDefault()\n      focusMenuItem(menu.ids.content, 'prev')\n    } else if (e.key === 'Home') {\n      e.preventDefault()\n      focusMenuItem(menu.ids.content, 'first')\n    } else if (e.key === 'End') {\n      e.preventDefault()\n      focusMenuItem(menu.ids.content, 'last')\n    }\n  }\n</script>\n\n{#if open}\n  <!-- outline-none replicates reka's content outline reset. -->\n  <div\n    bind:this={ref}\n    id={menu.ids.content}\n    role=\"menu\"\n    aria-orientation=\"vertical\"\n    tabindex=\"-1\"\n    data-uipkge\n    data-slot=\"dropdown-menu-content\"\n    data-state=\"open\"\n    data-side={side}\n    data-align={align}\n    class={cn(dropdownMenuContentVariants({ side }), 'outline-none', className)}\n    {style}\n    onkeydown={(e) => {\n      handleKeyDown(e)\n      onkeydown?.(e)\n    }}\n    {...restProps}\n  >\n    {@render children?.()}\n  </div>\n{/if}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/DropdownMenuContent.svelte"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/dropdown-menu-content.variants.ts",
      "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\n\n/**\n * Variant definitions live in their own file (rather than the package\n * `index.ts`) so consuming components can import without creating a circular\n * dependency through the index.\n *\n * Class strings are kept 1:1 with the Vue twin, including the two\n * `--reka-dropdown-menu-*` custom properties: DropdownMenuContent sets them\n * inline from its measured position (max-height clamps to the available\n * viewport space; transform-origin follows the placement side), so the shared\n * animation utilities resolve exactly as they do on reka-ui.\n */\nexport const dropdownMenuContentVariants = cva(\n  'z-50 max-h-(--reka-dropdown-menu-content-available-height) min-w-32 origin-(--reka-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n  {\n    variants: {\n      side: {\n        bottom: 'data-[side=bottom]:slide-in-from-top-2',\n        left: 'data-[side=left]:slide-in-from-right-2',\n        right: 'data-[side=right]:slide-in-from-left-2',\n        top: 'data-[side=top]:slide-in-from-bottom-2',\n      },\n    },\n  },\n)\n\nexport type DropdownMenuContentVariants = VariantProps<typeof dropdownMenuContentVariants>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/dropdown-menu-content.variants.ts"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/dropdown-menu-context.ts",
      "content": "import { getContext, setContext } from 'svelte'\n\n/**\n * Hand-rolled menu state (no headless dependency installed in the Svelte\n * registry yet). If bits-ui (the shadcn-svelte standard) is ever adopted,\n * these parts should be rebuilt on top of it.\n */\n\nexport interface DropdownMenuContextValue {\n  readonly ids: { trigger: string; content: string }\n  isOpen: () => boolean\n  setOpen: (open: boolean) => void\n  closeAndFocusTrigger: () => void\n}\n\nconst MENU_KEY = Symbol('uipkge:dropdown-menu')\n\nexport function setMenuContext(value: DropdownMenuContextValue): void {\n  setContext(MENU_KEY, value)\n}\n\nexport function getMenuContext(): DropdownMenuContextValue {\n  return getContext<DropdownMenuContextValue>(MENU_KEY)\n}\n\nexport interface DropdownMenuRadioContextValue {\n  getValue: () => string\n  setValue: (value: string) => void\n}\n\nconst RADIO_KEY = Symbol('uipkge:dropdown-menu-radio')\n\nexport function setRadioContext(value: DropdownMenuRadioContextValue): void {\n  setContext(RADIO_KEY, value)\n}\n\nexport function getRadioContext(): DropdownMenuRadioContextValue {\n  return getContext<DropdownMenuRadioContextValue>(RADIO_KEY)\n}\n\nexport interface DropdownMenuSubContextValue {\n  isSubOpen: () => boolean\n  setSubOpen: (open: boolean) => void\n  /** Close after a grace period so the pointer can travel between the\n   *  trigger and the panel. Any setSubOpen call cancels the pending close. */\n  scheduleClose: (delay?: number) => void\n  readonly ids: { trigger: string; content: string }\n}\n\nconst SUB_KEY = Symbol('uipkge:dropdown-menu-sub')\n\nexport function setSubContext(value: DropdownMenuSubContextValue): void {\n  setContext(SUB_KEY, value)\n}\n\nexport function getSubContext(): DropdownMenuSubContextValue {\n  return getContext<DropdownMenuSubContextValue>(SUB_KEY)\n}\n\nconst ITEM_SELECTOR =\n  '[role=\"menuitem\"]:not([data-disabled]), [role=\"menuitemcheckbox\"]:not([data-disabled]), [role=\"menuitemradio\"]:not([data-disabled])'\n\n/** Arrow-key / Home / End focus movement scoped to one menu container. */\nexport function focusMenuItem(containerId: string, where: 'first' | 'last' | 'next' | 'prev'): void {\n  const container = document.getElementById(containerId)\n  if (!container) return\n  const items = [...container.querySelectorAll(ITEM_SELECTOR)].filter(\n    (el) => el instanceof HTMLElement,\n  ) as HTMLElement[]\n  if (items.length === 0) {\n    container.focus()\n    return\n  }\n  const current = items.indexOf(document.activeElement as HTMLElement)\n  const next =\n    where === 'first'\n      ? items[0]\n      : where === 'last'\n        ? items[items.length - 1]\n        : where === 'next'\n          ? items[(current + 1) % items.length]\n          : items[(current - 1 + items.length) % items.length]\n  next?.focus()\n}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/dropdown-menu-context.ts"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/DropdownMenuGroup.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface DropdownMenuGroupProps extends HTMLAttributes<HTMLDivElement> {\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  let { children, ref = $bindable(null), ...restProps }: DropdownMenuGroupProps = $props()\n</script>\n\n<div bind:this={ref} role=\"group\" data-uipkge data-slot=\"dropdown-menu-group\" {...restProps}>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/DropdownMenuGroup.svelte"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/DropdownMenuItem.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface DropdownMenuItemProps extends HTMLAttributes<HTMLDivElement> {\n    disabled?: boolean\n    inset?: boolean\n    variant?: 'default' | 'destructive'\n    /** Called on selection. Selecting an item closes the menu. */\n    onSelect?: (e: Event) => void\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { getMenuContext } from './dropdown-menu-context'\n\n  let {\n    class: className,\n    disabled = false,\n    inset = false,\n    variant = 'default',\n    onSelect,\n    children,\n    ref = $bindable(null),\n    onclick,\n    ...restProps\n  }: DropdownMenuItemProps = $props()\n\n  const menu = getMenuContext()\n\n  function select(e: Event) {\n    if (disabled) return\n    onSelect?.(e)\n    menu.closeAndFocusTrigger()\n  }\n</script>\n\n<div\n  bind:this={ref}\n  role=\"menuitem\"\n  tabindex={disabled ? undefined : -1}\n  aria-disabled={disabled || undefined}\n  data-uipkge\n  data-slot=\"dropdown-menu-item\"\n  data-inset={inset ? '' : undefined}\n  data-variant={variant}\n  data-disabled={disabled ? '' : undefined}\n  class={cn(\n    'focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*=\\'text-\\'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus-visible:ring-2 focus-visible:ring-inset data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\\'size-\\'])]:size-4',\n    className,\n  )}\n  onclick={(e) => {\n    select(e)\n    onclick?.(e)\n  }}\n  onkeydown={(e) => {\n    if (e.key === 'Enter' || e.key === ' ') {\n      e.preventDefault()\n      select(e)\n    }\n  }}\n  {...restProps}\n>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/DropdownMenuItem.svelte"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/DropdownMenuLabel.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface DropdownMenuLabelProps extends HTMLAttributes<HTMLDivElement> {\n    inset?: boolean\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n\n  let { class: className, inset = false, children, ref = $bindable(null), ...restProps }: DropdownMenuLabelProps =\n    $props()\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge\n  data-slot=\"dropdown-menu-label\"\n  data-inset={inset ? '' : undefined}\n  class={cn('px-2 py-1.5 text-sm font-medium data-[inset]:pl-8', className)}\n  {...restProps}\n>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/DropdownMenuLabel.svelte"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/DropdownMenuRadioGroup.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface DropdownMenuRadioGroupProps extends HTMLAttributes<HTMLDivElement> {\n    /** Controlled selected value. Use `bind:value` for two-way binding. */\n    value?: string\n    /** Called with the next value. Selecting keeps the menu open. */\n    onValueChange?: (value: string) => void\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { setRadioContext } from './dropdown-menu-context'\n\n  let {\n    value = $bindable(''),\n    onValueChange,\n    children,\n    ref = $bindable(null),\n    ...restProps\n  }: DropdownMenuRadioGroupProps = $props()\n\n  setRadioContext({\n    getValue: () => value,\n    setValue: (next: string) => {\n      if (value === next) return\n      value = next\n      onValueChange?.(next)\n    },\n  })\n</script>\n\n<div bind:this={ref} role=\"group\" data-uipkge data-slot=\"dropdown-menu-radio-group\" {...restProps}>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/DropdownMenuRadioGroup.svelte"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/DropdownMenuRadioItem.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface DropdownMenuRadioItemProps extends HTMLAttributes<HTMLDivElement> {\n    value: string\n    disabled?: boolean\n    /** Custom indicator icon (default: filled Circle). */\n    indicatorIcon?: Snippet\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { Circle } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n  import { getRadioContext } from './dropdown-menu-context'\n\n  let {\n    class: className,\n    value,\n    disabled = false,\n    indicatorIcon,\n    children,\n    ref = $bindable(null),\n    onclick,\n    ...restProps\n  }: DropdownMenuRadioItemProps = $props()\n\n  const group = getRadioContext()\n  const checked = $derived(group.getValue() === value)\n\n  function select() {\n    if (disabled) return\n    group.setValue(value)\n  }\n</script>\n\n<div\n  bind:this={ref}\n  role=\"menuitemradio\"\n  tabindex={disabled ? undefined : -1}\n  aria-checked={checked}\n  aria-disabled={disabled || undefined}\n  data-uipkge\n  data-slot=\"dropdown-menu-radio-item\"\n  data-state={checked ? 'checked' : 'unchecked'}\n  data-disabled={disabled ? '' : undefined}\n  class={cn(\n    'focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus-visible:ring-2 focus-visible:ring-inset data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\\'size-\\'])]:size-4',\n    className,\n  )}\n  onclick={(e) => {\n    select()\n    onclick?.(e)\n  }}\n  onkeydown={(e) => {\n    if (e.key === 'Enter' || e.key === ' ') {\n      e.preventDefault()\n      select()\n    }\n  }}\n  {...restProps}\n>\n  <span class=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n    {#if checked}\n      {#if indicatorIcon}\n        {@render indicatorIcon()}\n      {:else}\n        <Circle class=\"size-2 fill-current\" aria-hidden=\"true\" />\n      {/if}\n    {/if}\n  </span>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/DropdownMenuRadioItem.svelte"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/DropdownMenuSeparator.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface DropdownMenuSeparatorProps 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, ref = $bindable(null), ...restProps }: DropdownMenuSeparatorProps = $props()\n</script>\n\n<div\n  bind:this={ref}\n  role=\"separator\"\n  data-uipkge\n  data-slot=\"dropdown-menu-separator\"\n  class={cn('bg-border -mx-1 my-1 h-px', className)}\n  {...restProps}\n></div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/DropdownMenuSeparator.svelte"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/DropdownMenuShortcut.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface DropdownMenuShortcutProps extends HTMLAttributes<HTMLElement> {\n    ref?: HTMLElement | 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 }: DropdownMenuShortcutProps = $props()\n</script>\n\n<span\n  bind:this={ref}\n  data-uipkge\n  data-slot=\"dropdown-menu-shortcut\"\n  class={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}\n  {...restProps}\n>\n  {@render children?.()}\n</span>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/DropdownMenuShortcut.svelte"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/DropdownMenuSub.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n\n  export interface DropdownMenuSubProps {\n    /** Controlled sub-menu open state. Use `bind:open` for two-way binding. */\n    open?: boolean\n    /** Called whenever the sub-menu open state changes. */\n    onOpenChange?: (open: boolean) => void\n    children?: Snippet\n  }\n</script>\n\n<script lang=\"ts\">\n  import { setSubContext } from './dropdown-menu-context'\n\n  let { open = $bindable(false), onOpenChange, children }: DropdownMenuSubProps = $props()\n\n  const uid = $props.id()\n  const ids = { trigger: `${uid}-trigger`, content: `${uid}-content` }\n\n  let closeTimer: ReturnType<typeof setTimeout> | null = null\n\n  function clearTimer() {\n    if (closeTimer) {\n      clearTimeout(closeTimer)\n      closeTimer = null\n    }\n  }\n\n  function setSubOpen(next: boolean) {\n    // Any explicit set wins over a pending scheduled close (e.g. entering\n    // the panel while the trigger's grace timer is still running).\n    clearTimer()\n    if (open === next) return\n    open = next\n    onOpenChange?.(next)\n  }\n\n  function scheduleClose(delay = 120) {\n    clearTimer()\n    closeTimer = setTimeout(() => {\n      closeTimer = null\n      setSubOpen(false)\n    }, delay)\n  }\n\n  setSubContext({ isSubOpen: () => open, setSubOpen, scheduleClose, ids })\n</script>\n\n{@render children?.()}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/DropdownMenuSub.svelte"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/DropdownMenuSubContent.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface DropdownMenuSubContentProps extends HTMLAttributes<HTMLDivElement> {\n    sideOffset?: number\n    alignOffset?: number\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { focusMenuItem, getSubContext } from './dropdown-menu-context'\n\n  let {\n    class: className,\n    sideOffset = 8,\n    alignOffset = -4,\n    children,\n    ref = $bindable(null),\n    onkeydown,\n    ...restProps\n  }: DropdownMenuSubContentProps = $props()\n\n  const sub = getSubContext()\n  const subOpen = $derived(sub.isSubOpen())\n\n  let style = $state('position: fixed; top: 0; left: 0; visibility: hidden;')\n\n  function position() {\n    const trigger = document.getElementById(sub.ids.trigger)\n    const content = document.getElementById(sub.ids.content)\n    if (!trigger || !content) return\n    const t = trigger.getBoundingClientRect()\n    const w = content.offsetWidth\n    // Cascade to the right; flip left when there is no room.\n    let left = t.right + sideOffset\n    if (left + w > window.innerWidth - 8) left = t.left - w - sideOffset\n    left = Math.max(8, left)\n    const top = Math.max(8, Math.min(t.top + alignOffset, window.innerHeight - 16))\n    style =\n      `position: fixed; top: ${top}px; left: ${left}px; ` +\n      `--reka-dropdown-menu-content-available-height: ${Math.max(80, window.innerHeight - top - 8)}px; ` +\n      `--reka-dropdown-menu-content-transform-origin: center left;`\n  }\n\n  $effect(() => {\n    if (!subOpen) return\n    position()\n    window.addEventListener('resize', position)\n    window.addEventListener('scroll', position, true)\n    return () => {\n      window.removeEventListener('resize', position)\n      window.removeEventListener('scroll', position, true)\n    }\n  })\n\n  function handleKeyDown(e: KeyboardEvent) {\n    if (e.key === 'ArrowDown') {\n      e.preventDefault()\n      focusMenuItem(sub.ids.content, 'next')\n    } else if (e.key === 'ArrowUp') {\n      e.preventDefault()\n      focusMenuItem(sub.ids.content, 'prev')\n    } else if (e.key === 'ArrowLeft') {\n      e.preventDefault()\n      sub.setSubOpen(false)\n      document.getElementById(sub.ids.trigger)?.focus()\n    } else if (e.key === 'Home') {\n      e.preventDefault()\n      focusMenuItem(sub.ids.content, 'first')\n    } else if (e.key === 'End') {\n      e.preventDefault()\n      focusMenuItem(sub.ids.content, 'last')\n    }\n  }\n</script>\n\n{#if subOpen}\n  <div\n    bind:this={ref}\n    id={sub.ids.content}\n    role=\"menu\"\n    aria-orientation=\"vertical\"\n    tabindex=\"-1\"\n    data-uipkge\n    data-slot=\"dropdown-menu-sub-content\"\n    data-state=\"open\"\n    data-side=\"right\"\n    class={cn(\n      'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--reka-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg outline-none',\n      className,\n    )}\n    {style}\n    onkeydown={(e) => {\n      handleKeyDown(e)\n      onkeydown?.(e)\n    }}\n    onmouseenter={() => sub.setSubOpen(true)}\n    onmouseleave={() => sub.scheduleClose()}\n    {...restProps}\n  >\n    {@render children?.()}\n  </div>\n{/if}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/DropdownMenuSubContent.svelte"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/DropdownMenuSubTrigger.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface DropdownMenuSubTriggerProps extends HTMLAttributes<HTMLDivElement> {\n    disabled?: boolean\n    inset?: boolean\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { ChevronRight } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n  import { focusMenuItem, getSubContext } from './dropdown-menu-context'\n\n  let {\n    class: className,\n    disabled = false,\n    inset = false,\n    children,\n    ref = $bindable(null),\n    onclick,\n    ...restProps\n  }: DropdownMenuSubTriggerProps = $props()\n\n  const sub = getSubContext()\n  const subOpen = $derived(sub.isSubOpen())\n\n  function open() {\n    if (disabled) return\n    sub.setSubOpen(true)\n  }\n\n  function scheduleClose() {\n    if (disabled) return\n    sub.scheduleClose()\n  }\n\n  function toggle(e: Event) {\n    if (disabled) return\n    e.stopPropagation()\n    sub.setSubOpen(!sub.isSubOpen())\n  }\n</script>\n\n<div\n  bind:this={ref}\n  id={sub.ids.trigger}\n  role=\"menuitem\"\n  tabindex={disabled ? undefined : -1}\n  aria-haspopup=\"menu\"\n  aria-expanded={subOpen}\n  aria-controls={sub.ids.content}\n  aria-disabled={disabled || undefined}\n  data-uipkge\n  data-slot=\"dropdown-menu-sub-trigger\"\n  data-state={subOpen ? 'open' : 'closed'}\n  data-inset={inset ? '' : undefined}\n  data-disabled={disabled ? '' : undefined}\n  class={cn(\n    'focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring data-[state=open]:bg-accent data-[state=open]:text-accent-foreground data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*=\\'text-\\'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus-visible:ring-2 focus-visible:ring-inset data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\\'size-\\'])]:size-4',\n    className,\n  )}\n  onclick={(e) => {\n    toggle(e)\n    onclick?.(e)\n  }}\n  onkeydown={(e) => {\n    if (e.key === 'ArrowRight' && !subOpen) {\n      e.preventDefault()\n      open()\n      focusMenuItem(sub.ids.content, 'first')\n    } else if (e.key === 'ArrowLeft' && subOpen) {\n      e.preventDefault()\n      sub.setSubOpen(false)\n    } else if (e.key === 'Enter' || e.key === ' ') {\n      e.preventDefault()\n      toggle(e)\n      if (!disabled && sub.isSubOpen()) focusMenuItem(sub.ids.content, 'first')\n    }\n  }}\n  onmouseenter={open}\n  onmouseleave={scheduleClose}\n  onfocus={open}\n  {...restProps}\n>\n  {@render children?.()}\n  <ChevronRight class=\"ml-auto size-4\" />\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/DropdownMenuSubTrigger.svelte"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/DropdownMenuTrigger.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLButtonAttributes } from 'svelte/elements'\n\n  export interface DropdownMenuTriggerProps extends HTMLButtonAttributes {\n    /** Render your own trigger element (e.g. a Button) with the trigger's\n     *  props — the Svelte counterpart of reka's `as-child`. */\n    child?: Snippet<[{ props: Record<string, unknown> }]>\n    ref?: HTMLButtonElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { getMenuContext } from './dropdown-menu-context'\n\n  let {\n    class: className,\n    disabled,\n    child,\n    children,\n    ref = $bindable(null),\n    onclick,\n    ...restProps\n  }: DropdownMenuTriggerProps = $props()\n\n  const menu = getMenuContext()\n  const open = $derived(menu.isOpen())\n\n  function toggle() {\n    if (disabled) return\n    menu.setOpen(!menu.isOpen())\n  }\n\n  // `child` consumers spread these onto their own element; the runtime event\n  // always carries currentTarget, hence the narrow-to-wide call below.\n  function handleChildClick(e: MouseEvent) {\n    toggle()\n    ;(onclick as unknown as ((e: MouseEvent) => void) | undefined)?.(e)\n  }\n\n  const sharedProps = $derived({\n    id: menu.ids.trigger,\n    'aria-haspopup': 'menu' as const,\n    'aria-expanded': open,\n    'aria-controls': menu.ids.content,\n    'data-state': open ? 'open' : 'closed',\n    disabled,\n    ...restProps,\n  })\n</script>\n\n{#if child}\n  {@render child({\n    props: {\n      ...sharedProps,\n      'data-uipkge': '',\n      'data-slot': 'dropdown-menu-trigger',\n      class: className,\n      onclick: handleChildClick,\n    },\n  })}\n{:else}\n  <button\n    bind:this={ref}\n    type=\"button\"\n    data-uipkge\n    data-slot=\"dropdown-menu-trigger\"\n    class={className}\n    {...sharedProps}\n    onclick={(e) => {\n      toggle()\n      onclick?.(e)\n    }}\n  >\n    {@render children?.()}\n  </button>\n{/if}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/DropdownMenuTrigger.svelte"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/DropdownMenuPortal.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n\n  export interface DropdownMenuPortalProps {\n    children?: Snippet\n  }\n</script>\n\n<script lang=\"ts\">\n  import { portal } from './portal'\n\n  let { children }: DropdownMenuPortalProps = $props()\n</script>\n\n<div use:portal data-uipkge data-slot=\"dropdown-menu-portal\" style:display=\"contents\">\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/DropdownMenuPortal.svelte"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/portal.ts",
      "content": "import type { Action } from 'svelte/action'\n\n/**\n * Move the node to `document.body` (or a selector/element target) while it is\n * mounted. Actions only run in the browser, so this is SSR-safe.\n */\nexport const portal: Action<HTMLElement, HTMLElement | string | undefined> = (node, target) => {\n  const targetEl = typeof target === 'string' ? document.querySelector(target) : (target ?? document.body)\n  ;(targetEl ?? document.body).appendChild(node)\n  return {\n    destroy() {\n      node.remove()\n    },\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/portal.ts"
    },
    {
      "path": "packages/registry-svelte/components/dropdown-menu/index.ts",
      "content": "export { default as DropdownMenu, type DropdownMenuProps } from './DropdownMenu.svelte'\nexport {\n  default as DropdownMenuCheckboxItem,\n  type DropdownMenuCheckboxItemProps,\n} from './DropdownMenuCheckboxItem.svelte'\nexport { default as DropdownMenuContent, type DropdownMenuContentProps } from './DropdownMenuContent.svelte'\nexport { default as DropdownMenuGroup, type DropdownMenuGroupProps } from './DropdownMenuGroup.svelte'\nexport { default as DropdownMenuItem, type DropdownMenuItemProps } from './DropdownMenuItem.svelte'\nexport { default as DropdownMenuLabel, type DropdownMenuLabelProps } from './DropdownMenuLabel.svelte'\nexport { default as DropdownMenuPortal, type DropdownMenuPortalProps } from './DropdownMenuPortal.svelte'\nexport { default as DropdownMenuRadioGroup, type DropdownMenuRadioGroupProps } from './DropdownMenuRadioGroup.svelte'\nexport { default as DropdownMenuRadioItem, type DropdownMenuRadioItemProps } from './DropdownMenuRadioItem.svelte'\nexport { default as DropdownMenuSeparator, type DropdownMenuSeparatorProps } from './DropdownMenuSeparator.svelte'\nexport { default as DropdownMenuShortcut, type DropdownMenuShortcutProps } from './DropdownMenuShortcut.svelte'\nexport { default as DropdownMenuSub, type DropdownMenuSubProps } from './DropdownMenuSub.svelte'\nexport { default as DropdownMenuSubContent, type DropdownMenuSubContentProps } from './DropdownMenuSubContent.svelte'\nexport { default as DropdownMenuSubTrigger, type DropdownMenuSubTriggerProps } from './DropdownMenuSubTrigger.svelte'\nexport { default as DropdownMenuTrigger, type DropdownMenuTriggerProps } from './DropdownMenuTrigger.svelte'\nexport { dropdownMenuContentVariants, type DropdownMenuContentVariants } from './dropdown-menu-content.variants'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/dropdown-menu/index.ts"
    }
  ],
  "dependencies": [
    "@lucide/svelte",
    "class-variance-authority"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Floating menu launched from a trigger button — for account switchers, row actions, editor menus, and any short list of commands. Supports labels, icons, separators, keyboard shortcuts, checkbox/radio items, and nested submenus. Hand-rolled on Svelte 5 runes (no headless dependency); ARIA + keyboard navigation handled.",
  "categories": [
    "overlay"
  ]
}