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'; import type { OptionEntry } from '../utils/useOptionCollection.js'; /** One suggestion for the {@link AutocompleteProps.options} recipe prop — a flat entry rendered as an `AutocompleteItem`. */ export interface AutocompleteOption { /** Value committed to the field when this suggestion is chosen. */ value: string; /** * Text matched by `filter` and written to the Input on selection. * @default undefined — falls back to `value`. */ label?: string | undefined; /** * Excludes this suggestion from selection and pointer highlight. * @default false */ disabled?: boolean | undefined; } /** A labeled group of {@link AutocompleteOption}s for {@link AutocompleteProps.options} — grouping is data, never a wrapper component. */ export interface AutocompleteOptionGroup { /** Heading rendered as an `AutocompleteGroupLabel` above this group's suggestions. */ label: string; /** The suggestions belonging to this group. */ options: AutocompleteOption[]; } /** One entry of {@link AutocompleteProps.options}: a flat suggestion, or a labeled group of them. */ export type AutocompleteOptionOrGroup = AutocompleteOption | AutocompleteOptionGroup; /** * Filters and ranks Items against the draft text: `'manual'` filters nothing, * a predicate filters, and a number ranks (0 excludes, positive keeps and * orders by score descending). */ export type AutocompleteFilter = 'manual' | ((itemText: string, inputValue: string) => boolean | number); /** What drove an {@link Autocomplete} value change. */ export type AutocompleteValueChangeReason = 'keyboard' | 'pointer' | 'programmatic'; /** * The payload `onValueChange` receives; `created` is `true` when the commit * was the draft text itself rather than an existing suggestion. */ export interface AutocompleteValueChangeDetail { /** The new committed value. */ value: string; /** Previous value, or `null` when there was none. */ previousValue: string | null; /** What drove the change. */ reason: AutocompleteValueChangeReason; /** True when the commit was the draft text rather than a suggestion. */ created: boolean; } /** @internal Shared state distributed from {@link Autocomplete} to its parts via context. */ export interface AutocompleteContextValue { inputRef: React.RefObject; /** Excluded from outside-interaction detection so its click can't close the popup it just opened. */ triggerRef: React.RefObject; contentRef: React.RefObject; /** Id of `AutocompleteList`, referenced by the input's `aria-controls`. */ listId: string; /** The committed value, which need not match any suggestion. */ value: string; /** Whether an option value is the committed one, for `aria-selected`. */ isSelected: (value: string) => boolean; inputValue: string; onInputValueChange: (value: string) => void; open: boolean; onOpenChange: (open: boolean) => void; activeId: string | null; /** CSS `order` for score-ranked Items when `filter` ranks; null leaves order untouched. */ itemOrder: ReadonlyMap | null; setActiveId: (id: string | null) => void; moveActive: (direction: 1 | -1) => string | null; /** Moves the highlight to the first/last Item, only while open (Home/End). */ moveActiveToEdge: (edge: 'first' | 'last') => string | null; openWithHighlight: (edge: 'first' | 'last') => void; itemCount: number; registerItem: (id: string, entry: OptionEntry) => void; unregisterItem: (id: string) => void; getItem: (id: string) => OptionEntry | undefined; /** Whether an item's text survives the current `filter` and draft text. */ matchesFilter: (itemText: string) => boolean; /** Commits a suggestion — writes its text and value, closes, and tags the change with `reason`. */ commitItem: (value: string, text: string, reason: AutocompleteValueChangeReason) => void; /** Commits the raw draft (Tab, blur) or restores the last committed text when `allowCustomValue` is off. */ commitInputValue: (reason: AutocompleteValueChangeReason) => void; /** Closes and restores the last committed text (Escape). */ revert: () => void; /** Empties draft, value, and the remembered committed text so blur can't restore what was cleared. */ clear: () => void; allowCustomValue: boolean; openOnFocus: boolean; disabled: boolean; readOnly: boolean; invalid: boolean; /** Whether suggestions are in flight, exposed as `aria-busy`/`data-loading`, suppressing count announcement. */ loading: boolean; /** Form field name, stamped on the real `` the Input part renders. */ name: string | undefined; /** Id of the `
` the field submits into, stamped on the real `` the Input part renders. */ form: string | undefined; } /** * Constraint-validation handle for {@link Autocomplete}'s ref; delegates to * the Input's real ``, or reports valid when none is mounted. */ export type AutocompleteHandle = FormValidityHandle; /** * Props for the {@link Autocomplete} root. Standard attributes land on * the derived Input (`role="combobox"`). */ export interface AutocompleteProps extends PassThroughProps { /** * Native datalist mode: browser-owned suggestions, no popover; options supply values. * @default 'popover' */ mode?: 'popover' | 'list' | undefined; /** * The `AutocompleteInput`/`AutocompletePortal` parts — `Autocomplete` itself renders no DOM. * @default undefined — renders nothing without children. */ children?: React.ReactNode | undefined; /** * The committed value, for controlled usage, distinct from the draft `inputValue`. * @default undefined — uncontrolled, tracks its own committed value internally. */ value?: string | undefined; /** * Initial committed value; also seeds `defaultInputValue`. * @default '' — nothing committed, so blurring an untouched field commits nothing. */ defaultValue?: string | undefined; /** * Fires with the new committed value on Enter, a suggestion click, Tab, or blur — never on a keystroke. * @default undefined — no-op if omitted. */ onValueChange?: ((detail: AutocompleteValueChangeDetail) => void) | undefined; /** * Controlled draft text; with no `filter` set, this prop is the filtering mechanism itself. * @default undefined — uncontrolled, tracks its own draft text internally. */ inputValue?: string | undefined; /** * Initial draft text, falls back to defaultValue if unset. * @default defaultValue — the field opens showing whatever value it starts committed to. */ defaultInputValue?: string | undefined; /** * Fires with the new draft text on every keystroke, commit, or revert. * @default undefined — no-op if omitted. */ onInputValueChange?: ((inputValue: string) => void) | undefined; /** * Whether the suggestion popup is open, for controlled usage. * @default undefined — uncontrolled, tracks its own open state internally. */ open?: boolean | undefined; /** * Initial open state; popup waits for keystroke or trigger if false. * @default false */ defaultOpen?: boolean | undefined; /** * Fires with the new open state whenever it changes. * @default undefined — no-op if omitted. */ onOpenChange?: ((open: boolean) => void) | undefined; /** * Fires after the open-state animation completes; use for work that must not race the transition. * @default undefined — no-op if omitted. */ onOpenChangeComplete?: ((open: boolean) => void) | undefined; /** * Lets the committed value be text outside the suggestion list; off, an unmatched draft reverts to the last committed text on blur. * @default true */ allowCustomValue?: boolean | undefined; /** * Opens the suggestion popup on focus instead of waiting for the first keystroke. * @default false */ openOnFocus?: boolean | undefined; /** * Keeps the first suggestion highlighted as the filtered list changes while open, so Enter always has a target. * @default false */ autoHighlight?: boolean | undefined; /** * Wraps arrow navigation around list ends; set `false` to clamp. Home/End always jump to an end regardless. * @default true */ loop?: boolean | undefined; /** * Filters and ranks Items: `'manual'` filters nothing (default), a predicate filters and ranks by score. * @default undefined — with `options`, a case-insensitive substring match on each option's text; with hand-composed Items, nothing is filtered and the consumer renders only the matches. */ filter?: AutocompleteFilter | undefined; /** * Blocks all interaction; the input is natively disabled and the popup can't open. * @default false */ disabled?: boolean | undefined; /** * Shows the committed text but blocks changes, while remaining focusable, unlike `disabled`. * @default false */ readOnly?: boolean | undefined; /** * Marks the field as invalid, exposed as `aria-invalid`/`data-invalid` on the Input. * @default false */ invalid?: boolean | undefined; /** * Marks suggestions as in flight; suppresses the suggestion-count announcement that would race an in-progress fetch. * @default false */ loading?: boolean | undefined; /** * Form field name; needed on the root since a shadow-root input can't associate with the ancestor form. * @default undefined — the field submits nothing at all. */ name?: string | undefined; /** * Id of the `` the field submits into; requires `name` to have any effect. * @default undefined — associates with the nearest ancestor ``. */ form?: string | undefined; /** * Formats the polite announcement when the rendered suggestion count changes while open; return an empty string to announce nothing. * @default defaultGetSuggestionCountLabel — e.g. `"3 suggestions available"` */ getResultCountLabel?: ((count: number) => string) | undefined; /** * Flat or grouped suggestion data; when present, derives the Input, Portal, and List instead of using children. * @default undefined — nothing is derived; compose the parts yourself via `children`. */ options?: AutocompleteOptionOrGroup[] | undefined; /** * Customises how a derived suggestion renders; only read while `options` derives the anatomy. * @default undefined — renders `option.label ?? option.value`. */ renderOption?: ((option: AutocompleteOption, state: { selected: boolean; }) => React.ReactNode) | undefined; /** * Placeholder for the derived Input; only read while `options` derives the anatomy. * @default undefined */ placeholder?: string | undefined; /** * Whether the derived anatomy renders a Clear button that empties the draft text and the committed value. * @default false — no Clear button is derived. */ clearable?: boolean | undefined; /** * Accessible name for the derived input and list; only read while `options` derives the anatomy. * @default undefined — falls back to `placeholder`; with neither, no attribute is emitted. */ '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 AutocompleteInput}, the free-text field that owns real DOM focus for the whole interaction. */ export interface AutocompleteInputProps extends Omit, 'value' | 'defaultValue'> { } /** Props for {@link AutocompleteTrigger}, the optional button that toggles the suggestion popup. */ export interface AutocompleteTriggerProps extends PrimitivePropsWithRef<'button'> { } /** Props for {@link AutocompletePortal}, which teleports its content to `document.body` while the popup is open. */ export interface AutocompletePortalProps extends PortalProps { /** * Render (and mount `AutocompleteContent`) even while closed, instead of * unmounting. Useful for animation libraries that need the outgoing content * present in the DOM during an exit transition. * @default false */ forceMount?: boolean | undefined; } /** * Props for {@link AutocompleteContent}, extending {@link PopperContentProps} * for floating-ui positioning; defaults `side` to `'bottom'`, `align` to * `'start'`, and `sideOffset` to `4`. */ export interface AutocompleteContentProps extends PopperContentProps { /** * Keep this popup mounted even while closed, instead of unmounting it. * @default false */ forceMount?: boolean | undefined; /** * Fires on Escape while open; call `event.preventDefault()` to keep the popup open and the draft text unchanged. * @default undefined — no-op if omitted. */ onEscapeKeyDown?: ((event: KeyboardEvent) => void) | undefined; /** * Fires on pointer-down outside the popup and the input; call `event.preventDefault()` to keep the popup open. * @default undefined — no-op if omitted. */ onPointerDownOutside?: ((event: PointerEvent) => void) | undefined; /** * Fires when focus leaves the popup and the input, via a cancelable stand-in since native `focusin` cannot itself be canceled. * @default undefined — no-op if omitted. */ onFocusOutside?: ((event: FocusOutsideEvent) => void) | undefined; /** * Fires before either outside handler above, with the same event, so one `event.preventDefault()` here vetoes dismissal from either trigger. * @default undefined — no-op if omitted. */ onInteractOutside?: ((event: InteractOutsideEvent) => void) | undefined; } /** Props for {@link AutocompleteList}, the `role="listbox"` the suggestions live in. */ export interface AutocompleteListProps extends PrimitivePropsWithRef<'div'> { } /** Props for a single {@link AutocompleteItem} suggestion within `AutocompleteList`. */ export interface AutocompleteItemProps extends PrimitivePropsWithRef<'div'> { /** Value committed to the autocomplete root when this suggestion is chosen. */ value: string; /** * Excludes this suggestion from selection and pointer highlight. * @default false */ disabled?: boolean | undefined; /** * Text matched by `filter` and written to the Input on commit; needed only when the item renders more than its label. * @default undefined — falls back to the item's rendered text content. */ textValue?: string | undefined; } /** Props for {@link AutocompleteEmpty}, shown in place of the suggestion list when no items are rendered. */ export interface AutocompleteEmptyProps extends PrimitivePropsWithRef<'div'> { } /** Props for {@link AutocompleteClear}, the optional button that empties the draft text and the committed value. */ export interface AutocompleteClearProps extends PrimitivePropsWithRef<'button'> { } /** Props for {@link AutocompleteGroup}, a named section of related suggestions inside `AutocompleteList`. */ export interface AutocompleteGroupProps extends PrimitivePropsWithRef<'div'> { } /** Props for {@link AutocompleteGroupLabel}, the heading that names its enclosing `AutocompleteGroup`. */ export interface AutocompleteGroupLabelProps extends PrimitivePropsWithRef<'div'> { } /** @internal What an {@link AutocompleteGroup} publishes to the `AutocompleteGroupLabel` inside it. */ export interface AutocompleteGroupContextValue { /** The id the group points `aria-labelledby` at, which its label claims. */ labelId: string; }