import type { IconProp } from '../icon'; import type { Snippet } from 'svelte'; import type { HTMLInputAttributes } from 'svelte/elements'; export type SelectOption = { label: string; value: T; disabled?: boolean; }; export type SelectOptionGroup = { label: string; /** When set together with `groupHeaderSelectable`, clicking the header emits this value. */ value?: T; options: SelectOption[]; }; /** Discriminated structurally — `'options' in item` distinguishes a group from an option. */ export type SelectItem = SelectOption | SelectOptionGroup; export declare const isSelectGroup: (item: SelectItem) => item is SelectOptionGroup; /** * Flat row passed to the shared listbox. Parent components build this list from their own data * model (sync options + Fuse filter, async server response, multi-select with toggle membership, * etc.) so the listbox stays semantics-agnostic. */ export type SelectRowOption = { kind: 'option'; option: SelectOption; group?: string; indent: boolean; selected: boolean; }; export type SelectRowGroupHeader = { kind: 'group-header'; label: string; /** Pre-computed by parent — header is clickable when consumer enables groupHeaderSelectable and group has a `value`. */ selectable: boolean; selected: boolean; /** * Tri-state for `groupHeader='toggle-all'` + `selectionMode='children-include-parent'`. * In every other configuration this is `'checked'` (= selected) or `'unchecked'`. */ state: 'checked' | 'indeterminate' | 'unchecked'; value?: T; /** The group's child options — used by `selectionMode='children-include-parent'` to cascade parent membership. */ options?: SelectOption[]; }; export type SelectRowCreate = { kind: 'create'; query: string; }; export type SelectRow = SelectRowOption | SelectRowGroupHeader | SelectRowCreate; /** * One advisory row of `InputSuggest`. Suggestions never constrain the typed value — they only * offer a shortcut to a known string. */ export type InputSuggestion = { /** Text substituted into the field when the row is picked — also the row's label. */ value: string; /** Secondary line rendered under the value in a muted tone. */ description?: string; }; /** Creatable mode — passing this object to a select enables the create UI. */ export type SelectCreateOptions = { /** Validates each query, trimmed — the same string the create row shows and `on.create` receives. Only when it returns true does the create UI appear (and the query isn't a duplicate of an existing label). */ canCreate: (query: string) => boolean; /** Replaces the whole localized text in front of the typed query on the create row: `Create "foo"` → `Invite "foo"`, and drops the leading `+` glyph (the label fills the full row width). Not localized by the kit; pass already-translated text. Has no effect on the create-with-parent section, which keeps its own localized wording. */ prefix?: string; }; /** Common (data-source-agnostic) prop surface shared by `Singleselect` (sync) and `SingleselectAsync`. */ export type SingleselectCommonProps = { /** Trigger height preset. @default 'md' */ size?: 'sm' | 'md' | 'lg'; /** Placeholder shown only when nothing is selected and the user is not typing. @default '' */ placeholder?: string; /** Fully disabled — no focus, no interaction, dimmed styling. @default false */ disabled?: boolean; /** Read-only — focusable, value visible, but selection cannot change. @default false */ readonly?: boolean; /** Inert subtree — non-interactive but unstyled (use for "view-only" parents). @default false */ inert?: boolean; /** Visual error state (accent border + aria-invalid). @default false */ error?: boolean; /** Removes the field border; underline indicator still appears on open/error. @default false */ borderless?: boolean; /** * Group-header behavior: * - `'static'` — non-interactive label (default). * - `'value'` — clicking the header emits the group's own `value` (typed `T`); group must define `value`. * @default 'static' */ groupHeader?: 'static' | 'value'; /** Equality comparator for `T`. Required when `T` is an object. @default (a, b) => a === b */ compare?: (a: T, b: T) => boolean; /** Enables creatable mode — a "Create …" row for the current query. See {@link SelectCreateOptions}. */ createOptions?: SelectCreateOptions; /** Leading icon — string SVG source, `{ src, color?, size? }` object, or custom snippet. */ icon?: IconProp; /** Trailing chevron icon override. Same shape as `icon`. */ chevronIcon?: IconProp; /** Custom row renderer. Replaces the option's label cell — receives `{ option }`. */ optionSnippet?: Snippet<[{ option: SelectOption; }]>; /** Custom selection renderer (what's shown in the trigger when collapsed). Same shape as `optionSnippet`. */ selectionSnippet?: Snippet<[{ option: SelectOption; }]>; /** Replaces the entire listbox body — consumer renders rows in any layout (e.g. a grid). Receives the rows produced by the existing filter pipeline + highlight index + the `pickRow`/`hoverRow` callbacks the default rendering uses, so search and selection still work through Singleselect. When this snippet is set, the listbox panel stops imposing a width (no `width`, no `min-width: trigger`), so the panel sizes to its content; `triggerWidth` is forwarded as a hint for consumers who want to opt back into stretching/shrinking to the trigger themselves. */ listbox?: Snippet<[ { rows: SelectRow[]; highlight: number; triggerWidth: number | undefined; on: { pickRow: (row: SelectRow) => void; hoverRow: (index: number) => void; }; } ]>; id?: string; name?: string; autocomplete?: HTMLInputAttributes['autocomplete']; 'aria-label'?: string; 'aria-describedby'?: string; 'aria-required'?: boolean; }; /** Full prop surface of `multiselect-base.svelte`, shared with proxies via `Omit<…> & OwnProps`. */ export type MultiselectBaseProps = { /** Server-side fetcher. Invoked with an empty query on open (initial page) and again — debounced — on every keystroke. */ loadOptions: (query: string) => Promise[]>; /** Debounce window for `loadOptions` while typing. @default 400 */ debounceMs?: number; /** Current selection — array of pre-resolved options so chip labels render before the first fetch lands. */ value: SelectOption[]; /** Trigger height preset. @default 'md' */ size?: 'sm' | 'md' | 'lg'; /** Placeholder shown only when nothing is selected and the user is not typing. @default '' */ placeholder?: string; /** Fully disabled — no focus, no interaction, dimmed styling. @default false */ disabled?: boolean; /** Read-only — value visible, but selection cannot change. @default false */ readonly?: boolean; /** Inert subtree — non-interactive but unstyled. @default false */ inert?: boolean; /** Visual error state (accent border + aria-invalid). @default false */ error?: boolean; /** Removes the field border. @default false */ borderless?: boolean; /** When false, typing never enters filtering mode — the input stays the combobox anchor (focus/ARIA/keyboard) but is read-only. A click/keyboard-only picker. @default true */ searchable?: boolean; /** * Group-header behavior: * - `'static'` — non-interactive label (default). * - `'value'` — clicking the header toggles the group's own `value` in the selection. * - `'toggle-all'` — clicking the header toggles every option in the group at once. * ⚠️ With async loadOptions, this toggles only the options on the current server page; * the consumer is responsible for preloading the full group if global toggle is needed. * @default 'static' */ groupHeader?: 'static' | 'value' | 'toggle-all'; /** Equality comparator for `T`. Required when `T` is an object. @default (a, b) => a === b */ compare?: (a: T, b: T) => boolean; /** * Multi cascade behavior when `groupHeader='toggle-all'`: * - `'children-only'` (default) — picking a child adds only the child; the group's own `value` stays independent. * - `'children-include-parent'` — picking any child auto-adds the group's `value` to the selection; unpicking the last child removes it. Toggle-all on the header in this mode also adds/removes the group's `value` together with children. * @default 'children-only' */ selectionMode?: 'children-only' | 'children-include-parent'; /** * How rows render selected state inside the listbox: * - `'checkmark'` (default) — ✓ on the right; group headers show check only in `'checked'` state. * - `'checkbox'` — checkbox on the left of every row; group headers show tri-state in `selectionMode='children-include-parent'`, binary otherwise. * - `'highlight'` — selected rows get the active background only. * @default 'checkmark' */ selectedDisplay?: 'checkmark' | 'checkbox' | 'highlight'; /** Enables creatable mode. When `parentSource` is non-empty, a create-with-parent section replaces the simple "Create …" row. See {@link SelectCreateOptions}. */ createOptions?: SelectCreateOptions; /** Where chips render. `'external'` puts them in a row above the trigger and unlocks `reorderable`. @default 'inline' */ chipMode?: 'inline' | 'external'; /** Enables drag-and-drop reorder on the external chip row. Ignored unless `chipMode='external'`. @default false */ reorderable?: boolean; /** Inline-mode only: collapses chips beyond N into a "+N more" indicator. @default Infinity */ maxVisible?: number; /** Chips don't truncate their labels with ellipsis. @default false */ showFullItem?: boolean; /** Chip color tone. When set, overrides the chip's default colors (e.g. `'accent'` for a tag-input look). */ chipVariant?: 'neutral' | 'accent'; /** Show the trailing chevron. Set `false` for a plain field look (e.g. tag input). The loading spinner still shows. @default true */ showChevron?: boolean; /** Hide already-selected options from the listbox (tag-input style — picked tags disappear from suggestions). @default false */ hideSelected?: boolean; /** Leading icon — string SVG source, `{ src, color?, size? }` object, or custom snippet. */ icon?: IconProp; /** Trailing chevron icon override. Same shape as `icon`. */ chevronIcon?: IconProp; /** Custom row renderer. Replaces the option's label cell — receives `{ option }`. */ optionSnippet?: Snippet<[{ option: SelectOption; }]>; /** Custom chip renderer (replaces both inline and external chip label cells). */ chipSnippet?: Snippet<[{ option: SelectOption; }]>; /** When provided, replaces the entire chip row inside the trigger. Pass an empty snippet to hide chips completely (selection visible only inside the popover). */ selectionSnippet?: Snippet<[{ options: SelectOption[]; }]>; /** * Enables the create-with-parent section (shown above the listbox when `createOptions.canCreate` gates true). * Receives the FULL parent-candidate list independent of current filter — so the parent picker offers * every candidate regardless of what's currently visible in the listbox. When undefined or empty, a * single "Create …" row is shown instead. */ parentSource?: SelectOption[]; id?: string; name?: string; autocomplete?: HTMLInputAttributes['autocomplete']; 'aria-label'?: string; 'aria-describedby'?: string; 'aria-required'?: boolean; on?: { /** Fires when the user picks/unpicks/reorders. Emits the full new selection. */ change?: (value: SelectOption[]) => void; /** Fires when the user activates "Create …". `query` is trimmed. When `parentValue` is present, the user picked it via the create-with-parent section. Return a Promise to keep the spinner up until the consumer finishes. */ create?: (payload: { query: string; parentValue?: T; }) => void | Promise; }; };