{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "select",
  "title": "Select",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/select/Select.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface SelectProps extends HTMLAttributes<HTMLDivElement> {\n    /** Controlled value. Omit + use `defaultValue` for uncontrolled. */\n    value?: string\n    defaultValue?: string\n    /** Controlled open state. Omit + use `defaultOpen` for uncontrolled. */\n    open?: boolean\n    defaultOpen?: boolean\n    disabled?: boolean\n    /** Renders a hidden input so the value submits with native forms. */\n    name?: string\n    required?: boolean\n    onValueChange?: (value: string) => void\n    onOpenChange?: (open: boolean) => void\n    children?: Snippet\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { setContext, untrack } from 'svelte'\n  import { cn } from '$lib/utils'\n  import { SELECT_CONTEXT_KEY, type SelectContext, type SelectItemData } from './select-context'\n\n  let {\n    class: className,\n    value = $bindable<string | undefined>(),\n    defaultValue,\n    open = $bindable<boolean | undefined>(),\n    defaultOpen = false,\n    disabled = false,\n    name,\n    required = false,\n    onValueChange,\n    onOpenChange,\n    children,\n    ref = $bindable(null),\n    ...restProps\n  }: SelectProps = $props()\n\n  let innerValue = $state<string | undefined>(defaultValue)\n  let innerOpen = $state(defaultOpen)\n  let highlighted = $state<string | undefined>(undefined)\n  let items = $state<SelectItemData[]>([])\n  let triggerEl = $state<HTMLElement | null>(null)\n\n  const currentValue = $derived(value ?? innerValue)\n  const currentOpen = $derived(open ?? innerOpen)\n\n  const triggerId = `select-trigger-${Math.random().toString(36).slice(2, 9)}`\n  const contentId = `select-content-${Math.random().toString(36).slice(2, 9)}`\n\n  function setOpen(next: boolean) {\n    if (disabled) return\n    innerOpen = next\n    open = next\n    onOpenChange?.(next)\n    if (next) {\n      const enabled = items.filter((i) => !i.disabled)\n      const selected = enabled.find((i) => i.value === currentValue)\n      highlighted = (selected ?? enabled[0])?.value\n    } else {\n      highlighted = undefined\n    }\n  }\n\n  function selectValue(next: string) {\n    innerValue = next\n    value = next\n    onValueChange?.(next)\n    setOpen(false)\n    focusTrigger()\n  }\n\n  function moveHighlight(direction: 1 | -1) {\n    const enabled = items.filter((i) => !i.disabled)\n    if (enabled.length === 0) return\n    const idx = enabled.findIndex((i) => i.value === highlighted)\n    const next = idx < 0 ? (direction === 1 ? 0 : enabled.length - 1) : (idx + direction + enabled.length) % enabled.length\n    highlighted = enabled[next]!.value\n  }\n\n  function highlightFirst() {\n    highlighted = items.find((i) => !i.disabled)?.value\n  }\n\n  function highlightLast() {\n    const enabled = items.filter((i) => !i.disabled)\n    highlighted = enabled[enabled.length - 1]?.value\n  }\n\n  // Called from each SelectItem's $effect. Untracked so reading `items` here\n  // doesn't make every item's effect depend on the whole list — otherwise each\n  // registration re-ran all items' effects (effect_update_depth_exceeded).\n  function registerItem(item: SelectItemData) {\n    untrack(() => {\n      const idx = items.findIndex((i) => i.value === item.value)\n      if (idx >= 0) items[idx] = item\n      else items.push(item)\n    })\n  }\n\n  function unregisterItem(itemValue: string) {\n    untrack(() => {\n      items = items.filter((i) => i.value !== itemValue)\n    })\n  }\n\n  function registerTrigger(el: HTMLElement | null) {\n    triggerEl = el\n  }\n\n  function focusTrigger() {\n    triggerEl?.focus()\n  }\n\n  const ctx: SelectContext = {\n    get open() {\n      return currentOpen\n    },\n    get value() {\n      return currentValue\n    },\n    get highlighted() {\n      return highlighted\n    },\n    get disabled() {\n      return disabled\n    },\n    get items() {\n      return items\n    },\n    get triggerId() {\n      return triggerId\n    },\n    get contentId() {\n      return contentId\n    },\n    get triggerEl() {\n      return triggerEl\n    },\n    setOpen,\n    selectValue,\n    setHighlighted: (v) => (highlighted = v),\n    moveHighlight,\n    highlightFirst,\n    highlightLast,\n    registerItem,\n    unregisterItem,\n    registerTrigger,\n    focusTrigger,\n  }\n  setContext(SELECT_CONTEXT_KEY, ctx)\n</script>\n\n<div bind:this={ref} data-uipkge data-slot=\"select\" data-state={currentOpen ? 'open' : 'closed'} class={cn('relative', className)} {...restProps}>\n  {@render children?.()}\n  {#if name}\n    <input type=\"hidden\" {name} value={currentValue ?? ''} {required} />\n  {/if}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/select/Select.svelte"
    },
    {
      "path": "packages/registry-svelte/components/select/SelectContent.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface SelectContentProps extends HTMLAttributes<HTMLDivElement> {\n    /** Preferred side. Flips automatically when space runs out. */\n    side?: 'top' | 'bottom'\n    align?: 'start' | 'center' | 'end'\n    sideOffset?: number\n    children?: Snippet\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { untrack } from 'svelte'\n  import { cn } from '$lib/utils'\n  import { getSelectContext } from './select-context'\n  import SelectScrollDownButton from './SelectScrollDownButton.svelte'\n  import SelectScrollUpButton from './SelectScrollUpButton.svelte'\n\n  let {\n    class: className,\n    side = 'bottom',\n    align = 'start',\n    sideOffset = 4,\n    children,\n    ref = $bindable(null),\n    ...restProps\n  }: SelectContentProps = $props()\n\n  const ctx = getSelectContext('SelectContent')\n\n  let contentEl: HTMLDivElement | null = $state(null)\n  let viewportEl: HTMLDivElement | null = $state(null)\n  let pos = $state({ top: 0, left: 0, minWidth: 0, maxHeight: 320, placed: false })\n  let resolvedSide = $state<'top' | 'bottom'>(side)\n\n  $effect(() => {\n    ref = contentEl\n  })\n\n  let mounted = $state(false)\n  $effect(() => {\n    mounted = true\n  })\n\n  function place() {\n    const trigger = ctx.triggerEl\n    if (!trigger) return\n    const rect = trigger.getBoundingClientRect()\n    const viewportH = window.innerHeight\n    const viewportW = window.innerWidth\n    const maxHeight = Math.min(320, Math.max(160, viewportH - 32))\n    const below = viewportH - rect.bottom - sideOffset\n    const above = rect.top - sideOffset\n    // Honor `side` unless it has < 160px while the other side has more room.\n    let s = side\n    if (s === 'bottom' && below < 160 && above > below) s = 'top'\n    if (s === 'top' && above < 160 && below > above) s = 'bottom'\n    resolvedSide = s\n    const height = Math.min(maxHeight, contentEl?.offsetHeight || maxHeight)\n    const top = s === 'bottom' ? rect.bottom + sideOffset : Math.max(8, rect.top - sideOffset - height)\n    const width = Math.max(rect.width, 128)\n    let left = align === 'start' ? rect.left : align === 'end' ? rect.right - width : rect.left + (rect.width - width) / 2\n    left = Math.min(Math.max(8, left), Math.max(8, viewportW - width - 8))\n    pos = { top, left, minWidth: rect.width, maxHeight, placed: true }\n  }\n\n  $effect(() => {\n    if (!ctx.open) {\n      // Reset without tracking `pos`: reading and writing it here re-ran this\n      // effect forever (effect_update_depth_exceeded on every closed Select).\n      untrack(() => {\n        if (pos.placed) pos = { ...pos, placed: false }\n      })\n      return\n    }\n    // Place after mount so contentEl has measurable height.\n    const raf = requestAnimationFrame(() => {\n      place()\n      contentEl?.focus({ preventScroll: true })\n    })\n    const onDocPointerDown = (e: PointerEvent) => {\n      const t = e.target as Node | null\n      if (t && contentEl?.contains(t)) return\n      if (t && ctx.triggerEl?.contains(t)) return\n      ctx.setOpen(false)\n    }\n    const onScroll = () => place()\n    const onResize = () => place()\n    document.addEventListener('pointerdown', onDocPointerDown, { capture: true })\n    window.addEventListener('scroll', onScroll, { capture: true, passive: true })\n    window.addEventListener('resize', onResize)\n    return () => {\n      cancelAnimationFrame(raf)\n      document.removeEventListener('pointerdown', onDocPointerDown, { capture: true })\n      window.removeEventListener('scroll', onScroll, { capture: true })\n      window.removeEventListener('resize', onResize)\n    }\n  })\n\n  // Keep the highlighted option visible while arrowing through the list.\n  $effect(() => {\n    if (!ctx.open || !viewportEl) return\n    const id = ctx.highlighted\n    if (!id) return\n    const el = viewportEl.querySelector(`[data-value=\"${CSS.escape(id)}\"]`)\n    el?.scrollIntoView({ block: 'nearest' })\n  })\n\n  function onKeyDown(e: KeyboardEvent) {\n    if (e.key === 'ArrowDown') {\n      e.preventDefault()\n      ctx.moveHighlight(1)\n    } else if (e.key === 'ArrowUp') {\n      e.preventDefault()\n      ctx.moveHighlight(-1)\n    } else if (e.key === 'Home') {\n      e.preventDefault()\n      ctx.highlightFirst()\n    } else if (e.key === 'End') {\n      e.preventDefault()\n      ctx.highlightLast()\n    } else if (e.key === 'Enter' || e.key === ' ') {\n      e.preventDefault()\n      if (ctx.highlighted !== undefined) ctx.selectValue(ctx.highlighted)\n    } else if (e.key === 'Escape') {\n      e.preventDefault()\n      ctx.setOpen(false)\n      ctx.focusTrigger()\n    } else if (e.key === 'Tab') {\n      ctx.setOpen(false)\n    }\n  }\n\n  const activeDescendant = $derived(\n    ctx.highlighted ? `${ctx.contentId}-item-${CSS.escape(ctx.highlighted)}` : undefined,\n  )\n\n  // Teleport the listbox to <body> so it escapes overflow clipping and stacks\n  // above page content. (Svelte 5's <svelte:body> takes no children, so the\n  // portal is a plain action — Svelte keeps updating moved nodes.)\n  function portal(node: HTMLElement) {\n    document.body.appendChild(node)\n    return {\n      destroy() {\n        node.remove()\n      },\n    }\n  }\n\n  function offDocument(node: HTMLElement) {\n    document.createDocumentFragment().appendChild(node)\n    return {\n      destroy() {\n        node.remove()\n      },\n    }\n  }\n</script>\n\n{#if ctx.open}\n  <div use:portal\n      bind:this={contentEl}\n      data-uipkge\n      data-slot=\"select-content\"\n      data-state=\"open\"\n      data-side={resolvedSide}\n      role=\"listbox\"\n      tabindex=\"-1\"\n      id={ctx.contentId}\n      aria-labelledby={ctx.triggerId}\n      aria-activedescendant={activeDescendant}\n      aria-orientation=\"vertical\"\n      style=\"position: fixed; top: {pos.top}px; left: {pos.left}px; min-width: {pos.minWidth}px; max-height: {pos.maxHeight}px; {pos.placed\n        ? ''\n        : 'visibility: hidden;'}\"\n      class={cn(\n        'bg-popover text-popover-foreground 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 motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=closed]:zoom-out-95 motion-safe:data-[state=open]:zoom-in-95 motion-safe:data-[side=bottom]:slide-in-from-top-2 motion-safe:data-[side=left]:slide-in-from-right-2 motion-safe:data-[side=right]:slide-in-from-left-2 motion-safe:data-[side=top]:slide-in-from-bottom-2 z-50 min-w-32 overflow-hidden rounded-md border shadow-md motion-safe:data-[state=closed]:duration-[var(--dur-exit)] motion-safe:data-[state=open]:duration-200',\n        'data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1',\n        className,\n      )}\n      onkeydown={onKeyDown}\n      {...restProps}\n    >\n      <SelectScrollUpButton />\n      <div bind:this={viewportEl} data-slot=\"select-viewport\" class=\"max-h-[inherit] overflow-x-hidden overflow-y-auto scroll-my-1 p-1\">\n        {@render children?.()}\n      </div>\n      <SelectScrollDownButton />\n    </div>\n{:else if mounted}\n  <!-- Closed: still mount the items, off-document, so they register their\n    labels and SelectValue can show the selected one (reka-ui does the same).\n    Client-only, like reka-ui: SelectItem uses CSS.escape, absent in SSR. -->\n  <div hidden use:offDocument>\n    {@render children?.()}\n  </div>\n{/if}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/select/SelectContent.svelte"
    },
    {
      "path": "packages/registry-svelte/components/select/SelectGroup.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface SelectGroupProps extends HTMLAttributes<HTMLDivElement> {\n    children?: Snippet\n  }\n</script>\n\n<script lang=\"ts\">\n  let { children, ...restProps }: SelectGroupProps = $props()\n</script>\n\n<div data-uipkge data-slot=\"select-group\" role=\"group\" {...restProps}>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/select/SelectGroup.svelte"
    },
    {
      "path": "packages/registry-svelte/components/select/SelectItem.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface SelectItemProps extends HTMLAttributes<HTMLDivElement> {\n    value: string\n    disabled?: boolean\n    /** Display label. Defaults to the rendered text content. */\n    label?: string\n    children?: Snippet\n    /** Replaces the default check icon shown for the selected item. */\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  import { getSelectContext } from './select-context'\n\n  let {\n    class: className,\n    value,\n    disabled = false,\n    label,\n    children,\n    indicatorIcon,\n    ref = $bindable(null),\n    ...restProps\n  }: SelectItemProps = $props()\n\n  const ctx = getSelectContext('SelectItem')\n  const isSelected = $derived(ctx.value === value)\n  const isHighlighted = $derived(ctx.highlighted === value)\n\n  $effect(() => {\n    // Tracks value/label/disabled; the cleanup unregisters the previous value.\n    const v = value\n    const l = label\n    const d = disabled\n    if (!ref) return\n    ctx.registerItem({ value: v, label: l ?? ref.textContent?.trim() ?? v, disabled: d })\n    return () => ctx.unregisterItem(v)\n  })\n\n  function onClick() {\n    if (disabled) return\n    ctx.selectValue(value)\n  }\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge\n  data-slot=\"select-item\"\n  data-value={value}\n  data-highlighted={isHighlighted ? '' : undefined}\n  data-state={isSelected ? 'checked' : 'unchecked'}\n  role=\"option\"\n  id={`${ctx.contentId}-item-${CSS.escape(value)}`}\n  aria-selected={isSelected}\n  aria-disabled={disabled || undefined}\n  data-disabled={disabled ? '' : undefined}\n  class={cn(\n    'focus:bg-accent focus:text-accent-foreground [&_svg:not([class*=\\'text-\\'])]:text-muted-foreground data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\\'size-\\'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2',\n    className,\n  )}\n  onclick={onClick}\n  onpointermove={() => {\n    if (!disabled) ctx.setHighlighted(value)\n  }}\n  {...restProps}\n>\n  <span class=\"absolute right-2 flex size-3.5 items-center justify-center\">\n    {#if isSelected}\n      {#if indicatorIcon}\n        {@render indicatorIcon()}\n      {:else}\n        <Check class=\"size-4\" aria-hidden=\"true\" />\n      {/if}\n    {/if}\n  </span>\n\n  <span data-uipkge data-slot=\"select-item-text\">\n    {@render children?.()}\n  </span>\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/select/SelectItem.svelte"
    },
    {
      "path": "packages/registry-svelte/components/select/SelectItemText.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface SelectItemTextProps extends HTMLAttributes<HTMLSpanElement> {\n    children?: Snippet\n  }\n</script>\n\n<script lang=\"ts\">\n  let { children, ...restProps }: SelectItemTextProps = $props()\n</script>\n\n<span data-uipkge data-slot=\"select-item-text\" {...restProps}>\n  {@render children?.()}\n</span>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/select/SelectItemText.svelte"
    },
    {
      "path": "packages/registry-svelte/components/select/SelectLabel.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface SelectLabelProps extends HTMLAttributes<HTMLDivElement> {\n    children?: Snippet\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n\n  let { class: className, children, ...restProps }: SelectLabelProps = $props()\n</script>\n\n<div data-uipkge data-slot=\"select-label\" class={cn('text-muted-foreground px-2 py-2 text-xs', className)} {...restProps}>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/select/SelectLabel.svelte"
    },
    {
      "path": "packages/registry-svelte/components/select/SelectScrollDownButton.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface SelectScrollDownButtonProps extends HTMLAttributes<HTMLDivElement> {\n    children?: Snippet\n  }\n</script>\n\n<script lang=\"ts\">\n  import { ChevronDown } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n\n  let { class: className, children, ...restProps }: SelectScrollDownButtonProps = $props()\n\n  let btnEl: HTMLDivElement | null = $state(null)\n  let timer: ReturnType<typeof setInterval> | null = null\n\n  function viewport(): HTMLElement | null {\n    return btnEl?.closest('[data-slot=\"select-content\"]')?.querySelector('[data-slot=\"select-viewport\"]') ?? null\n  }\n\n  function step() {\n    const vp = viewport()\n    if (vp) vp.scrollTop += 24\n  }\n\n  function start(e: PointerEvent) {\n    e.preventDefault()\n    step()\n    timer = setInterval(step, 50)\n  }\n\n  function stop() {\n    if (timer) {\n      clearInterval(timer)\n      timer = null\n    }\n  }\n</script>\n\n<div\n  bind:this={btnEl}\n  data-uipkge\n  data-slot=\"select-scroll-down-button\"\n  aria-hidden=\"true\"\n  class={cn('flex cursor-default items-center justify-center py-1', className)}\n  onpointerdown={start}\n  onpointerup={stop}\n  onpointerleave={stop}\n  {...restProps}\n>\n  {#if children}\n    {@render children()}\n  {:else}\n    <ChevronDown class=\"size-4\" aria-hidden=\"true\" />\n  {/if}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/select/SelectScrollDownButton.svelte"
    },
    {
      "path": "packages/registry-svelte/components/select/SelectScrollUpButton.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface SelectScrollUpButtonProps extends HTMLAttributes<HTMLDivElement> {\n    children?: Snippet\n  }\n</script>\n\n<script lang=\"ts\">\n  import { ChevronUp } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n\n  let { class: className, children, ...restProps }: SelectScrollUpButtonProps = $props()\n\n  let btnEl: HTMLDivElement | null = $state(null)\n  let timer: ReturnType<typeof setInterval> | null = null\n\n  function viewport(): HTMLElement | null {\n    return btnEl?.closest('[data-slot=\"select-content\"]')?.querySelector('[data-slot=\"select-viewport\"]') ?? null\n  }\n\n  function step() {\n    const vp = viewport()\n    if (vp) vp.scrollTop -= 24\n  }\n\n  function start(e: PointerEvent) {\n    e.preventDefault()\n    step()\n    timer = setInterval(step, 50)\n  }\n\n  function stop() {\n    if (timer) {\n      clearInterval(timer)\n      timer = null\n    }\n  }\n</script>\n\n<div\n  bind:this={btnEl}\n  data-uipkge\n  data-slot=\"select-scroll-up-button\"\n  aria-hidden=\"true\"\n  class={cn('flex cursor-default items-center justify-center py-1', className)}\n  onpointerdown={start}\n  onpointerup={stop}\n  onpointerleave={stop}\n  {...restProps}\n>\n  {#if children}\n    {@render children()}\n  {:else}\n    <ChevronUp class=\"size-4\" aria-hidden=\"true\" />\n  {/if}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/select/SelectScrollUpButton.svelte"
    },
    {
      "path": "packages/registry-svelte/components/select/SelectSeparator.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface SelectSeparatorProps extends HTMLAttributes<HTMLDivElement> {}\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n\n  let { class: className, ...restProps }: SelectSeparatorProps = $props()\n</script>\n\n<div\n  data-uipkge\n  data-slot=\"select-separator\"\n  role=\"separator\"\n  aria-orientation=\"horizontal\"\n  class={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}\n  {...restProps}\n></div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/select/SelectSeparator.svelte"
    },
    {
      "path": "packages/registry-svelte/components/select/SelectTrigger.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLButtonAttributes } from 'svelte/elements'\n\n  export interface SelectTriggerProps extends HTMLButtonAttributes {\n    size?: 'sm' | 'default' | 'lg'\n    state?: 'default' | 'error' | 'success'\n    loading?: boolean\n    children?: Snippet\n    ref?: HTMLButtonElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { ChevronDown, Loader } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n  import { getSelectContext } from './select-context'\n\n  let {\n    class: className,\n    size = 'default',\n    state = 'default',\n    loading = false,\n    children,\n    ref = $bindable(null),\n    disabled,\n    ...restProps\n  }: SelectTriggerProps = $props()\n\n  const ctx = getSelectContext('SelectTrigger')\n  const isDisabled = $derived(disabled || loading || ctx.disabled)\n\n  const sizeClasses = {\n    sm: 'h-8 text-sm px-2.5 py-1.5',\n    default: 'h-9 text-sm px-3 py-2',\n    lg: 'h-11 text-base px-4 py-2.5',\n  }\n\n  const stateClasses = {\n    default: 'border-input dark:hover:bg-input/50',\n    error:\n      'border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',\n    success: 'border-success focus-visible:border-success',\n  }\n\n  $effect(() => {\n    ctx.registerTrigger(ref)\n    return () => ctx.registerTrigger(null)\n  })\n\n  function onClick() {\n    if (isDisabled) return\n    ctx.setOpen(!ctx.open)\n  }\n\n  function onKeyDown(e: KeyboardEvent) {\n    if (isDisabled) return\n    if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') {\n      e.preventDefault()\n      ctx.setOpen(true)\n    } else if (e.key === 'ArrowUp') {\n      e.preventDefault()\n      ctx.setOpen(true)\n      ctx.highlightLast()\n    } else if (e.key === 'Escape' && ctx.open) {\n      e.preventDefault()\n      ctx.setOpen(false)\n    }\n  }\n</script>\n\n<button\n  bind:this={ref}\n  type=\"button\"\n  data-uipkge\n  data-slot=\"select-trigger\"\n  data-size={size}\n  data-state-value={state}\n  data-state={ctx.open ? 'open' : 'closed'}\n  aria-haspopup=\"listbox\"\n  aria-expanded={ctx.open}\n  aria-controls={ctx.contentId}\n  aria-busy={loading}\n  id={ctx.triggerId}\n  disabled={isDisabled}\n  class={cn(\n    'border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*=\\'text-\\'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex w-full items-center justify-between gap-2 rounded-md border bg-transparent text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',\n    sizeClasses[size],\n    stateClasses[state],\n    className,\n  )}\n  onclick={onClick}\n  onkeydown={onKeyDown}\n  {...restProps}\n>\n  {@render children?.()}\n  {#if !loading}\n    <ChevronDown class=\"size-4 opacity-50\" aria-hidden=\"true\" />\n  {:else}\n    <Loader class=\"size-4 animate-spin opacity-50\" aria-hidden=\"true\" />\n  {/if}\n</button>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/select/SelectTrigger.svelte"
    },
    {
      "path": "packages/registry-svelte/components/select/SelectValue.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface SelectValueProps extends HTMLAttributes<HTMLSpanElement> {\n    placeholder?: string\n    children?: Snippet\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { getSelectContext } from './select-context'\n\n  let { class: className, placeholder, children, ...restProps }: SelectValueProps = $props()\n\n  const ctx = getSelectContext('SelectValue')\n  const selected = $derived(ctx.items.find((i) => i.value === ctx.value))\n</script>\n\n<span\n  data-uipkge\n  data-slot=\"select-value\"\n  data-placeholder={selected ? undefined : ''}\n  class={cn('pointer-events-none truncate', className)}\n  {...restProps}\n>\n  {#if children}\n    {@render children()}\n  {:else if selected}\n    {selected.label}\n  {:else}\n    {placeholder}\n  {/if}\n</span>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/select/SelectValue.svelte"
    },
    {
      "path": "packages/registry-svelte/components/select/NativeSelect.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes, HTMLSelectAttributes } from 'svelte/elements'\n\n  export interface NativeSelectOption {\n    label: string\n    value: string | number\n    disabled?: boolean\n  }\n\n  export interface NativeSelectProps extends HTMLAttributes<HTMLDivElement> {\n    value?: string | number\n    defaultValue?: string | number\n    options?: (NativeSelectOption | string)[]\n    /** Primary sizing API (React parity). Wins over `size` when both are set. */\n    sizeVariant?: 'sm' | 'md' | 'lg'\n    /** @deprecated Use `sizeVariant`. Kept as an alias — both stay functional. */\n    size?: 'sm' | 'md' | 'lg'\n    disabled?: boolean\n    selectClass?: string\n    /** Native select passthrough attributes (name, required, aria-*). */\n    selectAttributes?: HTMLSelectAttributes\n    onValueChange?: (value: string) => void\n    children?: Snippet\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { ChevronDown } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n\n  let {\n    class: className,\n    value = $bindable<string | number | undefined>(),\n    defaultValue,\n    options,\n    sizeVariant,\n    size,\n    disabled = false,\n    selectClass,\n    selectAttributes,\n    onValueChange,\n    children,\n    ref = $bindable(null),\n    ...restProps\n  }: NativeSelectProps = $props()\n\n  const effectiveSize = $derived(sizeVariant ?? size ?? 'md')\n\n  let inner = $state<string | number | undefined>(defaultValue)\n  const current = $derived(value ?? inner)\n\n  const sizeClasses: Record<string, string> = {\n    sm: 'h-8 text-xs pl-2.5 pr-8',\n    md: 'h-9 text-sm pl-3 pr-9',\n    lg: 'h-11 text-base pl-4 pr-10',\n  }\n\n  const iconSizes: Record<string, string> = {\n    sm: 'size-3.5 right-2.5',\n    md: 'size-4 right-3',\n    lg: 'size-5 right-3.5',\n  }\n\n  const normalizedOptions = $derived.by(() => {\n    if (!options) return []\n    return options.map((opt) => {\n      if (typeof opt === 'string') {\n        return { label: opt, value: opt, disabled: false }\n      }\n      return opt\n    })\n  })\n\n  function onChange(e: Event) {\n    const next = (e.target as HTMLSelectElement).value\n    inner = next\n    value = next\n    onValueChange?.(next)\n  }\n</script>\n\n<div bind:this={ref} data-uipkge data-slot=\"native-select-wrapper\" class={cn('relative inline-flex w-full items-center', className)} {...restProps}>\n  <select\n    data-slot=\"native-select\"\n    {disabled}\n    value={current}\n    class={cn(\n      'border-input bg-background w-full appearance-none rounded-md border shadow-xs transition-[color,box-shadow]',\n      'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] focus-visible:outline-none',\n      'disabled:bg-muted/30 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',\n      sizeClasses[effectiveSize],\n      selectClass,\n    )}\n    onchange={onChange}\n    {...selectAttributes}\n  >\n    {#if normalizedOptions.length > 0}\n      {#each normalizedOptions as opt (String(opt.value))}\n        <option value={opt.value} disabled={opt.disabled}>\n          {opt.label}\n        </option>\n      {/each}\n    {:else}\n      {@render children?.()}\n    {/if}\n  </select>\n  <ChevronDown\n    data-slot=\"native-select-icon\"\n    aria-hidden=\"true\"\n    class={cn('text-muted-foreground pointer-events-none absolute transition-opacity', disabled && 'opacity-50', iconSizes[effectiveSize])}\n  />\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/select/NativeSelect.svelte"
    },
    {
      "path": "packages/registry-svelte/components/select/index.ts",
      "content": "export { default as Select, type SelectProps } from './Select.svelte'\nexport { default as SelectContent, type SelectContentProps } from './SelectContent.svelte'\nexport { default as SelectGroup, type SelectGroupProps } from './SelectGroup.svelte'\nexport { default as SelectItem, type SelectItemProps } from './SelectItem.svelte'\nexport { default as SelectItemText, type SelectItemTextProps } from './SelectItemText.svelte'\nexport { default as SelectLabel, type SelectLabelProps } from './SelectLabel.svelte'\nexport { default as SelectScrollDownButton, type SelectScrollDownButtonProps } from './SelectScrollDownButton.svelte'\nexport { default as SelectScrollUpButton, type SelectScrollUpButtonProps } from './SelectScrollUpButton.svelte'\nexport { default as SelectSeparator, type SelectSeparatorProps } from './SelectSeparator.svelte'\nexport { default as SelectTrigger, type SelectTriggerProps } from './SelectTrigger.svelte'\nexport { default as SelectValue, type SelectValueProps } from './SelectValue.svelte'\nexport { default as NativeSelect, type NativeSelectProps, type NativeSelectOption } from './NativeSelect.svelte'\nexport { type SelectOption, readKey } from './option-types'\nexport { SELECT_CONTEXT_KEY, type SelectContext, type SelectItemData } from './select-context'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/select/index.ts"
    },
    {
      "path": "packages/registry-svelte/components/select/option-types.ts",
      "content": "/**\n * Shared option shape for `AdvanceSelect` and any future options-driven select.\n * Components accept this canonical shape OR any object shape if `value-key` /\n * `label-key` are provided.\n */\nexport interface SelectOption<V = string> {\n  label: string\n  value: V\n  disabled?: boolean\n  /** Items sharing this key render under one heading. */\n  group?: string\n}\n\n/**\n * Resolve `option[key]` with a default fallback. Used by AdvanceSelect so every\n * accessor obeys `value-key` / `label-key`.\n */\nexport function readKey<T>(option: T, key: string, fallback?: unknown): unknown {\n  if (option == null || typeof option !== 'object') return fallback\n  const v = (option as Record<string, unknown>)[key]\n  return v === undefined ? fallback : v\n}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/select/option-types.ts"
    },
    {
      "path": "packages/registry-svelte/components/select/select-context.ts",
      "content": "import { getContext } from 'svelte'\n\nexport interface SelectItemData {\n  value: string\n  label: string\n  disabled: boolean\n}\n\n/**\n * Shared select state, provided by `Select` and consumed by the parts.\n * Readonly fields are implemented as getters over runes state in `Select`,\n * so `$derived(ctx.open)` in a part stays reactive.\n */\nexport interface SelectContext {\n  readonly open: boolean\n  readonly value: string | undefined\n  readonly highlighted: string | undefined\n  readonly disabled: boolean\n  readonly items: SelectItemData[]\n  readonly triggerId: string\n  readonly contentId: string\n  readonly triggerEl: HTMLElement | null\n  setOpen: (open: boolean) => void\n  selectValue: (value: string) => void\n  setHighlighted: (value: string | undefined) => void\n  moveHighlight: (direction: 1 | -1) => void\n  highlightFirst: () => void\n  highlightLast: () => void\n  registerItem: (item: SelectItemData) => void\n  unregisterItem: (value: string) => void\n  registerTrigger: (el: HTMLElement | null) => void\n  focusTrigger: () => void\n}\n\nexport const SELECT_CONTEXT_KEY = Symbol('uipkge-select-context')\n\nexport function getSelectContext(component: string): SelectContext {\n  const ctx = getContext<SelectContext | undefined>(SELECT_CONTEXT_KEY)\n  if (!ctx) throw new Error(`${component} must be used inside <Select>.`)\n  return ctx\n}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/select/select-context.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Dropdown select primitive — single-select with optional groups, descriptions per item, an accessible hand-rolled listbox (no headless dependency), and zero-JS styled NativeSelect for lightweight/mobile use.",
  "categories": [
    "control"
  ]
}