import type * as React from 'react'; import type { BadgeProps } from '../badge/types.cjs'; import type { TagGroupProps } from '../tag-group/types.cjs'; import type { AutoFocusEvent } from '../utils/dismissal.cjs'; import type { FormValidityHandle } from '../utils/formControl.cjs'; import type { PassThroughProps } from '../utils/passThrough.cjs'; import type { PopperContentProps } from '../utils/popper.cjs'; import type { PortalProps } from '../utils/portal.cjs'; import type { PrimitivePropsWithRef } from '../utils/primitive.cjs'; import type { ScoredFilterResult } from '../utils/scoredFilter.cjs'; import type { InteractOutsideEvent } from '../utils/useDismiss.cjs'; import type { OptionEntry } from '../utils/useOptionCollection.cjs'; import type { SelectionChangeDetail, SelectionChangeReason } from '../utils/useSelection.cjs'; /** One entry of {@link SelectProps.items}: option metadata before rendering. */ export interface SelectItemDescriptor { /** Identifier committed to the root's value when chosen. */ value: string; /** Text for SelectValue and typeahead; overridden by mounted SelectItemText. @default undefined — falls back to value. */ label?: string | undefined; /** Excludes from trigger typeahead; rendered SelectItem's disabled excludes from selection. @default false */ disabled?: boolean | undefined; } /** One option for {@link SelectProps.options} recipe prop — flat entry rendered as SelectItem. */ export interface SelectOption { /** Identifier committed when chosen. */ value: string; /** Text for SelectValue and typeahead. @default undefined — falls back to value. */ label?: string | undefined; /** Excludes from selection and typeahead. @default false */ disabled?: boolean | undefined; } /** Labeled group of SelectOptions for SelectProps.options; grouping is data-driven. */ export interface SelectOptionGroup { /** Heading rendered as SelectLabel above this group's options. */ label: string; /** Options in this group. */ options: SelectOption[]; } /** One entry of {@link SelectProps.options}: a flat option, or a labeled group of them. */ export type SelectOptionOrGroup = SelectOption | SelectOptionGroup; /** Decides which options survive the draft text: a boolean keeps or drops, a number filters and ranks. */ export type SelectFilter = (itemText: string, inputValue: string) => ScoredFilterResult; /** Where opening should put the active option: 'selected', 'first', or 'last'. @internal */ export type SelectOpenIntent = 'selected' | 'first' | 'last'; /** @internal Shared state distributed from {@link Select} to its parts via context. */ export interface SelectContextValue { /** Hands focus back to the trigger; the one place `close-auto-focus` announces from. */ focusTrigger: () => void; /** SelectContent's onCloseAutoFocus callback; mutable ref because Content's lifecycle spans the root's. */ closeAutoFocusRef: React.RefObject<((event: AutoFocusEvent) => void) | undefined>; triggerRef: React.RefObject; contentRef: React.RefObject; contentId: string; /** Id on SelectValue; trigger's aria-labelledby points at it. */ valueId: string; /** Whether SelectValue is mounted (aria-labelledby must name an existing element). */ hasValuePart: boolean; /** Called by SelectValue on mount/unmount to keep trigger's aria-labelledby valid. */ setHasValuePart: (present: boolean) => void; /** Id of virtually-focused option or null; published as aria-activedescendant/data-highlighted. */ activeId: string | null; /** Moves virtual focus to an option or clears it. */ setActiveId: (id: string | null) => void; /** Where next open should put active option; written by trigger, read by SelectContent on mount. Ref for inter-part messaging, not rendering. */ openIntentRef: React.RefObject; /** The filter Input, which holds DOM focus in the trigger's place while `filterable`. */ inputRef: React.RefObject; /** Whether more than one option can be selected. */ multiple: boolean; /** Whether a filter Input stands in for the trigger and narrows the options. */ filterable: boolean; /** Whether the option list is in flight, published as aria-busy/data-loading. */ loading: boolean; /** Whether Control+A may sweep every enabled option while multiple. */ allowSelectAll: boolean; /** Whether the selection reached maxSelected, capping the options that would grow it. */ atMax: boolean; /** Draft text in the filter Input. */ inputValue: string; /** Replaces the draft text, reporting it through onInputValueChange. */ setInputValue: (next: string) => void; /** Whether committing an option closes the listbox (defaults to !multiple). */ closeOnSelect: boolean; /** The rendered enabled options in traversal order, ranked when the filter scores. */ orderedOptions: () => HTMLElement[]; /** Rank each visible option renders in when the filter scores numerically; null leaves DOM order. */ itemOrder: ReadonlyMap | null; /** How many options currently survive the filter, for the Empty part. */ itemCount: number; /** Publishes an option's filter text while it is rendered. */ registerItem: (id: string, entry: OptionEntry) => void; /** Withdraws an option the filter dropped, or unmount removed. */ unregisterItem: (id: string) => void; /** The entry an option id registered, for committing the highlighted option. */ getItem: (id: string) => OptionEntry | undefined; /** Whether an option's text survives the current filter and draft text. */ matchesFilter: (text: string) => boolean; /** Selects the run between the range anchor and the highlighted option. */ selectRange: () => void; /** Selects from the highlighted option to one end of the list, additively. */ selectToEdge: (edge: 'first' | 'last') => void; /** Selects every enabled option, or clears them when all are already selected. */ selectAllOrClear: () => void; /** Empties the selection and any draft text in one step. */ clear: () => void; /** Removes the last selected value, for Backspace on an empty filter Input. */ removeLast: () => void; /** Removes one value from the selection, for a Tag's remove button. */ deselect: (value: string) => void; /** Every selected value, normalized to an array. */ selectedValues: string[]; /** Whether one option value is currently selected. */ isSelected: (value: string) => boolean; /** Commits an option: replaces or toggles it when multiple. */ selectItem: (value: string, reason?: SelectionChangeReason) => void; open: boolean; onOpenChange: (open: boolean) => void; disabled: boolean; /** Selection is fixed; trigger stays focusable and in form submission, but listbox refuses to open. */ readOnly: boolean; required: boolean; invalid: boolean; name?: string | undefined; registerOptionLabel: (value: string, label: string) => void; /** Label for a value: registered SelectItemText, then items, then value itself. */ getOptionLabel: (value: string) => string; /** Every option known without opening (the trigger-typeahead pool). */ collection: SelectItemDescriptor[]; } /** Props for Select root; standard attributes land on the derived SelectTrigger. */ export interface SelectProps extends PassThroughProps { /** * Trigger, portal, and content parts with selection/open state via context. * @default undefined */ children?: React.ReactNode | undefined; /** Selected option value(s) for controlled usage; pair with onValueChange. @default undefined — uncontrolled. */ value?: string | string[] | undefined; /** Initially selected value(s) for uncontrolled usage. @default undefined — no option selected until user picks one. */ defaultValue?: string | string[] | undefined; /** Fires when selection changes with the whole selection, not the toggled option. @default undefined — no-op if omitted. */ onValueChange?: ((detail: SelectionChangeDetail) => void) | undefined; /** Allows more than one option selected. Sets aria-multiselectable="true" on Content, toggles options instead of replacing, keeps listbox open. @default false */ multiple?: boolean | undefined; /** Picking the already-selected option in single-select mode clears it; ignored in multiple mode. @default false */ deselectable?: boolean | undefined; /** Largest number of values selectable while multiple; unselected options go aria-disabled once reached, and selected ones stay removable. @default undefined — the selection grows without a cap. */ maxSelected?: number | undefined; /** Whether Control+A selects every enabled option while multiple and the listbox is open, and the derived anatomy offers a SelectAll row. @default true */ allowSelectAll?: boolean | undefined; /** Whether the derived anatomy renders a filter Input in the trigger's place, narrowing the options as the user types. @default false */ filterable?: boolean | undefined; /** Controlled draft text in the filter Input; pair with onInputValueChange. Read only while filterable. @default undefined — uncontrolled. */ inputValue?: string | undefined; /** Initial draft text in the filter Input, for uncontrolled usage. @default '' */ defaultInputValue?: string | undefined; /** Fires with the new draft text on every keystroke, and with the empty string when a selection or Clear empties it. @default undefined — no-op if omitted. */ onInputValueChange?: ((inputValue: string) => void) | undefined; /** Predicate deciding which options survive the draft text: a boolean filters, a number filters and ranks (0 excludes). Read only while filterable. @default undefined — every option survives. */ filter?: SelectFilter | undefined; /** Whether the option list is in flight, published as aria-busy and data-loading without blocking interaction. @default false */ loading?: boolean | undefined; /** Whether the derived anatomy renders a Clear button emptying the selection and draft text in one press. @default false */ clearable?: boolean | undefined; /** Formats the message announced after each selection change while multiple; return an empty string to announce nothing. @default defaultGetSelectionLabel — "2 selected". */ getSelectionLabel?: ((count: number) => string) | undefined; /** Option collection declared up front. Enables closed-trigger typeahead and labels on first paint before Content mounts. @default undefined — only knows rendered options. */ items?: SelectItemDescriptor[] | undefined; /** Flat or grouped option data to render automatically — trigger, portal, listbox, and Items — instead of hand composition. @default undefined — compose parts yourself. */ options?: SelectOptionOrGroup[] | undefined; /** Customizes how derived option renders inside SelectItem; receives option and selected state. @default undefined — renders option.label ?? option.value. */ renderOption?: ((option: SelectOption, state: { selected: boolean; }) => React.ReactNode) | undefined; /** Customizes trigger content in place of default label/chips; receives selected option(s) or null. @default undefined — renders default label/placeholder/chips. */ renderValue?: ((selected: SelectOption | SelectOption[] | null) => React.ReactNode) | undefined; /** Customises a selected value's content inside its derived Tag; only read while multiple and options derives the anatomy. @default undefined — renders option.label ?? option.value. */ renderTag?: ((option: SelectOption) => React.ReactNode) | undefined; /** Placeholder shown by derived trigger while nothing is selected; only read while options derives. @default undefined — renders nothing when no option selected. */ placeholder?: string | undefined; /** Picking an option closes the listbox. Defaults to !multiple; can override the default. @default !multiple */ closeOnSelect?: boolean | undefined; /** Whether the listbox popup is open for controlled usage; pair with onOpenChange. @default undefined — uncontrolled. */ open?: boolean | undefined; /** Initial open state for uncontrolled usage. @default false */ defaultOpen?: boolean | undefined; /** Fires with new open state whenever it changes, controlled or not. @default undefined — no-op if omitted. */ onOpenChange?: ((open: boolean) => void) | undefined; /** Fires with new open state once animation finishes (after SelectContent's enter/exit ends). @default undefined — no-op if omitted. */ onOpenChangeComplete?: ((open: boolean) => void) | undefined; /** Disables the trigger and prevents opening. Distinct from readOnly, which keeps focusable and submits. @default false */ disabled?: boolean | undefined; /** Unchangeable value. Trigger keeps tab stop and focus, carries aria-readonly="true" and data-readonly, submits the value. @default false */ readOnly?: boolean | undefined; /** Marks hidden form controls as required for constraint validation (only meaningful when name is set). @default false */ required?: boolean | undefined; /** Marks select as invalid, exposed as aria-invalid/data-invalid on SelectTrigger. @default false */ invalid?: boolean | undefined; /** Visually-hidden native form control kept in sync with value for submission, reset, and validation. @default undefined — no hidden control. */ name?: string | undefined; /** Form id the select submits into, forwarded to every hidden control so select can live outside the form it belongs to. @default undefined — submit into nearest ancestor form. */ form?: string | undefined; /** Browser autofill hint, forwarded to the first mirrored hidden control — the same one that answers for the whole set's validity. @default undefined — no autofill hint. */ autoComplete?: string | undefined; /** * Corner rung the skin rounds the control by, fed to the derived `SelectTrigger`. * @default undefined — the skin's own default corner. */ shape?: 'rounded' | 'square' | 'pill' | (string & {}) | undefined; /** * Size rung the skin scales the control by, fed to the derived `SelectTrigger` and every derived Tag. * @default undefined — the skin's own default size. */ size?: 'sm' | 'lg' | (string & {}) | undefined; /** * Severity rung the skin colours the chips by, fed to every derived Tag. * @default undefined — the skin's own neutral styling. */ status?: 'info' | 'success' | 'warning' | 'danger' | (string & {}) | undefined; /** * Spacing rung the skin packs the tag strip by, fed to the derived `SelectTagList`. * @default undefined — the skin's own default spacing. */ density?: 'comfortable' | 'compact' | (string & {}) | undefined; /** * Matches the listbox width to the trigger, fed to the derived `SelectContent`. * @default undefined — the panel sizes to its content. */ matchWidth?: boolean | undefined; /** * Accessible name for the derived Trigger or filter Input, and the tag strip beside it. * @default undefined — the field must be named some other way. */ 'aria-label'?: string | undefined; /** * DOM node the derived Portal renders the overlay into, so it inherits that subtree's theme and stacking context (e.g. a `[data-skdx-theme]` wrapper). * @default undefined — `document.body`. */ portalContainer?: Element | DocumentFragment | null | undefined; } /** Props for {@link SelectTrigger}, the button that opens the listbox and displays the current value. */ export interface SelectTriggerProps extends PrimitivePropsWithRef<'button'> { /** * Corner rung the skin rounds the trigger by, mirrored to `data-shape`. * @default undefined — the skin's own default corner. */ shape?: 'rounded' | 'square' | 'pill' | (string & {}) | undefined; /** * Size rung the skin scales the trigger by, mirrored to `data-size`. * @default undefined — the skin's own default size. */ size?: 'sm' | 'lg' | (string & {}) | undefined; } /** Props for {@link SelectInput}, the filter text field that stands in for the trigger while `filterable`. */ export interface SelectInputProps extends Omit, 'value' | 'defaultValue'> { } /** Props for {@link SelectTagList}, the strip holding one removable Tag per selected value. */ export interface SelectTagListProps extends Omit { } /** Props for {@link SelectTag}, one selected value inside the tag strip. */ export interface SelectTagProps extends Omit { /** The selected value this tag represents; deselects it when remove is pressed. */ value: string; } /** Props for {@link SelectTagRemove}, the button deselecting its tag's value. */ export interface SelectTagRemoveProps extends PrimitivePropsWithRef<'button'> { } /** Props for {@link SelectClear}, the button emptying selection and draft text in one press. */ export interface SelectClearProps extends PrimitivePropsWithRef<'button'> { } /** Props for {@link SelectAll}, the listbox row toggling every enabled option at once. */ export interface SelectAllProps extends PrimitivePropsWithRef<'div'> { } /** Props for {@link SelectEmpty}, the message shown when the filter leaves no option. */ export interface SelectEmptyProps extends PrimitivePropsWithRef<'div'> { } /** Props for SelectValue: renders the selected option's label or placeholder inside the trigger. */ export interface SelectValueProps extends PrimitivePropsWithRef<'span'> { /** Shown while no option is selected. @default undefined — renders nothing. */ placeholder?: React.ReactNode | undefined; } /** Props for SelectIcon, a decorative indicator inside the trigger (defaults to ▾). */ export interface SelectIconProps extends PrimitivePropsWithRef<'span'> { } /** Props for SelectPortal: teleports its content to document.body while the listbox is open. */ export interface SelectPortalProps extends PortalProps { /** Mount SelectContent even while closed, for animation exit transitions. @default false */ forceMount?: boolean | undefined; } /** Props for SelectContent: the listbox popup with side='bottom', align='start', sideOffset=4 defaults. */ export interface SelectContentProps extends PopperContentProps { /** Mount listbox even while closed, for animation exit transitions. @default false */ forceMount?: boolean | undefined; /** Fires on Escape while open; preventDefault() keeps it open. @default undefined — no-op if omitted. */ onEscapeKeyDown?: ((event: KeyboardEvent) => void) | undefined; /** Fires on pointer-down outside listbox and trigger; preventDefault() keeps it open. @default undefined — no-op if omitted. */ onPointerDownOutside?: ((event: PointerEvent) => void) | undefined; /** Fires when focus moves outside (cancelable focusin stand-in); preventDefault() keeps it open. @default undefined — no-op if omitted. */ onFocusOutside?: ((event: CustomEvent) => void) | undefined; /** Fires for outside interaction before specific handler; preventDefault() vetoes both paths. @default undefined — no-op if omitted. */ onInteractOutside?: ((event: InteractOutsideEvent) => void) | undefined; /** Fires before focus returns to trigger as listbox closes; preventDefault() suppresses return. @default undefined — no-op if omitted. */ onCloseAutoFocus?: ((event: AutoFocusEvent) => void) | undefined; /** * Matches this panel width to its trigger, mirrored as a bare `data-match-width`. * @default undefined — the panel sizes to its content. */ matchWidth?: boolean | undefined; } /** Props for {@link SelectViewport}, the scrollable container for `SelectItem`s inside `SelectContent`. */ export interface SelectViewportProps extends PrimitivePropsWithRef<'div'> { } /** @internal Shared state distributed from SelectItem to its text/indicator via context. */ export interface SelectItemContextValue { value: string; selected: boolean; registerText: (text: string) => void; } /** Props for a single SelectItem option within SelectContent. */ export interface SelectItemProps extends PrimitivePropsWithRef<'div'> { /** Identifier committed to the root's value when chosen. */ value: string; /** Excludes from selection and keyboard navigation. @default false */ disabled?: boolean | undefined; /** Text for Trigger label and typeahead, published as data-text-value; falls back to rendered text. @default undefined — falls back to rendered text. */ textValue?: string | undefined; } /** Props for SelectItemText: an Item's label content, captured so Value can render it inside Trigger. */ export interface SelectItemTextProps extends PrimitivePropsWithRef<'span'> { } /** Props for SelectItemIndicator: selected-state glyph inside an Item, rendered only while selected. */ export interface SelectItemIndicatorProps extends PrimitivePropsWithRef<'span'> { /** Mount this indicator even while its item isn't selected, for animation exit transitions. @default false */ forceMount?: boolean | undefined; } /** Props for SelectGroup, a labeled group of related SelectItems. */ export interface SelectGroupProps extends PrimitivePropsWithRef<'div'> { } /** What SelectGroup publishes to its SelectLabel: the id the group points aria-labelledby at. */ export interface SelectGroupContextValue { /** The id the group's aria-labelledby names (null when SelectLabel rendered outside any group). */ labelId: string | null; } /** Props for SelectLabel: non-interactive heading for Group, providing its accessible name. */ export interface SelectLabelProps extends PrimitivePropsWithRef<'div'> { } /** Props for SelectSeparator: visual divider between Items or Groups inside Content, renders aria-hidden element. */ export interface SelectSeparatorProps extends PrimitivePropsWithRef<'div'> { } /** What Select's forwarded ref carries: constraint-validation methods only (root renders no DOM besides mirrored inputs). */ export type SelectHandle = FormValidityHandle;