{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "option-picker",
  "title": "Option picker",
  "description": "Searchable single and multiple selection with removable chips and optional icon slots.",
  "dependencies": [
    "class-variance-authority@^0.7"
  ],
  "registryDependencies": [
    "@dwellir/dwellir-theme",
    "@dwellir/utils",
    "@dwellir/button",
    "@dwellir/popover",
    "@dwellir/input-modality",
    "@dwellir/rune-icons"
  ],
  "files": [
    {
      "path": "components/ui/option-picker.tsx",
      "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport {\n  type ComponentProps,\n  type KeyboardEvent,\n  type ReactNode,\n  useCallback,\n  useId,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport { RuneChevronDown } from '@/components/brand/rune-icons';\nimport { Popover, PopoverAnchor } from '@/components/ui/popover';\nimport { useInputModality } from '@/lib/input-modality';\nimport { cn } from '@/lib/utils';\nimport {\n  OptionGroups,\n  OptionPickerChip,\n  OptionPickerContent,\n  OptionPickerFormValue,\n  OptionPickerInput,\n  OptionPickerListElement,\n  OptionPickerSummary,\n} from './option-picker-parts';\n\nexport type OptionPickerOption = {\n  value: string;\n  label: string;\n  /** Decorative content shared by the option and its chip, including an endpoint's owner logo. */\n  icon?: ReactNode;\n  group?: string;\n  level?: 'chain' | 'network' | 'endpoint';\n  keywords?: string[];\n  disabled?: boolean;\n};\n\ntype Selection =\n  | {\n      multiple?: false;\n      value?: string;\n      defaultValue?: string;\n      onValueChange?: (value: string) => void;\n    }\n  | {\n      multiple: true;\n      value?: string[];\n      defaultValue?: string[];\n      onValueChange?: (value: string[]) => void;\n    };\n\ntype OptionPickerActionState = { open: boolean; onOpenChange: (open: boolean) => void };\n\nexport type OptionPickerProps = Selection &\n  VariantProps<typeof optionPickerTriggerVariants> &\n  Omit<ComponentProps<'input'>, 'value' | 'defaultValue' | 'onChange' | 'children' | 'size'> & {\n    'aria-label': string;\n    options: OptionPickerOption[];\n    empty?: ReactNode;\n    maxChips?: number;\n    maxSelections?: number;\n    maxVisibleOptions?: number;\n    summary?: boolean;\n    listPlacement?: 'popover' | 'inline';\n    action?: (state: OptionPickerActionState) => ReactNode;\n  };\n\n/* SelectTrigger's appearance, copied while #1991's shared trigger variant is pending. */\nconst optionPickerTriggerVariants = cva(\n  \"flex min-w-0 items-center gap-2 rounded-control border border-input bg-field text-lg text-foreground transition-[border-color] duration-200 outline-none hover:border-input-hover  has-[:focus-visible]:border-ring has-[:focus-visible]:outline-solid has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-ring data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[invalid=true]:border-destructive data-[invalid=true]:outline-destructive md:pointer-fine:text-sm motion-reduce:transition-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n  {\n    variants: { size: { default: 'min-h-10 pr-3 pl-4', sm: 'min-h-8 pr-2.5 pl-3.5' } },\n    defaultVariants: { size: 'default' },\n  },\n);\n\nconst announcementVariants = cva('', {\n  variants: {\n    limit: { true: 'block text-sm text-muted-foreground', false: 'sr-only' },\n  },\n});\n\ntype SearchEntry = { option: OptionPickerOption; text: string };\n\nfunction indexOptions(options: OptionPickerOption[]): SearchEntry[] {\n  return options.map((option) => ({\n    option,\n    text: [option.label, option.value, option.group, ...(option.keywords ?? [])]\n      .join(' ')\n      .toLocaleLowerCase(),\n  }));\n}\n\nfunction matches(index: SearchEntry[], query: string) {\n  const terms = query.toLocaleLowerCase().trim().split(/\\s+/);\n  return index\n    .filter(({ text }) => terms.every((term) => text.includes(term)))\n    .map(({ option }) => option);\n}\n\nfunction isNavigationKey(key: string, open: boolean) {\n  return ['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(key) && (open || key.startsWith('Arrow'));\n}\n\nfunction nextActive(options: OptionPickerOption[], active: string, key: string, open: boolean) {\n  const enabled = options.filter((option) => !option.disabled);\n  const current = enabled.findIndex((option) => option.value === active);\n  if (key === 'Home') return enabled[0]?.value ?? '';\n  if (key === 'End') return enabled.at(-1)?.value ?? '';\n  if (key === 'ArrowUp')\n    return enabled[open ? Math.max(0, current - 1) : enabled.length - 1]?.value ?? '';\n  return enabled[open ? Math.min(enabled.length - 1, current + 1) : 0]?.value ?? '';\n}\n\nfunction useSelection(props: Selection) {\n  const [internal, setInternal] = useState<string | string[]>(\n    props.defaultValue ?? (props.multiple ? [] : ''),\n  );\n  const current = props.value ?? internal;\n  const values = Array.isArray(current) ? current : current ? [current] : [];\n  const update = (next: string[]) => {\n    if (props.multiple) {\n      setInternal(next);\n      props.onValueChange?.(next);\n    } else {\n      setInternal(next[0] ?? '');\n      props.onValueChange?.(next[0] ?? '');\n    }\n  };\n  const reset = useCallback(() => {\n    if (props.value === undefined) setInternal(props.defaultValue ?? (props.multiple ? [] : ''));\n  }, [props.value, props.defaultValue, props.multiple]);\n  return { values, update, reset };\n}\n\nfunction OptionPicker(props: OptionPickerProps) {\n  useInputModality();\n  const {\n    options,\n    multiple = false,\n    maxChips = 3,\n    maxSelections,\n    maxVisibleOptions = 150,\n    listPlacement = 'popover',\n    summary,\n    action,\n    placeholder = 'Search options',\n    empty = 'No options found. Try another search.',\n    size,\n    className,\n    disabled,\n    required,\n    name,\n    form,\n    ref,\n    onKeyDown,\n    onClick,\n    onFocus,\n    'aria-label': label,\n    'aria-describedby': describedBy,\n    'aria-invalid': invalid,\n    value: _value,\n    defaultValue: _defaultValue,\n    onValueChange: _onValueChange,\n    ...inputProps\n  } = props;\n  const { values, update, reset } = useSelection(props);\n  const [open, setOpen] = useState(false);\n  const [query, setQuery] = useState('');\n  const [active, setActive] = useState('');\n  const [announcement, setAnnouncement] = useState({ message: '', limit: false });\n  const announce = (message: string, limit = false) => setAnnouncement({ message, limit });\n  const input = useRef<HTMLInputElement>(null);\n  const portal = useRef<HTMLDivElement>(null);\n  const root = useRef<HTMLDivElement>(null);\n  const returnFocus = useRef(false);\n  const selectionId = useId();\n  const isOpen = open && !disabled;\n  useLayoutEffect(() => {\n    input.current?.setCustomValidity(required && !values.length ? 'Choose an option.' : '');\n  }, [required, values.length]);\n  useLayoutEffect(() => {\n    const owner = form ? document.getElementById(form) : input.current?.form;\n    if (!(owner instanceof HTMLFormElement)) return;\n    const restore = (event: Event) =>\n      queueMicrotask(() => {\n        if (event.defaultPrevented) return;\n        reset();\n        setQuery('');\n        setOpen(false);\n      });\n    owner.addEventListener('reset', restore);\n    return () => owner.removeEventListener('reset', restore);\n  }, [form, reset]);\n  useLayoutEffect(() => {\n    if (disabled) setOpen(false);\n  }, [disabled]);\n  useLayoutEffect(() => {\n    if (!isOpen && !multiple) setQuery('');\n  }, [isOpen, multiple]);\n  const searchIndex = useMemo(() => indexOptions(options), [options]);\n  const filtered = useMemo(() => matches(searchIndex, query), [searchIndex, query]);\n  const listed = useMemo(\n    () => filtered.slice(0, Math.max(1, Math.floor(maxVisibleOptions))),\n    [filtered, maxVisibleOptions],\n  );\n  useLayoutEffect(() => {\n    setActive(listed.find((option) => !option.disabled)?.value ?? '');\n  }, [listed]);\n  useLayoutEffect(() => {\n    if (!isOpen) return;\n    const close = (event: globalThis.KeyboardEvent) => {\n      if (event.key !== 'Escape') return;\n      event.preventDefault();\n      event.stopPropagation();\n      returnFocus.current = true;\n      setOpen(false);\n      input.current?.focus();\n    };\n    // Dialogs listen on document capture; the open list owns Escape before they see it.\n    window.addEventListener('keydown', close, true);\n    return () => window.removeEventListener('keydown', close, true);\n  }, [isOpen]);\n  useLayoutEffect(() => {\n    if (!isOpen || !active) return;\n    const row = document.getElementById(`${selectionId}-${encodeURIComponent(active)}`);\n    const list = row?.closest<HTMLElement>('[role=listbox]');\n    if (!row || !list) return;\n    const bounds = row.getBoundingClientRect();\n    const viewport = list.getBoundingClientRect();\n    if (bounds.top < viewport.top) list.scrollTop -= viewport.top - bounds.top;\n    else if (bounds.bottom > viewport.bottom) list.scrollTop += bounds.bottom - viewport.bottom;\n  }, [isOpen, active, selectionId]);\n  const selected = values.map(\n    (value) => options.find((option) => option.value === value) ?? { value, label: value },\n  );\n  const visible = selected.slice(0, Math.max(0, Math.floor(maxChips)));\n  const overflow = selected.length - visible.length;\n\n  const changeOpen = (next: boolean) => {\n    setOpen(next);\n    if (next) input.current?.focus();\n  };\n  const search = (next: string) => {\n    setQuery(next);\n    setOpen(true);\n  };\n  const remove = (option: OptionPickerOption) => {\n    update(values.filter((value) => value !== option.value));\n    announce(`${option.label} removed.`);\n  };\n  const select = (option: OptionPickerOption | undefined) => {\n    if (!option || option.disabled) return;\n    if (multiple && values.includes(option.value)) {\n      remove(option);\n      return;\n    }\n    if (multiple && maxSelections !== undefined && values.length >= maxSelections) {\n      announce(`Selection limit reached. Choose up to ${maxSelections} options.`, true);\n      return;\n    }\n    update(multiple ? [...values, option.value] : [option.value]);\n    announce(`${option.label} selected.`);\n    if (!multiple) {\n      returnFocus.current = true;\n      setQuery('');\n      setOpen(false);\n    }\n  };\n  const keyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n    onKeyDown?.(event);\n    if (event.defaultPrevented || event.nativeEvent.isComposing) return;\n    if (event.key === 'Backspace' && !query && multiple && selected.length) {\n      event.preventDefault();\n      remove(selected[selected.length - 1]);\n    } else if (event.key === 'Enter' && isOpen) {\n      event.preventDefault();\n      select(listed.find((item) => item.value === active));\n    } else if (isNavigationKey(event.key, isOpen)) {\n      event.preventDefault();\n      setActive(nextActive(listed, active, event.key, isOpen));\n      changeOpen(true);\n    } else if (event.key === 'Tab') setOpen(false);\n  };\n\n  return (\n    <div ref={root} data-slot=\"option-picker\" className={cn('min-w-0', className)}>\n      <OptionPickerFormValue\n        name={name}\n        form={form}\n        disabled={disabled}\n        multiple={multiple}\n        values={values}\n      />\n      <OptionPickerSummary\n        summary={summary}\n        count={values.length}\n        maxSelections={maxSelections}\n        disabled={disabled}\n        onClear={() => {\n          update([]);\n          setQuery('');\n          announce('Selection cleared.');\n          input.current?.focus();\n        }}\n      />\n      <div ref={portal} data-slot=\"option-picker-control\">\n        <Popover open={isOpen} onOpenChange={changeOpen}>\n          <PopoverAnchor asChild>\n            <div\n              data-slot=\"option-picker-field\"\n              data-size={size ?? 'default'}\n              data-disabled={disabled}\n              aria-disabled={disabled}\n              data-invalid={invalid === true || invalid === 'true'}\n              className={optionPickerTriggerVariants({ size })}\n            >\n              <div\n                data-slot=\"option-picker-values\"\n                className=\"flex min-w-0 flex-1 flex-wrap items-center gap-1\"\n              >\n                {multiple\n                  ? visible.map((option) => (\n                      <OptionPickerChip\n                        key={option.value}\n                        option={option}\n                        disabled={disabled}\n                        onRemove={() => {\n                          remove(option);\n                          input.current?.focus();\n                        }}\n                      />\n                    ))\n                  : null}\n                {multiple && overflow > 0 ? (\n                  <span\n                    data-slot=\"option-picker-overflow\"\n                    aria-hidden=\"true\"\n                    className=\"shrink-0 px-2 text-sm text-muted-foreground tabular-nums\"\n                  >\n                    +{overflow} more\n                  </span>\n                ) : null}\n                <OptionPickerInput\n                  {...inputProps}\n                  form={form}\n                  ref={(node) => {\n                    input.current = node;\n                    if (typeof ref === 'function') ref(node);\n                    else if (ref) ref.current = node;\n                  }}\n                  aria-label={label}\n                  aria-describedby={[describedBy, selectionId].filter(Boolean).join(' ')}\n                  aria-invalid={invalid}\n                  aria-required={required || undefined}\n                  disabled={disabled}\n                  query={query}\n                  onQueryChange={search}\n                  open={isOpen}\n                  active={active}\n                  baseId={selectionId}\n                  selection={multiple ? undefined : selected[0]}\n                  placeholder={placeholder}\n                  size={size}\n                  onClick={(event) => {\n                    onClick?.(event);\n                    if (!event.defaultPrevented) changeOpen(true);\n                  }}\n                  onFocus={onFocus}\n                  onKeyDown={keyDown}\n                />\n              </div>\n              <div\n                data-slot=\"option-picker-action\"\n                inert={disabled}\n                className=\"flex shrink-0 items-center\"\n              >\n                {action ? (\n                  action({ open: Boolean(isOpen), onOpenChange: changeOpen })\n                ) : (\n                  <button\n                    data-slot=\"option-picker-toggle\"\n                    type=\"button\"\n                    disabled={disabled}\n                    aria-label={`Toggle ${label}`}\n                    aria-expanded={isOpen}\n                    className=\"flex size-6 touch-manipulation items-center justify-center rounded-full text-muted-foreground outline-none pointer-coarse:size-11\"\n                    onClick={() => changeOpen(!open)}\n                  >\n                    <RuneChevronDown\n                      data-slot=\"option-picker-chevron\"\n                      aria-hidden=\"true\"\n                      className=\"size-4\"\n                    />\n                  </button>\n                )}\n              </div>\n            </div>\n          </PopoverAnchor>\n          <OptionPickerContent\n            open={isOpen}\n            listPlacement={listPlacement}\n            container={portal.current}\n            aria-label={`${label} options`}\n            align=\"start\"\n            className=\"flex h-80 w-(--radix-popover-trigger-width) flex-col overflow-hidden p-0 animate-none!\"\n            onOpenAutoFocus={(event) => event.preventDefault()}\n            onCloseAutoFocus={(event) => {\n              event.preventDefault();\n              if (returnFocus.current) input.current?.focus();\n              returnFocus.current = false;\n            }}\n            onInteractOutside={(event) => {\n              if (event.target instanceof Node && root.current?.contains(event.target))\n                event.preventDefault();\n            }}\n          >\n            <OptionPickerListElement\n              id={`${selectionId}-listbox`}\n              aria-label={label}\n              aria-multiselectable={multiple || undefined}\n            >\n              <OptionGroups\n                options={listed}\n                values={values}\n                active={active}\n                onActiveChange={setActive}\n                onSelect={select}\n                baseId={selectionId}\n              />\n            </OptionPickerListElement>\n            {!filtered.length ? (\n              <p\n                data-slot=\"option-picker-empty\"\n                role=\"status\"\n                className=\"shrink-0 py-6 text-center text-sm\"\n              >\n                {empty}\n              </p>\n            ) : null}\n            {listed.length < filtered.length ? (\n              <p\n                data-slot=\"option-picker-remainder\"\n                role=\"status\"\n                aria-live=\"polite\"\n                className=\"shrink-0 px-3 py-2 text-sm text-muted-foreground\"\n              >\n                Showing {listed.length} of {filtered.length}. Keep typing to narrow.\n              </p>\n            ) : null}\n          </OptionPickerContent>\n        </Popover>\n      </div>\n      <span data-slot=\"option-picker-selection\" id={selectionId} className=\"sr-only\">\n        {selected.length ? selected.map((option) => option.label).join(', ') : 'No selection'}\n      </span>\n      <span\n        data-slot=\"option-picker-announcement\"\n        role=\"status\"\n        aria-live=\"polite\"\n        aria-atomic=\"true\"\n        className={announcementVariants({ limit: announcement.limit })}\n      >\n        {announcement.message}\n      </span>\n    </div>\n  );\n}\n\nexport type { OptionPickerChipProps } from './option-picker-parts';\nexport {\n  OptionPicker,\n  type OptionPickerActionState,\n  OptionPickerChip,\n  optionPickerTriggerVariants,\n};\n",
      "type": "registry:ui"
    },
    {
      "path": "components/ui/option-picker-parts.tsx",
      "content": "import { cva } from 'class-variance-authority';\nimport { type ComponentProps, useCallback, useEffect, useState } from 'react';\nimport { RuneCheck, RuneClose } from '@/components/brand/rune-icons';\nimport { Button } from '@/components/ui/button';\nimport { PopoverContent } from '@/components/ui/popover';\nimport { cn } from '@/lib/utils';\nimport type { OptionPickerOption } from './option-picker';\n\nconst optionVariants = cva(\n  'relative flex min-h-11 min-w-0 cursor-pointer items-center gap-2 rounded-lg px-3 py-2 text-sm outline-none select-none data-[active=true]:bg-secondary data-[active=true]:text-secondary-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 in-data-[input=keyboard]:data-[active=true]:outline-solid in-data-[input=keyboard]:data-[active=true]:outline-2 in-data-[input=keyboard]:data-[active=true]:-outline-offset-2 in-data-[input=keyboard]:data-[active=true]:outline-ring [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4',\n  {\n    variants: { level: { chain: '', network: 'pl-7', endpoint: 'pl-11 font-mono' } },\n  },\n);\n\nconst labelVariants = cva('min-w-0', {\n  variants: { level: { chain: 'truncate', network: 'truncate', endpoint: 'break-all' } },\n  defaultVariants: { level: 'chain' },\n});\n\nfunction OptionLabel({ option }: { option: OptionPickerOption }) {\n  return (\n    <>\n      {option.icon ? (\n        <span\n          data-slot=\"option-picker-icon\"\n          aria-hidden=\"true\"\n          className=\"flex shrink-0 items-center\"\n        >\n          {option.icon}\n        </span>\n      ) : null}\n      <span\n        data-slot=\"option-picker-option-label\"\n        title={option.level === 'endpoint' ? option.label : undefined}\n        className={labelVariants({ level: option.level })}\n      >\n        {option.label}\n      </span>\n    </>\n  );\n}\n\nexport type OptionPickerChipProps = {\n  option: OptionPickerOption;\n  disabled?: boolean;\n  onRemove: () => void;\n};\n\nfunction OptionPickerChip({ option, disabled, onRemove }: OptionPickerChipProps) {\n  return (\n    <span\n      data-slot=\"option-picker-chip\"\n      className=\"inline-flex min-w-0 max-w-full items-center gap-1 rounded-full bg-muted pl-2 text-sm text-foreground\"\n    >\n      <OptionLabel option={option} />\n      <button\n        data-slot=\"option-picker-chip-remove\"\n        type=\"button\"\n        disabled={disabled}\n        aria-label={`Remove ${option.label}`}\n        className=\"flex size-6 shrink-0 touch-manipulation items-center justify-center rounded-full outline-none hover:bg-secondary focus-visible:outline-solid focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring pointer-coarse:size-11\"\n        onClick={onRemove}\n      >\n        <RuneClose\n          data-slot=\"option-picker-chip-remove-icon\"\n          aria-hidden=\"true\"\n          className=\"size-3\"\n        />\n      </button>\n    </span>\n  );\n}\n\nfunction OptionGroups({\n  options,\n  values,\n  active,\n  onActiveChange,\n  onSelect,\n  baseId,\n}: {\n  options: OptionPickerOption[];\n  values: string[];\n  active: string;\n  onActiveChange: (value: string) => void;\n  onSelect: (option: OptionPickerOption) => void;\n  baseId: string;\n}) {\n  return Array.from(\n    Map.groupBy(options, (option) => option.group ?? ''),\n    ([group, items]) => (\n      <fieldset\n        key={group}\n        data-slot=\"option-picker-group\"\n        className=\"min-w-0 border-0 p-0\"\n        aria-labelledby={group ? `${baseId}-group-${encodeURIComponent(group)}` : undefined}\n      >\n        {group ? (\n          <div\n            data-slot=\"option-picker-group-label\"\n            id={`${baseId}-group-${encodeURIComponent(group)}`}\n            className=\"px-3 pt-2 pb-1 text-xs font-medium text-muted-foreground\"\n          >\n            {group}\n          </div>\n        ) : null}\n        {items.map((option) => (\n          <div\n            key={option.value}\n            id={`${baseId}-${encodeURIComponent(option.value)}`}\n            data-slot=\"option-picker-option\"\n            role=\"option\"\n            tabIndex={-1}\n            aria-selected={values.includes(option.value)}\n            aria-disabled={option.disabled || undefined}\n            data-disabled={option.disabled}\n            data-active={active === option.value}\n            className={optionVariants({ level: option.level })}\n            onPointerMove={() => {\n              if (!option.disabled) onActiveChange(option.value);\n            }}\n            onMouseDown={(event) => event.preventDefault()}\n            onClick={() => {\n              if (!option.disabled) onSelect(option);\n            }}\n          >\n            <OptionLabel option={option} />\n            <RuneCheck\n              data-slot=\"option-picker-option-check\"\n              aria-hidden=\"true\"\n              className={cn(\n                'ml-auto size-4 shrink-0',\n                !values.includes(option.value) && 'invisible',\n              )}\n            />\n          </div>\n        ))}\n      </fieldset>\n    ),\n  );\n}\n\nconst optionPickerListVariants = cva('', {\n  variants: {\n    listPlacement: {\n      popover: '',\n      inline:\n        'mt-2 min-w-0 overflow-hidden rounded-2xl border border-border bg-popover text-popover-foreground',\n    },\n  },\n});\n\n/*\n * The list renders inside the picker, so a modal's scroll lock lets it scroll by\n * touch. A modal clips what overflows it: once placed, a list that reaches past\n * the modal's edge scrolls the modal, not the page behind, until it shows. It\n * then keeps its side: scrolling the modal back to the summary would otherwise\n * flip the list over the summary's Clear. The modal's scroller is its body part,\n * else its panel: closest() finds the nearer one.\n */\nconst MODALS =\n  '[data-slot=dialog-body], [data-slot=sheet-body], [data-slot=alert-dialog-body], [data-slot=dialog-content], [data-slot=sheet-content], [data-slot=alert-dialog-content]';\n\nfunction revealInModal(list: HTMLElement | null) {\n  const modal = list?.closest<HTMLElement>(MODALS);\n  if (!list || !modal) return;\n  const box = list.getBoundingClientRect();\n  const frame = modal.getBoundingClientRect();\n  const below = box.bottom - frame.bottom + 12;\n  const above = frame.top - box.top + 12;\n  // A list taller than the modal keeps its top, where the first options are.\n  if (above > 0) modal.scrollTop -= above;\n  else if (below > 0) modal.scrollTop += Math.min(below, -above);\n}\n\n/* Radix places the list a frame or two after it mounts: act once its box holds still. */\nfunction whenPlaced(list: HTMLElement, done: () => void) {\n  let last = '';\n  let frames = 0;\n  const check = () => {\n    if (!list.isConnected || ++frames > 30) return;\n    const box = list.getBoundingClientRect();\n    const now = `${box.top},${box.bottom}`;\n    if (now === last && box.bottom > 0) done();\n    else {\n      last = now;\n      requestAnimationFrame(check);\n    }\n  };\n  requestAnimationFrame(check);\n}\n\nfunction OptionPickerContent({\n  open,\n  listPlacement,\n  ...props\n}: ComponentProps<typeof PopoverContent> & { open: boolean; listPlacement: 'popover' | 'inline' }) {\n  const [side, setSide] = useState<'top' | 'bottom'>();\n  useEffect(() => {\n    if (!open) setSide(undefined);\n  }, [open]);\n  const placeInModal = useCallback((list: HTMLDivElement | null) => {\n    if (!list?.closest(MODALS)) return;\n    whenPlaced(list, () => {\n      revealInModal(list);\n      setSide(list.dataset.side === 'top' ? 'top' : 'bottom');\n    });\n  }, []);\n  if (listPlacement === 'popover')\n    return <PopoverContent ref={placeInModal} side={side} avoidCollisions={!side} {...props} />;\n  if (!open) return null;\n  return (\n    <div\n      data-slot=\"option-picker-inline-list\"\n      className={optionPickerListVariants({ listPlacement })}\n    >\n      {props.children}\n    </div>\n  );\n}\n\nfunction OptionPickerListElement(props: ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"option-picker-list\"\n      role=\"listbox\"\n      className=\"max-h-80 min-h-0 flex-1 scroll-py-2 overflow-x-hidden overflow-y-auto overscroll-contain p-2\"\n      {...props}\n    />\n  );\n}\n\nfunction OptionPickerSummary({\n  summary,\n  count,\n  maxSelections,\n  disabled,\n  onClear,\n}: {\n  summary?: boolean;\n  count: number;\n  maxSelections?: number;\n  disabled?: boolean;\n  onClear: () => void;\n}) {\n  const visible = summary ?? (maxSelections !== undefined || count > 0);\n  if (!visible) return null;\n  return (\n    <div\n      data-slot=\"option-picker-summary\"\n      className=\"flex min-w-0 items-center justify-between gap-2 pb-1\"\n    >\n      <span\n        data-slot=\"option-picker-count\"\n        role=\"status\"\n        aria-live=\"polite\"\n        aria-atomic=\"true\"\n        className=\"text-sm text-muted-foreground tabular-nums\"\n      >\n        {maxSelections === undefined ? `${count} selected` : `${count} of ${maxSelections}`}\n      </span>\n      <Button\n        type=\"button\"\n        variant=\"ghost\"\n        size=\"xs\"\n        disabled={disabled || !count}\n        onClick={onClear}\n      >\n        Clear\n      </Button>\n    </div>\n  );\n}\n\nfunction OptionPickerFormValue({\n  name,\n  form,\n  disabled,\n  multiple,\n  values,\n}: Pick<ComponentProps<'input'>, 'name' | 'form' | 'disabled'> & {\n  multiple: boolean;\n  values: string[];\n}) {\n  if (!name) return null;\n  const submitted = multiple ? values : [values[0] ?? ''];\n  return submitted.map((value) => (\n    <input\n      key={value}\n      data-slot=\"option-picker-form-value\"\n      type=\"hidden\"\n      name={name}\n      form={form}\n      disabled={disabled}\n      value={value}\n    />\n  ));\n}\n\nconst inputVariants = cva(\n  'min-w-0 flex-1 basis-20 bg-transparent text-lg text-foreground outline-none md:pointer-fine:text-sm pointer-coarse:min-h-11',\n  {\n    variants: {\n      size: { default: 'h-8', sm: 'h-6' },\n      selected: { true: 'placeholder:text-foreground', false: 'placeholder:text-muted-foreground' },\n    },\n    defaultVariants: { size: 'default' },\n  },\n);\n\nfunction OptionPickerInput({\n  query,\n  onQueryChange,\n  open,\n  active,\n  baseId,\n  selection,\n  size,\n  ...props\n}: Omit<ComponentProps<'input'>, 'size'> & { size?: 'default' | 'sm' | null } & {\n  query: string;\n  onQueryChange: (query: string) => void;\n  open: boolean;\n  active: string;\n  baseId: string;\n  selection?: OptionPickerOption;\n}) {\n  return (\n    <input\n      {...props}\n      type=\"text\"\n      autoComplete=\"off\"\n      spellCheck={false}\n      aria-autocomplete=\"list\"\n      onChange={(event) => onQueryChange(event.currentTarget.value)}\n      role=\"combobox\"\n      data-slot=\"option-picker-input\"\n      aria-expanded={open}\n      aria-controls={open ? `${baseId}-listbox` : undefined}\n      aria-activedescendant={open && active ? `${baseId}-${encodeURIComponent(active)}` : undefined}\n      value={query}\n      placeholder={!open && selection ? selection.label : props.placeholder}\n      className={inputVariants({\n        size: size ?? 'default',\n        selected: !open && Boolean(selection),\n      })}\n    />\n  );\n}\n\nexport {\n  OptionGroups,\n  OptionPickerChip,\n  OptionPickerContent,\n  OptionPickerFormValue,\n  OptionPickerInput,\n  OptionPickerListElement,\n  OptionPickerSummary,\n};\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}