{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "advance-select",
  "title": "Advance Select",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/advance-select/AdvanceSelect.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLButtonAttributes } from 'svelte/elements'\n  import type { AdvanceSelectFieldNames } from './types'\n\n  export interface AdvanceSelectProps<T = Record<string, unknown> | string | number>\n    extends Omit<HTMLButtonAttributes, 'value' | 'prefix'> {\n    value?: unknown | unknown[]\n    options: T[]\n\n    // Mode\n    mode?: 'single' | 'multiple' | 'tags'\n\n    // Field mapping\n    fieldNames?: AdvanceSelectFieldNames\n\n    // Appearance\n    size?: 'sm' | 'default' | 'lg'\n    variant?: 'outlined' | 'filled' | 'borderless'\n    status?: 'default' | 'error' | 'warning'\n    placeholder?: string\n\n    // Search\n    showSearch?: boolean\n    searchValue?: string\n    autoClearSearchValue?: boolean\n    filterOption?: boolean | ((input: string, option: T) => boolean)\n    optionFilterProp?: string | string[]\n    filterSort?: (optionA: T, optionB: T, info: { searchValue: string }) => number\n\n    // Multiple/Tags\n    maxCount?: number\n    maxTagCount?: number\n    maxTagTextLength?: number\n    maxTagPlaceholder?: string | ((omittedValues: T[]) => string)\n    tokenSeparators?: string[]\n    hideSelected?: boolean\n    allowCreate?: boolean\n\n    // State\n    disabled?: boolean\n    loading?: boolean\n    allowClear?: boolean\n    open?: boolean\n    defaultOpen?: boolean\n    defaultActiveFirstOption?: boolean\n\n    // Customization\n    notFoundContent?: string\n    loadingText?: string\n    listHeight?: number\n    virtual?: boolean\n\n    // Snippets (Svelte counterparts of the Vue slots)\n    prefix?: Snippet\n    suffix?: Snippet\n    suffixIcon?: Snippet\n    clearIcon?: Snippet\n    tag?: Snippet<[{ value: unknown; label: string; closable: boolean; onClose: (e: Event) => void }]>\n    label?: Snippet<[{ value: unknown | unknown[]; label: string }]>\n    option?: Snippet<[{ option: T; index: number }]>\n    /** Primary empty-content API (React `emptyContent` parity). Wins over `empty` when both are set. */\n    emptyContent?: Snippet\n    /** @deprecated Use `emptyContent`. Kept as an alias — both stay functional. */\n    empty?: Snippet\n\n    ref?: HTMLButtonElement | null\n    /** Primary value-change API (React `onValueChange` parity). Fires alongside `onChange`. */\n    onValueChange?: (value: unknown | unknown[], option: T | T[] | undefined) => void\n    /** @deprecated Use `onValueChange`. Kept as an alias — both stay functional and fire together. */\n    onChange?: (value: unknown | unknown[], option: T | T[] | undefined) => void\n    onSelect?: (value: unknown, option: T) => void\n    onDeselect?: (value: unknown, option: T) => void\n    /** Primary search API (React `onSearchChange` parity). Fires alongside `onSearch`. */\n    onSearchChange?: (value: string) => void\n    /** @deprecated Use `onSearchChange`. Kept as an alias — both stay functional and fire together. */\n    onSearch?: (value: string) => void\n    onClear?: () => void\n    onOpenChange?: (open: boolean) => void\n    onFocus?: (event: FocusEvent) => void\n    onBlur?: (event: FocusEvent) => void\n    onPopupScroll?: (event: Event) => void\n    onInputKeyDown?: (event: KeyboardEvent) => void\n  }\n</script>\n\n<script lang=\"ts\" generics=\"T extends Record<string, unknown> | string | number\">\n  import type { FocusEventHandler } from 'svelte/elements'\n  import { tick } from 'svelte'\n  import { Check, ChevronDown, LoaderCircle, Search, X } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n  import { readKey } from './types'\n\n  // Self-contained combobox: the Vue twin composes the popover + command +\n  // badge + select registry items, which have no Svelte ports yet, so the\n  // dropdown panel, search field, and tag pills are hand-rolled here with\n  // class strings mirrored from those items. Revisit once they land.\n\n  let {\n    value = $bindable(),\n    options,\n    mode = 'single',\n    fieldNames = {},\n    size = 'default',\n    variant = 'outlined',\n    status = 'default',\n    placeholder = 'Select...',\n    showSearch = false,\n    searchValue = $bindable(),\n    autoClearSearchValue = true,\n    filterOption = true,\n    optionFilterProp = 'label',\n    filterSort,\n    maxCount,\n    maxTagCount = undefined,\n    maxTagTextLength = undefined,\n    maxTagPlaceholder,\n    tokenSeparators = [],\n    hideSelected = false,\n    allowCreate = false,\n    disabled = false,\n    loading = false,\n    allowClear = true,\n    open = $bindable(),\n    defaultOpen = false,\n    defaultActiveFirstOption = true,\n    notFoundContent = 'No results.',\n    loadingText = 'Loading...',\n    listHeight = 300,\n    virtual = true,\n    class: className,\n    prefix,\n    suffix,\n    suffixIcon,\n    clearIcon,\n    tag,\n    label,\n    option,\n    emptyContent,\n    empty,\n    ref = $bindable(null),\n    onValueChange,\n    onChange,\n    onSelect,\n    onDeselect,\n    onSearchChange,\n    onSearch,\n    onClear,\n    onOpenChange,\n    onFocus,\n    onBlur,\n    onPopupScroll,\n    onInputKeyDown,\n    onfocus,\n    onblur,\n    ...restProps\n  }: AdvanceSelectProps<T> = $props()\n\n  function emitChange(value: unknown | unknown[], option: T | T[] | undefined) {\n    onValueChange?.(value, option)\n    onChange?.(value, option)\n  }\n\n  function emitSearch(value: string) {\n    onSearchChange?.(value)\n    onSearch?.(value)\n  }\n\n  // Snapshot once: later `defaultOpen` changes must not reopen a user-closed panel.\n  const getInitialOpen = () => open ?? defaultOpen\n  let internalOpen = $state(getInitialOpen())\n\n  // Controlled mode: a bound `open` always wins over internal state.\n  $effect.pre(() => {\n    if (open === undefined || open === internalOpen) return\n    setOpen(open)\n  })\n\n  let internalQuery = $state('')\n  const query = $derived(searchValue ?? internalQuery)\n\n  function setQuery(v: string) {\n    internalQuery = v\n    searchValue = v\n    emitSearch(v)\n  }\n\n  let wrapperEl: HTMLDivElement | null = $state(null)\n  let searchInput: HTMLInputElement | null = $state(null)\n\n  const valueKey = $derived(fieldNames.value ?? 'value')\n  const labelKey = $derived(fieldNames.label ?? 'label')\n  const groupKey = $derived(fieldNames.group ?? 'group')\n  const disabledKey = $derived(fieldNames.disabled ?? 'disabled')\n\n  function getValue(o: T): unknown {\n    return readKey(o, valueKey, o)\n  }\n  function getLabel(o: T): string {\n    return String(readKey(o, labelKey, ''))\n  }\n  function getGroup(o: T): string | undefined {\n    const g = readKey(o, groupKey)\n    return g == null ? undefined : String(g)\n  }\n  function isDisabled(o: T): boolean {\n    return Boolean(readKey(o, disabledKey, false))\n  }\n\n  const isMultiple = $derived(mode === 'multiple' || mode === 'tags')\n\n  const selectedValues = $derived.by(() => {\n    if (value == null) return []\n    if (isMultiple) {\n      return Array.isArray(value) ? value : []\n    }\n    return [value]\n  })\n\n  const selectedSet = $derived(new Set(selectedValues))\n\n  const selectedOptions = $derived(\n    selectedValues.map((v) => {\n      const found = options.find((o) => getValue(o) === v)\n      if (found) return found\n      // For created tags not in options, create a minimal option object\n      return { [labelKey]: String(v), [valueKey]: v } as T\n    }),\n  )\n\n  function getOptionByValue(v: unknown): T | undefined {\n    return options.find((o) => getValue(o) === v)\n  }\n\n  function matchesFilter(o: T, q: string): boolean {\n    if (typeof filterOption === 'function') {\n      return filterOption(q, o)\n    }\n    if (filterOption === false) return true\n    const labelText = getLabel(o).toLowerCase()\n    const search = q.toLowerCase()\n    const propsToSearch = Array.isArray(optionFilterProp) ? optionFilterProp : [optionFilterProp]\n    for (const prop of propsToSearch) {\n      if (prop === 'label' && labelText.includes(search)) return true\n      const val = String(readKey(o, prop, '')).toLowerCase()\n      if (val.includes(search)) return true\n    }\n    return false\n  }\n\n  const filteredOptions = $derived.by(() => {\n    let result = options\n    const q = query.trim()\n\n    if (q) {\n      result = result.filter((o) => matchesFilter(o, q))\n    }\n\n    if (hideSelected && isMultiple) {\n      result = result.filter((o) => !selectedSet.has(getValue(o)))\n    }\n\n    if (q && filterSort) {\n      const sort = filterSort\n      result = [...result].sort((a, b) => sort(a, b, { searchValue: q }))\n    }\n\n    return result\n  })\n\n  const grouped = $derived.by(() => {\n    const groups = new Map<string, T[]>()\n    for (const opt of filteredOptions) {\n      const key = getGroup(opt) ?? ''\n      if (!groups.has(key)) groups.set(key, [])\n      groups.get(key)!.push(opt)\n    }\n    return Array.from(groups, ([heading, items]) => ({ heading, items }))\n  })\n\n  const atMax = $derived.by(() => {\n    if (typeof maxCount !== 'number') return false\n    const count = Array.isArray(value) ? value.length : value ? 1 : 0\n    return count >= maxCount\n  })\n\n  const visibleTags = $derived.by(() => {\n    if (!isMultiple) return []\n    if (typeof maxTagCount === 'number') {\n      return selectedOptions.slice(0, maxTagCount)\n    }\n    return selectedOptions\n  })\n\n  const hiddenTagCount = $derived.by(() => {\n    if (!isMultiple) return 0\n    if (typeof maxTagCount === 'number') {\n      return Math.max(0, selectedOptions.length - maxTagCount)\n    }\n    return 0\n  })\n\n  function displayLabel(o: T): string {\n    let text = getLabel(o)\n    if (maxTagTextLength && text.length > maxTagTextLength) {\n      text = text.slice(0, maxTagTextLength) + '...'\n    }\n    return text\n  }\n\n  const showCreate = $derived(\n    query.trim() !== '' &&\n      (allowCreate || mode === 'tags') &&\n      !options.some((o) => getLabel(o) === query.trim()),\n  )\n\n  interface SelectableEntry {\n    kind: 'option' | 'create'\n    option?: T\n    disabled: boolean\n  }\n\n  const selectables = $derived.by((): SelectableEntry[] => {\n    const entries: SelectableEntry[] = filteredOptions.map((opt) => ({\n      kind: 'option',\n      option: opt,\n      disabled: isDisabled(opt) || (atMax && !selectedSet.has(getValue(opt))),\n    }))\n    if (showCreate) entries.push({ kind: 'create', disabled: false })\n    return entries\n  })\n\n  let activeIndex = $state(-1)\n\n  function resetActive() {\n    activeIndex = defaultActiveFirstOption && selectables.length > 0 ? 0 : -1\n    // Never land on a disabled entry.\n    if (activeIndex >= 0 && selectables[activeIndex]?.disabled) moveActive(1)\n  }\n\n  // Reset (and clamp) the highlight whenever the list or query changes.\n  $effect(() => {\n    query\n    internalOpen\n    selectables.length\n    if (!internalOpen) return\n    if (activeIndex > selectables.length - 1) activeIndex = selectables.length - 1\n    if (activeIndex >= 0 && selectables[activeIndex]?.disabled) moveActive(1)\n  })\n\n  function moveActive(delta: 1 | -1) {\n    if (selectables.length === 0) return\n    let next = activeIndex\n    for (let i = 0; i < selectables.length; i++) {\n      next = (next + delta + selectables.length) % selectables.length\n      if (!selectables[next]?.disabled) {\n        activeIndex = next\n        return\n      }\n    }\n  }\n\n  function flatIndexOf(opt: T): number {\n    return selectables.findIndex((e) => e.kind === 'option' && e.option === opt)\n  }\n\n  function setOpen(v: boolean) {\n    if (v === internalOpen) return\n    internalOpen = v\n    open = v\n    if (v) {\n      resetActive()\n      tick().then(() => searchInput?.focus())\n    } else if (autoClearSearchValue && query) {\n      setQuery('')\n    }\n    onOpenChange?.(v)\n  }\n\n  function selectOption(opt: T) {\n    if (isDisabled(opt)) return\n    const v = getValue(opt)\n\n    if (!isMultiple) {\n      value = v\n      emitChange(v, opt)\n      onSelect?.(v, opt)\n      setOpen(false)\n      if (autoClearSearchValue) setQuery('')\n      return\n    }\n\n    const current = Array.isArray(value) ? [...value] : []\n    if (selectedSet.has(v)) {\n      const next = current.filter((x) => x !== v)\n      value = next\n      emitChange(next, opt)\n      onDeselect?.(v, opt)\n    } else {\n      if (atMax) return\n      const next = [...current, v]\n      value = next\n      emitChange(next, opt)\n      onSelect?.(v, opt)\n    }\n\n    if (autoClearSearchValue) setQuery('')\n  }\n\n  function selectActive() {\n    const entry = selectables[activeIndex]\n    if (!entry || entry.disabled) return\n    if (entry.kind === 'create') createTag()\n    else if (entry.option) selectOption(entry.option)\n  }\n\n  function removeTag(tagValue: unknown, event: Event) {\n    event.stopPropagation()\n    if (disabled) return\n    const current = Array.isArray(value) ? [...value] : []\n    const next = current.filter((x) => x !== tagValue)\n    const opt = getOptionByValue(tagValue)\n    value = next\n    emitChange(next, opt)\n    if (opt) onDeselect?.(tagValue, opt)\n  }\n\n  function clearAll(event?: Event) {\n    event?.stopPropagation()\n    if (disabled) return\n    onClear?.()\n    if (isMultiple) {\n      value = []\n      emitChange([], [])\n    } else {\n      value = null\n      emitChange(null, undefined)\n    }\n    setQuery('')\n  }\n\n  function createTag() {\n    if (!allowCreate && mode !== 'tags') return\n    const q = query.trim()\n    if (!q) return\n    // Check if already exists\n    const exists = options.some((o) => getLabel(o) === q || String(getValue(o)) === q)\n    if (exists) return\n\n    if (!isMultiple) {\n      value = q\n      const newOption = { [labelKey]: q, [valueKey]: q } as T\n      emitChange(q, newOption)\n      onSelect?.(q, newOption)\n      setOpen(false)\n      setQuery('')\n      return\n    }\n\n    if (atMax) return\n    const current = Array.isArray(value) ? [...value] : []\n    const next = [...current, q]\n    const newOption = { [labelKey]: q, [valueKey]: q } as T\n    value = next\n    emitChange(next, newOption)\n    onSelect?.(q, newOption)\n    setQuery('')\n  }\n\n  function handleInputKeydown(event: KeyboardEvent) {\n    onInputKeyDown?.(event)\n    if (event.defaultPrevented) return\n    if (event.key === 'ArrowDown') {\n      event.preventDefault()\n      moveActive(1)\n    } else if (event.key === 'ArrowUp') {\n      event.preventDefault()\n      moveActive(-1)\n    } else if (event.key === 'Enter') {\n      if (mode === 'tags' && query.trim()) {\n        event.preventDefault()\n        createTag()\n      } else if (activeIndex >= 0) {\n        event.preventDefault()\n        selectActive()\n      }\n    } else if (event.key === 'Escape') {\n      event.preventDefault()\n      setOpen(false)\n    } else if (tokenSeparators.length && mode === 'tags' && tokenSeparators.includes(event.key)) {\n      event.preventDefault()\n      createTag()\n    }\n  }\n\n  function handleTriggerKeydown(event: KeyboardEvent) {\n    if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n      event.preventDefault()\n      if (!internalOpen) setOpen(true)\n      else moveActive(event.key === 'ArrowDown' ? 1 : -1)\n    } else if (event.key === 'Escape' && internalOpen) {\n      event.preventDefault()\n      setOpen(false)\n    }\n  }\n\n  function handleWindowClick(event: MouseEvent) {\n    if (internalOpen && wrapperEl && !wrapperEl.contains(event.target as Node)) {\n      setOpen(false)\n    }\n  }\n\n  const handleFocus: FocusEventHandler<HTMLButtonElement> = (event) => {\n    onfocus?.(event)\n    onFocus?.(event)\n  }\n\n  const handleBlur: FocusEventHandler<HTMLButtonElement> = (event) => {\n    onblur?.(event)\n    onBlur?.(event)\n  }\n\n  function handlePopupScroll(event: Event) {\n    onPopupScroll?.(event)\n  }\n\n  const sizeClasses = {\n    sm: 'h-8 text-xs px-2.5 py-1',\n    default: 'h-9 text-sm px-3 py-1.5',\n    lg: 'h-11 text-base px-4 py-2',\n  }\n\n  const variantClasses = {\n    outlined:\n      'border-input bg-transparent shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',\n    filled:\n      'border-transparent bg-muted/50 shadow-none focus-visible:bg-muted focus-visible:ring-ring/50 focus-visible:ring-[3px]',\n    borderless:\n      'border-transparent bg-transparent shadow-none focus-visible:bg-muted/30 focus-visible:ring-ring/50 focus-visible:ring-[3px]',\n  }\n\n  const statusClasses = {\n    default: '',\n    error:\n      'border-destructive focus-visible:border-destructive focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 aria-invalid:border-destructive',\n    warning: 'border-warning focus-visible:border-warning focus-visible:ring-warning/20',\n  }\n\n  const triggerBaseClasses = $derived(\n    cn(\n      'flex w-full items-center justify-between gap-2 rounded-md border text-sm transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50',\n      sizeClasses[size],\n      variantClasses[variant],\n      statusClasses[status],\n      className,\n    ),\n  )\n\n  const isEmpty = $derived.by(() => {\n    if (isMultiple) {\n      return !Array.isArray(value) || value.length === 0\n    }\n    return value == null || value === ''\n  })\n\n  const showClear = $derived(allowClear && !isEmpty && !disabled && !loading)\n  const showSearchInput = $derived(showSearch || mode === 'tags')\n\n  const optionItemClasses =\n    \"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground [&_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 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n\n  const listboxId = $props.id()\n</script>\n\n<svelte:window onclick={handleWindowClick} />\n\n<div bind:this={wrapperEl} class=\"relative\">\n  <button\n    bind:this={ref}\n    type=\"button\"\n    role=\"combobox\"\n    aria-expanded={internalOpen}\n    aria-controls={listboxId}\n    aria-invalid={status === 'error' ? true : undefined}\n    disabled={disabled || loading}\n    data-uipkge=\"\"\n    data-slot=\"advance-select\"\n    class={triggerBaseClasses}\n    onclick={() => {\n      if (!disabled && !loading) setOpen(!internalOpen)\n    }}\n    onkeydown={handleTriggerKeydown}\n    onfocus={handleFocus}\n    onblur={handleBlur}\n    {...restProps}\n  >\n    <!-- Prefix snippet -->\n    {#if prefix}\n      <span class=\"shrink-0\">\n        {@render prefix()}\n      </span>\n    {/if}\n\n    <!-- Multiple mode tags -->\n    {#if isMultiple}\n      <div class=\"flex flex-1 flex-nowrap items-center gap-1 overflow-x-auto [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden\">\n        {#if selectedOptions.length}\n          {#each visibleTags as opt (String(getValue(opt)))}\n            {#if tag}\n              {@render tag({\n                value: getValue(opt),\n                label: displayLabel(opt),\n                closable: !disabled,\n                onClose: (e: Event) => removeTag(getValue(opt), e),\n              })}\n            {:else}\n              <span\n                class={cn(\n                  'inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium whitespace-nowrap [&>svg]:size-3 gap-1 transition-colors duration-200',\n                  'border-transparent bg-secondary text-secondary-foreground',\n                  'bg-muted text-foreground h-6 gap-1 pr-1 pl-2 font-normal',\n                )}\n              >\n                <span class=\"truncate\">{displayLabel(opt)}</span>\n                {#if !disabled}\n                  <span\n                    role=\"button\"\n                    tabindex=\"0\"\n                    class=\"hover:bg-muted-foreground/20 inline-flex cursor-pointer items-center justify-center rounded-full p-0.5 transition-colors\"\n                    aria-label={`Remove ${getLabel(opt)}`}\n                    onclick={(e) => removeTag(getValue(opt), e)}\n                    onkeydown={(e) => {\n                      if (e.key === 'Enter' || e.key === ' ') {\n                        e.preventDefault()\n                        removeTag(getValue(opt), e)\n                      }\n                    }}\n                  >\n                    <X class=\"size-3\" />\n                  </span>\n                {/if}\n              </span>\n            {/if}\n          {/each}\n          {#if hiddenTagCount > 0}\n            <span\n              class={cn(\n                'inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-colors duration-200',\n                'border-transparent bg-secondary text-secondary-foreground',\n                'bg-muted text-foreground h-6 font-normal',\n              )}\n            >\n              {#if typeof maxTagPlaceholder === 'function'}\n                {maxTagPlaceholder(selectedOptions.slice(maxTagCount ?? 0))}\n              {:else if maxTagPlaceholder}\n                {maxTagPlaceholder}\n              {:else}\n                +{hiddenTagCount}\n              {/if}\n            </span>\n          {/if}\n        {:else}\n          <span class=\"text-muted-foreground truncate\">{placeholder}</span>\n        {/if}\n      </div>\n    {:else}\n      {#if label}\n        {@render label({ value, label: selectedOptions[0] ? getLabel(selectedOptions[0]) : '' })}\n      {:else}\n        <span class={cn('flex-1 truncate text-left', selectedOptions.length ? 'text-foreground' : 'text-muted-foreground')}>\n          {selectedOptions[0] ? getLabel(selectedOptions[0]) : placeholder}\n        </span>\n      {/if}\n    {/if}\n\n    <!-- Suffix area -->\n    <span class=\"flex shrink-0 items-center gap-1\">\n      {#if suffix}\n        {@render suffix()}\n      {/if}\n\n      {#if loading}\n        <LoaderCircle class=\"text-muted-foreground size-4 animate-spin\" />\n      {:else if showClear}\n        <span\n          role=\"button\"\n          tabindex=\"0\"\n          class=\"text-muted-foreground hover:text-foreground inline-flex cursor-pointer items-center rounded transition-colors\"\n          aria-label=\"Clear selection\"\n          onclick={(e) => clearAll(e)}\n          onkeydown={(e) => {\n            if (e.key === 'Enter' || e.key === ' ') {\n              e.preventDefault()\n              clearAll(e)\n            }\n          }}\n        >\n          {#if clearIcon}\n            {@render clearIcon()}\n          {:else}\n            <X class=\"size-4\" aria-hidden=\"true\" />\n          {/if}\n        </span>\n      {:else if suffixIcon}\n        {@render suffixIcon()}\n      {:else}\n        <ChevronDown class=\"text-muted-foreground size-4 opacity-50\" />\n      {/if}\n    </span>\n  </button>\n\n  {#if internalOpen}\n    <div\n      id={listboxId}\n      role=\"listbox\"\n      aria-multiselectable={isMultiple || undefined}\n      data-uipkge=\"\"\n      data-slot=\"advance-select-list\"\n      data-state=\"open\"\n      class=\"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 absolute top-full right-0 left-0 z-50 mt-1 w-full min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md outline-none\"\n      style:max-height={`${listHeight}px`}\n      onscroll={handlePopupScroll}\n    >\n      <div class=\"flex max-h-[inherit] flex-col overflow-hidden\">\n        {#if showSearchInput}\n          <div data-slot=\"advance-select-search\" class=\"flex h-9 shrink-0 items-center gap-2 border-b px-3\">\n            <Search class=\"text-muted-foreground size-4 shrink-0\" aria-hidden=\"true\" />\n            <input\n              bind:this={searchInput}\n              value={query}\n              {placeholder}\n              role=\"searchbox\"\n              aria-label=\"Search options\"\n              data-slot=\"advance-select-search-input\"\n              class=\"placeholder:text-muted-foreground flex h-9 w-full rounded-md bg-transparent text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n              oninput={(e) => setQuery(e.currentTarget.value)}\n              onkeydown={handleInputKeydown}\n            />\n          </div>\n        {/if}\n\n        <div class=\"max-h-[300px] flex-1 scroll-py-1 overflow-x-hidden overflow-y-auto p-1\">\n          {#if !loading && filteredOptions.length === 0 && !showCreate}\n            <div class=\"py-6 text-center text-sm\" data-slot=\"advance-select-empty\">\n              {#if emptyContent}\n                {@render emptyContent()}\n              {:else if empty}\n                {@render empty()}\n              {:else}\n                {notFoundContent}\n              {/if}\n            </div>\n          {/if}\n\n          {#if loading && filteredOptions.length === 0}\n            <div class=\"py-6 text-center text-sm\">\n              {loadingText}\n            </div>\n          {/if}\n\n          {#each grouped as group, gi (group.heading || gi)}\n            {#if gi > 0}\n              <div class=\"bg-border -mx-1 my-1 h-px\" role=\"separator\"></div>\n            {/if}\n            <div role=\"group\" aria-label={group.heading || undefined} class=\"text-foreground overflow-hidden p-1\">\n              {#if group.heading}\n                <div class=\"text-muted-foreground px-2 py-1.5 text-xs font-medium\">\n                  {group.heading}\n                </div>\n              {/if}\n              {#each group.items as opt, idx (String(getValue(opt)))}\n                {@const entryDisabled = isDisabled(opt) || (atMax && !selectedSet.has(getValue(opt)))}\n                {@const flatIndex = flatIndexOf(opt)}\n                <div\n                  role=\"option\"\n                  tabindex=\"-1\"\n                  aria-selected={selectedSet.has(getValue(opt))}\n                  data-highlighted={flatIndex === activeIndex || undefined}\n                  data-disabled={entryDisabled || undefined}\n                  data-slot=\"advance-select-option\"\n                  class={optionItemClasses}\n                  style:content-visibility={virtual && options.length > 100 ? 'auto' : undefined}\n                  onclick={() => selectOption(opt)}\n                  onkeydown={(e) => {\n                    if (e.key === 'Enter' || e.key === ' ') {\n                      e.preventDefault()\n                      selectOption(opt)\n                    }\n                  }}\n                  onmousemove={() => {\n                    if (!entryDisabled && flatIndex >= 0) activeIndex = flatIndex\n                  }}\n                >\n                  <Check class={cn('mr-2 size-4 shrink-0', selectedSet.has(getValue(opt)) ? 'opacity-100' : 'opacity-0')} />\n                  {#if option}\n                    {@render option({ option: opt, index: idx })}\n                  {:else}\n                    {getLabel(opt)}\n                  {/if}\n                </div>\n              {/each}\n            </div>\n          {/each}\n\n          <!-- Create new option in tags / allowCreate modes -->\n          {#if showCreate}\n            {@const createIndex = selectables.length - 1}\n            <div\n              role=\"option\"\n              tabindex=\"-1\"\n              aria-selected=\"false\"\n              data-highlighted={createIndex === activeIndex || undefined}\n              data-slot=\"advance-select-option\"\n              class={optionItemClasses}\n              onclick={createTag}\n              onkeydown={(e) => {\n                if (e.key === 'Enter' || e.key === ' ') {\n                  e.preventDefault()\n                  createTag()\n                }\n              }}\n              onmousemove={() => {\n                activeIndex = createIndex\n              }}\n            >\n              <Check class=\"mr-2 size-4 opacity-0\" />\n              Create \"{query.trim()}\"\n            </div>\n          {/if}\n        </div>\n\n        <!-- Footer for multiple mode -->\n        {#if isMultiple && selectedOptions.length}\n          <div class=\"flex shrink-0 items-center justify-between border-t px-2 py-1.5 text-xs\">\n            <span class=\"text-muted-foreground\">{selectedOptions.length} selected</span>\n            <button type=\"button\" class=\"text-muted-foreground hover:text-foreground\" onclick={(e) => clearAll(e)}>\n              Clear all\n            </button>\n          </div>\n        {/if}\n      </div>\n    </div>\n  {/if}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/advance-select/AdvanceSelect.svelte"
    },
    {
      "path": "packages/registry-svelte/components/advance-select/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 `fieldNames`\n * are provided. (Local copy of the select primitive's `option-types` so this\n * item installs standalone until the select port lands.)\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\nexport interface AdvanceSelectFieldNames {\n  label?: string\n  value?: string\n  group?: string\n  disabled?: string\n}\n\n/**\n * Resolve `option[key]` with a default fallback. Used by AdvanceSelect so every\n * accessor obeys `fieldNames`.\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/advance-select/types.ts"
    },
    {
      "path": "packages/registry-svelte/components/advance-select/index.ts",
      "content": "export { default as AdvanceSelect, type AdvanceSelectProps } from './AdvanceSelect.svelte'\nexport type { AdvanceSelectFieldNames, SelectOption } from './types'\nexport { readKey } from './types'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/advance-select/index.ts"
    }
  ],
  "dependencies": [
    "@lucide/svelte"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Searchable, async-capable select with keyboard navigation, multi-select, and option grouping. Drop in when the native `<select>` or the basic Select primitive runs out of room — large lists, debounced server-side filtering, custom rendered items.",
  "categories": [
    "form"
  ]
}