import "./option_list.css"; import type * as React from "react"; import { type ReactNode } from "react"; import { useRender } from "@base-ui/react/use-render"; import { type StyleProps } from "./style_props"; import type { SelectOption, SelectValue, SelectOnValueChange } from "./select"; /** `none`: a visually hidden input owns the keyboard, so arrow keys and * type-to-find work with no field on screen. `internal`: its own search field. */ export type OptionListSearch = { mode: "none"; } | { mode: "internal"; placeholder?: string; }; /** What is in the list, and what picking a row does. */ export interface OptionListModelParams { options: (SelectOption | undefined | false)[]; multi?: MULTI; value?: SelectValue | null; onValueChange?: SelectOnValueChange; /** Multi: commit-on-close. Single: fired (with the picked value) after a pick. */ onClose?: SelectOnValueChange; /** Single-select: a leading row, NAMED, that answers with `null`. */ clearable?: boolean; /** What that row is CALLED (locale default: "None"). */ emptyLabel?: string; /** Dismiss the surrounding surface (Escape, Tab, a single-select pick). */ onRequestClose?: () => void; /** Offer a "create" row when the query matches no option. */ allowCustom?: boolean; customOptionLabel?: (query: string) => string | null; customOptionPlacement?: "top" | "bottom"; /** Commit the typed query as a new value (the create row was picked). */ onCustomCommit?: (rawQuery: string) => void; enableSelectAll?: boolean; /** THE CALLER NARROWS THE SET. Its PRESENCE turns the local filter off: * `options` then IS the answer, and filtering again here would drop rows * matched on something this list cannot see. */ onSearchChange?: (query: string) => void; } export interface OptionListBodyProps extends StyleProps { search: OptionListSearch; testID?: string; /** Rich row content (a colour-dot badge, a member chip). Falls back to the label. */ renderOptionContent?: (option: SelectOption) => ReactNode; /** A single-line subtitle under the label (e.g. a code or company). */ getOptionDescription?: (option: SelectOption) => string | undefined; loading?: boolean; emptyText?: string; /** Multi only: values SOME of the edited things carry, drawn as a mixed tick. * Purely presentational — a mixed row is not in `value`, so pressing selects. */ indeterminateValues?: readonly T[]; accessibilityLabel?: string; selectAllLabel?: string; deselectAllLabel?: string; ref?: React.Ref; render?: useRender.RenderProp; } export interface OptionListModel { /** Spread onto a Base UI `Combobox.Root` — the collection, the selection and * the filter, in Base UI's own vocabulary. */ root: { items: string[]; multiple: boolean; value: string[] | string | null; onValueChange: (next: string[] | string | null) => void; inputValue: string; onInputValueChange: (next: string) => void; itemToStringLabel: (item: string) => string; filter: (item: string, query: string) => boolean; }; /** What the body needs beyond Base UI's own state. */ view: { optionOf: (item: string) => SelectOption | undefined; labelOf: (item: string) => string; isCustom: (item: string) => boolean; selectedOf: (item: string) => boolean; multi: boolean; selectAll: () => void; deselectAll: () => void; showSelectAll: boolean; showDeselectAll: boolean; hasSelection: boolean; itemCount: number; }; /** Escape / an outside dismissal — closes the surface and commits on close. */ requestClose: () => void; /** Whether the LIST announces its own close (`OptionListModelParams.ownsClose`). */ ownsClose: boolean; } /** * The kit's option shape expressed in Base UI's — the collection, the filter and * what a pick MEANS (a create row, a clear row, a multi toggle that stays open). * It holds no keyboard, no highlight and no scrolling: those are Base UI's. A * hook because two hosts share it, inline and in a popup. */ export declare function useOptionListModel(params: OptionListModelParams, locale: { emptyOption: string; }, /** * Who announces a close, and so who fires `onRequestClose` + `onClose`. The * INLINE list says so itself, since Base UI turns its own dismissal off for an * `inline` Combobox; a list inside a POPUP takes it from `onOpenChange` and must * not announce it twice, or a commit-on-close writes twice. */ host: { ownsClose: boolean; }): OptionListModel; /** * The rows, the optional search field and the footers — everything INSIDE a * `Combobox.Root`. `OptionList` hosts it inline; `Select` hosts it in its popup. */ export declare function OptionListBody(props: OptionListBodyProps & { model: OptionListModel; }): React.ReactElement>;