import { Children, isValidElement, useEffect, useId, useRef, useState, type ReactNode, type Ref, } from 'react' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { Icon } from './Icon' import styles from './Select.module.css' /* The dropdown, built here. It used to be the operating system's ` { search.current = el el?.focus({ preventScroll: true }) }} type="text" role="combobox" aria-expanded="true" aria-controls={listId} aria-autocomplete="list" aria-activedescendant={activeShown ? `${listId}-${active}` : undefined} aria-label={searchLabel} placeholder={searchLabel} autoComplete="off" spellCheck={false} className={styles.search} value={query} onChange={(e) => onSearch(e.target.value)} onKeyDown={onSearchKeyDown} />
{lines}
{visible.length === 0 ? noMatchText : ''}
) : (
{lines}
)} ) } /* Moving is over the options shown (`shown`, indices into `options`): all of them unless a search is filtering. `from` and the result are positions in `shown` turned back into indices. */ /** The first usable option from position `from` in that direction; if there is * none, `fallback` (by default the option at `from`). */ function firstUsable(options: Option[], shown: number[], from: number, dir: number, fallback?: number): number { for (let p = from; p >= 0 && p < shown.length; p += dir) { if (!options[shown[p]].disabled) return shown[p] } return fallback ?? shown[from] ?? -1 } /** The next usable option after `current` in that direction; if there is none, it * stays put. From an option not shown, it starts at the first or the last. */ function step(options: Option[], shown: number[], current: number, dir: number): number { const p = shown.indexOf(current) const from = p < 0 ? (dir > 0 ? 0 : shown.length - 1) : p + dir return firstUsable(options, shown, from, dir, p < 0 ? -1 : current) } /** Merges the internal ref with whatever the caller may pass. */ function merge(own: React.RefObject, outside?: Ref) { return (node: HTMLButtonElement | null) => { own.current = node if (typeof outside === 'function') outside(node) else if (outside != null) (outside as { current: HTMLButtonElement | null }).current = node } }