import * as React from "react"; import type { RecordPickerProp, SearchSelectOptionProp, SelectOptionGroupProp } from "../../props/components/data-entry.prop.js"; export type { RecordPickerProp, RecordPickerProp as RecordPickerProps }; /** * RecordPicker — ONE control whose SHAPE follows the size of the set behind it (gh#932). * * The report, from a `/human-tester` session on a consumer: "các select mà có khả năng nhiều dữ * liệu mà hiển thị kiểu dropdown có cả nghìn cái thì làm sao chọn được… dưới X người là dropdown, * trên X người là cho cấu hình search condition trong modal". A dropdown is the right control for * eight people and the wrong one for eight hundred, and today the consumer has to decide that by * hand, per screen — which is why the same concept ("pick a person") shipped three different ways * in one app, including a bare text field where a mistyped key failed with a 422 after save. * * ## Why this is a component and not a composition * * By the Framework-Component Test it passes on every count: three unrelated screens need it, the * behaviour is stateful and keyboard/ARIA-bearing rather than arrangement, and the knob that * matters (`threshold`) is a design decision a service sets once. What it does NOT do is invent * anything: under the threshold it renders `Select` — the same component, same props, same panel — * and over it, the dialog body is `Command` with its own filtering off, which is exactly how * `Select` already drives a server-backed list. * * ## The threshold is counted, not guessed * * `count` is the size of the WHOLE set, which a server knows and a page of results does not. With * `options` it defaults to their length. The default 10 is the owner's own number ("dưới 10 thì * dropdown, trên 10 thì mở modal search"), and a service overrides it once. * * ## Selected values survive a page they are not on * * A picker over ten thousand records will routinely hold a value whose row is not in the current * result page — on first render, there IS no result page. `selectedOptions` is how the consumer * hands over the labels it already has, and it is merged ahead of whatever loads later, so a chip * never renders as a raw id and never disappears when the query changes. */ /** * Adapt RecordPicker's cursor contract to Select's page-numbered loader (gh#964). * * `loadOptions` is typed to receive the `nextCursor` the server returned last time — an offset, an * opaque token, whatever the server minted. The inline and dialog shapes pass it straight back. * This adapter used to pass `String(page)` instead, so page 2 reached the server as "2" and a * consumer implementing the type as written read it as offset 2: duplicated and missing rows, with * no error. The server's `nextCursor` was used only as a has-more flag. * * Select asks for pages by number, so the adapter remembers, per query, which cursor fetches which * page: page 1 has none, and page N+1 is whatever page N's response said. A new query starts its * own chain, so one query's cursor can never be sent with another's text — a user typing, * deleting and retyping can make Select ask for page 2 of an earlier query after a later one began. * * The chains live in this closure, so they are discarded whenever `loadOptions` itself changes * (the caller memoises on it): a cursor minted by one data source is meaningless to the next. * * @internal Exported for tests only; the package barrel re-exports RecordPicker by name, not this. */ export declare function selectLoadOptionsFor(loadOptions: RecordPickerProp["loadOptions"]): (({ query, page }: { query: string; page: number; }) => Promise<{ options: SearchSelectOptionProp[]; hasMore: boolean; }>) | undefined; export declare const RecordPicker: React.ForwardRefExoticComponent, "defaultValue" | "onChange" | "value"> & { mode?: "single" | "multiple"; shape?: "auto" | "inline"; value?: string | string[] | null; defaultValue?: string | string[] | null; onValueChange?: (value: string | string[] | null) => void; options?: (SearchSelectOptionProp | SelectOptionGroupProp)[]; loadOptions?: (params: { query: string; filters: Record; cursor?: string; }) => Promise<{ options: SearchSelectOptionProp[]; count?: number; nextCursor?: string; }>; count?: number; threshold?: number; filters?: import("../../props/components/data-entry.prop.js").RecordPickerFilterProp[]; selectedOptions?: SearchSelectOptionProp[]; emptyOption?: { value: string; label: string; }; placeholder?: string; dialogTitle?: string; notFoundContent?: import("../../props/index.js").NotFoundContentProp; loadMoreLabel?: import("../../props/index.js").LabelProp; size?: "xs" | "sm" | "md" | "lg"; "data-field"?: string; } & React.RefAttributes>;