import type * as React from 'react'; import type { FormValidityHandle } from '../utils/formControl.js'; import type { PassThroughProps } from '../utils/passThrough.js'; import type { PopperContentProps } from '../utils/popper.js'; import type { PortalProps } from '../utils/portal.js'; import type { PrimitivePropsWithRef } from '../utils/primitive.js'; import type { FocusOutsideEvent, InteractOutsideEvent } from '../utils/useDismiss.js'; /** Flat option for ComboboxProps.options recipe prop. */ export interface ComboboxOption { /** Value committed when option is chosen. */ value: string; /** * Text shown for option; matched by filter, written back on selection. * @default undefined */ label?: string | undefined; /** * Excludes option from selection and pointer highlight. * @default false */ disabled?: boolean | undefined; } /** Labeled group of options for ComboboxProps.options. */ export interface ComboboxOptionGroup { /** Heading for the group. */ label: string; /** Options in this group. */ options: ComboboxOption[]; } /** One entry of {@link ComboboxProps.options}: a flat option, or a labeled group of them. */ export type ComboboxOptionOrGroup = ComboboxOption | ComboboxOptionGroup; /** Filtering strategy: 'manual' for consumer control or predicate for component filtering; numbers rank. */ export type ComboboxFilter = 'manual' | ((itemText: string, inputValue: string) => boolean | number); /** What drove a {@link Combobox} value change — the shared three-value vocabulary from `@skdx/shared`'s `change-reason` contract. */ export type ComboboxValueChangeReason = 'keyboard' | 'pointer' | 'programmatic'; /** Payload for the `onValueChange` event. */ export interface ComboboxValueChangeDetail { /** New selection (always array). */ value: string[]; /** Previous selection, empty when there was none. */ previousValue: string[]; /** What drove the change. */ reason: ComboboxValueChangeReason; /** First selected value, or null when nothing is. */ selectedValue: string | null; } /** Gesture to open listbox: 'type', 'click', or 'focus'. */ export type ComboboxOpenOn = 'type' | 'click' | 'focus'; /** What moved highlight: 'keyboard', 'pointer', or 'auto'. */ export type ComboboxHighlightReason = 'keyboard' | 'pointer' | 'auto'; /** Payload for the `onHighlightChange` event. */ export interface ComboboxHighlightChangeDetail { /** Option value now highlighted, or null if none. */ value: string | null; /** Previously highlighted option value, or null. */ previousValue: string | null; /** What moved it. */ reason: ComboboxHighlightReason; } /** @internal What one registered {@link ComboboxItem} contributes to the root's collection. */ export interface ComboboxItemEntry { /** Identifier committed to the root's value when this option is chosen. */ value: string; /** The item's `textValue`, or its rendered text — matched by `filter` and written back into the input. */ text: string; } /** @internal Shared state distributed from {@link Combobox} to its parts via context. */ export interface ComboboxContextValue { inputRef: React.RefObject; /** Optional trigger button ref for dismissal layer. */ triggerRef: React.RefObject; contentRef: React.RefObject; contentId: string; /** Whether more than one option can be selected. */ multiple: boolean; /** Every selected value, normalised to an array whether or not `multiple`. */ selectedValues: string[]; /** Whether one option value is currently selected. */ isSelected: (value: string) => boolean; /** Commits option: replaces selection or toggles in multi-select mode. */ selectItem: (value: string, text: string, reason: ComboboxValueChangeReason) => void; /** Empties selection and draft text. */ clear: () => void; inputValue: string; onInputValueChange: (value: string) => void; open: boolean; onOpenChange: (open: boolean) => void; activeId: string | null; /** Rank each currently-visible item's CSS `order` should carry when `filter` also scores it; `null` leaves order untouched. */ itemOrder: ReadonlyMap | null; /** Moves virtual highlight and tags with reason. */ setActiveId: (id: string | null, reason?: ComboboxHighlightReason) => void; /** Opens listbox and highlights first or last option. */ openWithHighlight: (edge: 'first' | 'last') => void; /** Whether arrow navigation wraps around the ends of the option list. */ loop: boolean; /** Which pointer/focus gesture opens the listbox, on top of the keyboard ones. */ openOn: ComboboxOpenOn; /** Replaces draft text with selected option's text when focus lost and `allowCustomValue` is off. */ reconcileDraftText: () => void; itemCount: number; registerItem: (id: string, entry: ComboboxItemEntry) => void; unregisterItem: (id: string) => void; /** Gets registered entry for option id. */ getItem: (id: string) => ComboboxItemEntry | undefined; /** Checks if item text matches filter and draft. */ matchesFilter: (itemText: string) => boolean; invalid: boolean; readOnly: boolean; disabled: boolean; loading: boolean; allowCustomValue: boolean; required: boolean; } /** Forwarded ref for constraint-validation methods. */ export type ComboboxHandle = FormValidityHandle; /** * Props for the {@link Combobox} root. Standard attributes ({@link * PassThroughProps}) land on the derived `ComboboxInput` (the `role="combobox"`). */ export interface ComboboxProps extends PassThroughProps { /** * The `ComboboxInput` and `ComboboxPortal` parts children to render. * @default undefined */ children?: React.ReactNode | undefined; /** * Selected value for controlled usage; pair with `onValueChange`. * @default undefined */ value?: string | string[] | undefined; /** * Initially selected value for uncontrolled usage. * @default undefined */ defaultValue?: string | string[] | undefined; /** * Fires when the selected value changes in the combobox. * @default undefined */ onValueChange?: ((detail: ComboboxValueChangeDetail) => void) | undefined; /** * Allows multiple options selected; toggles instead of replacing. * @default false */ multiple?: boolean | undefined; /** * Lets Enter commit unmatched draft text as a value ("creatable"). * @default false */ allowCustomValue?: boolean | undefined; /** * How items are filtered; 'manual' filters nothing, predicate filters and ranks. * @default 'manual' */ filter?: ComboboxFilter | undefined; /** * Keeps first option highlighted when list changes while open. * @default false */ autoHighlight?: boolean | undefined; /** * Wraps arrow navigation around list ends. * @default true */ loop?: boolean | undefined; /** * Fires when virtual focus changes; read-only, no controlled counterpart. * @default undefined */ onHighlightChange?: ((detail: ComboboxHighlightChangeDetail) => void) | undefined; /** * Which pointer/focus gesture opens listbox on top of keyboard ones. * @default 'type' */ openOn?: ComboboxOpenOn | undefined; /** * Marks results as in flight: aria-busy and data-loading. * @default false */ loading?: boolean | undefined; /** * Draft text for controlled usage; pair with `onInputValueChange`. * @default undefined */ inputValue?: string | undefined; /** * Initial draft text for uncontrolled usage. * @default '' */ defaultInputValue?: string | undefined; /** * Fires with new draft text on keystroke or when selection updates it. * @default undefined */ onInputValueChange?: ((value: string) => void) | undefined; /** * Whether listbox is open for controlled usage; pair with `onOpenChange`. * @default undefined */ open?: boolean | undefined; /** * Initial open state for uncontrolled usage. * @default false */ defaultOpen?: boolean | undefined; /** * Fires with the new open state when it changes. * @default undefined */ onOpenChange?: ((open: boolean) => void) | undefined; /** * Fires once the open/close animation completes. * @default undefined */ onOpenChangeComplete?: ((open: boolean) => void) | undefined; /** * Marks combobox invalid: aria-invalid and data-invalid. * @default false */ invalid?: boolean | undefined; /** * Makes input read-only: aria-readonly and data-readonly. * @default false */ readOnly?: boolean | undefined; /** * Disables combobox: disabled attribute and data-disabled. * @default false */ disabled?: boolean | undefined; /** * Formats the match count announcement for screen readers. * @default defaultGetResultCountLabel */ getResultCountLabel?: ((count: number) => string) | undefined; /** * Form field name; mirrors selected value for form submission. * @default undefined */ name?: string | undefined; /** * Id of the form this combobox submits into. * @default undefined */ form?: string | undefined; /** * Marks combobox required; valueMissing when nothing selected. * @default false */ required?: boolean | undefined; /** * Option data (flat or grouped) to render automatically. * @default undefined */ options?: ComboboxOptionOrGroup[] | undefined; /** * Custom rendering function for each option in the options list. * @default undefined */ renderOption?: ((option: ComboboxOption, state: { selected: boolean; }) => React.ReactNode) | undefined; /** * Placeholder text shown in the input when no value is selected. * @default undefined */ placeholder?: string | undefined; /** * Accessible name for derived input and listbox. * @default undefined */ 'aria-label'?: string | undefined; /** * Corner rung the skin rounds the field by, fed to the derived `ComboboxInput`. * @default undefined — the skin's own default corner. */ shape?: 'rounded' | 'square' | (string & {}) | undefined; /** * Size rung the skin scales the field by, fed to the derived `ComboboxInput`. * @default undefined — the skin's own default size. */ size?: 'md' | 'sm' | 'lg' | (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 ComboboxInput}, the text field with real DOM focus. */ export interface ComboboxInputProps extends Omit, 'value' | 'defaultValue' | 'size'> { /** * Corner rung the skin rounds the input by, mirrored to `data-shape`. * @default undefined — the skin's own default corner. */ shape?: 'rounded' | 'square' | (string & {}) | undefined; /** * Size rung the skin scales the input by, mirrored to `data-size`. * @default undefined — the skin's own default size. */ size?: 'md' | 'sm' | 'lg' | (string & {}) | undefined; } /** Props for ComboboxPortal, which teleports content to document.body. */ export interface ComboboxPortalProps extends PortalProps { /** * Mounts listbox content to the DOM even when closed, enabling exit animations. * @default false */ forceMount?: boolean | undefined; } /** Props for the ComboboxContent listbox popup. */ export interface ComboboxContentProps extends PopperContentProps { /** * Mounts the listbox DOM even when closed, enabling exit animations. * @default false */ forceMount?: boolean | undefined; /** * Fires when the Escape key is pressed while the listbox is open. * @default undefined */ onEscapeKeyDown?: ((event: KeyboardEvent) => void) | undefined; /** * Fires on pointer-down outside listbox and input. * @default undefined */ onPointerDownOutside?: ((event: PointerEvent) => void) | undefined; /** * Fires when focus moves outside listbox and input. * @default undefined */ onFocusOutside?: ((event: FocusOutsideEvent) => void) | undefined; /** * Fires when any interaction occurs outside the listbox and input. * @default undefined */ onInteractOutside?: ((event: InteractOutsideEvent) => void) | undefined; } /** Props for single ComboboxItem option. */ export interface ComboboxItemProps extends PrimitivePropsWithRef<'div'> { /** Value committed when option is chosen. */ value: string; /** * Excludes option from selection and pointer highlight. * @default false */ disabled?: boolean | undefined; /** * Text for filter matching and selection write-back to input. * @default undefined */ textValue?: string | undefined; } /** Props for ComboboxEmpty, shown when no items are rendered. */ export interface ComboboxEmptyProps extends PrimitivePropsWithRef<'div'> { } /** Props for ComboboxTrigger, optional button that toggles listbox. */ export interface ComboboxTriggerProps extends PrimitivePropsWithRef<'button'> { } /** Props for ComboboxClear, optional button that empties selection. */ export interface ComboboxClearProps extends PrimitivePropsWithRef<'button'> { } /** Props for ComboboxGroup, named section of related options. */ export interface ComboboxGroupProps extends PrimitivePropsWithRef<'div'> { } /** Props for ComboboxGroupLabel, heading for enclosing ComboboxGroup. */ export interface ComboboxGroupLabelProps extends PrimitivePropsWithRef<'div'> { } /** @internal What ComboboxGroup publishes to ComboboxGroupLabel inside it. */ export interface ComboboxGroupContextValue { /** Id group points aria-labelledby at. */ labelId: string; }