@cfxlabsinc/b2b-services
    Preparing search index...
    type InfiniteComboboxSingleProps<T> = {
        bounded?: boolean;
        disabled?: boolean;
        extraItem?: (query: string, noMatches: boolean) => ComboboxExtraItem | null;
        facets?: readonly ComboboxFacet[];
        fetchPage: EntitySelectorFetchPage<T>;
        getId: (item: T) => string;
        getLabel: (item: T) => string;
        getSublabel?: (item: T) => string | undefined;
        hideSearch?: boolean;
        initialPage?: EntitySelectorPage<T> | null;
        isOptionDisabled?: (item: T) => boolean;
        noun: string;
        onClose?: () => void;
        onSelect: (item: T) => void;
        placeholder: string;
        queryKey?: ReadonlyArray<unknown>;
        renderEmpty?: (query: string) => ReactNode;
        renderFooter?: (query: string) => ReactNode;
        renderRow?: (item: T) => ReactNode;
        searchPlaceholder: string;
        selectedSeed?: T | null;
        sortOptions?: readonly EntitySelectorSortOption[];
        value: string | null;
        valueLabel?: string;
        valueSublabel?: string;
    }

    Type Parameters

    • T
    Index
    bounded?: boolean

    The option source is a client-held bounded set (staticOptionsPage) rather than a server search. Drops the keystroke debounce, which exists only to spare the server and here just adds lag, and trims the server-search phrasing out of the no-match copy. Set by StaticComboboxSingle.

    disabled?: boolean
    extraItem?: (query: string, noMatches: boolean) => ComboboxExtraItem | null

    Query-driven action rendered as the FIRST CommandItem, so it owns cmdk's Enter (cmdk re-highlights the first item on every input change) and joins arrow-key navigation. id must be distinct from every row id. noMatches lets a last-resort affordance hold back while real rows are on screen, so it never steals Enter from the option the operator was actually after.

    facets?: readonly ComboboxFacet[]

    Drilldown facets rendered below the search box (multidimensional narrow); selected values thread to fetchPage as facets[key]. Omit for a plain single-select selector.

    Fetches one page of options; { ok: false } becomes the error state.

    getId: (item: T) => string
    getLabel: (item: T) => string
    getSublabel?: (item: T) => string | undefined

    Optional monospaced secondary label (e.g. an id) shown on trigger and rows.

    hideSearch?: boolean

    Drop the search box — for a bounded source short enough to read at a glance, where a filter over four rows is furniture rather than an affordance. StaticComboboxSingle sets it from the option count; prefer that over passing this by hand.

    cmdk keeps working without its input: the keydown handler lives on the Command root, so the root takes tabIndex={0} and the popover focuses it on open. Never set this alongside facets — the drilldown bar assumes a query above it.

    initialPage?: EntitySelectorPage<T> | null

    Server-rendered first page; see useInfiniteOptions. Default view only.

    isOptionDisabled?: (item: T) => boolean

    Marks a row unselectable while keeping it listed — for an option that exists but can't be used here (a bank account with no credentials in this environment). Prefer this over filtering: a missing row reads as "no such account", a disabled one as "this one, but not yet".

    noun: string

    Plural noun, e.g. "companies" — used in error and empty copy.

    onClose?: () => void

    Fires when the popover closes. Form callers pass field.onBlur here rather than onto the trigger: opening the popover blurs the trigger, so a trigger-level blur marks an onTouched field touched — and renders its "required" error — while the operator is still choosing.

    onSelect: (item: T) => void
    placeholder: string
    queryKey?: ReadonlyArray<unknown>
    renderEmpty?: (query: string) => ReactNode

    Replaces the default empty copy when the result set is empty. Fed the live (undebounced) query so the caller can name what it searched for.

    renderFooter?: (query: string) => ReactNode

    Renders below the list, fed the live (undebounced) search text — an escape hatch for NON-interactive hints. Anything actionable belongs in extraItem (a footer sits outside cmdk's listbox, so keys never reach it).

    renderRow?: (item: T) => ReactNode

    Custom option-row content; falls back to getLabel + getSublabel.

    searchPlaceholder: string
    selectedSeed?: T | null

    Resolved row for the committed value — keeps the trigger label when the value isn't on the current page.

    sortOptions?: readonly EntitySelectorSortOption[]
    value: string | null

    The selected option's id, or null.

    valueLabel?: string

    The committed value's label, for a caller that has the name but not the row — a chip map learned from picks, a server-side id→name lookup. The trigger only ever reads getId / getLabel / getSublabel off a resolved row, so demanding a whole T to render one string forces those callers to fabricate one with filler fields nothing reads. Loses to a real row.

    valueSublabel?: string

    Monospaced second line alongside valueLabel.