import type { ReactNode } from "react"; import type { LoadState } from "@stapel/core"; import type { CategoryNode } from "../catalog/tree.js"; import type { CategoryLabel } from "../catalog/labels.js"; import type { UseCategoryCatalogOptions } from "../model/queries.js"; /** One selectable option in the picker. */ export interface CategoryOption { readonly node: CategoryNode; readonly label: CategoryLabel; /** Root → this node, for a skin that shows the full path in the option. */ readonly path: readonly CategoryLabel[]; /** `true` when this node has no children — usually the only kind a listing * may be filed under. See `leavesOnly`. */ readonly isLeaf: boolean; } export interface CategoryPickerBag { /** * The options at the current drill level, or the search matches while a * query is typed. `empty` is a real answer here — a leaf category has no * children, and a search can match nothing. */ readonly state: LoadState; /** Where the drill-down currently is: root → open node. */ readonly path: readonly CategoryNode[]; /** The selected node, or `null`. */ readonly selected: CategoryNode | null; /** Blocked when nothing is selected, or when the selection is not a leaf * and `leavesOnly` is on. Carries the REASON — a disabled control that does * not say why is the thing `ActionAvailability` exists to prevent. */ readonly submitBlockedReason: CategoryPickerBlockedReason | null; readonly query: string; setQuery(value: string): void; /** Descend into a node. A leaf selects instead of descending. */ open(node: CategoryNode): void; /** Back up one level. */ up(): void; select(node: CategoryNode | null): void; refetch(): void; } /** Why the picker will not hand a value back yet. */ export type CategoryPickerBlockedReason = "nothing_selected" | "not_a_leaf"; export interface CategoryPickerProps extends UseCategoryCatalogOptions { /** Controlled selection. */ value?: number | null; onChange?: (id: number | null, node: CategoryNode | null) => void; /** * Only leaf categories are a valid answer. Default `true`: a listing filed * under "Electronics" instead of "Electronics › Phones › Used" inherits the * wrong feature set, and the compose form then asks the wrong questions. */ leavesOnly?: boolean; /** Resolve a translation key to a caption, so SEARCH matches what the * person can read. Without it, search matches the key. */ translate?: (key: string) => string; children: (bag: CategoryPickerBag) => ReactNode; } /** * The category chooser of the compose form, headless. * * Two modes over the SAME synced tree: drill-down (roots → children → …) and * a flat search. The search matches the RENDERED caption when a `translate` is * supplied and the raw key otherwise, which is the only honest behaviour for a * catalogue whose names are translation keys (`catalog/labels.ts`): a person * typing "phones" is typing what they see, not `category.phones`. * * Nothing here requests anything — the catalogue is already in memory. */ export declare function CategoryPicker(props: CategoryPickerProps): ReactNode; //# sourceMappingURL=CategoryPicker.d.ts.map