import { LLSelectBase, type LLSelectBaseSettings, type LLSelectChangeMeta, type LLSelectSettingsInputOf } from './base.js'; /** * Trigger display mode of {@link LLSelectMultiple}. * - `'count'`: a text summary like "3 / 10 selected". * - `'tags'`: one removable chip per chosen item. * See {@link LLSelectMultipleSettings.triggerDisplay}. * @group Settings * @category Multiple */ export type LLSelectTriggerDisplay = 'count' | 'tags'; /** * Tri-state of the choose-all row (also the `data-chosen-state` attribute * value): how much of the VISIBLE enabled subset is currently chosen. * @group Settings * @category Multiple */ export type LLSelectChosenState = 'none' | 'some' | 'all'; /** * Context passed to {@link LLSelectMultipleSettings.createTriggerContentElFn}. * @group Settings * @category Multiple */ export interface LLSelectMultipleTriggerContext { chosenItems: readonly T[]; items: readonly T[]; } /** * Resolved (defaults applied) settings for {@link LLSelectMultiple}: the base * settings plus the multi-mode fields - the runtime type of `this.settings`, * one bag built complete in the constructor. * @group Settings * @category Multiple */ export interface LLSelectMultipleSettings extends LLSelectBaseSettings { /** * Fired when the chosen-items set actually changes. Receives the new set * and the PREVIOUS one (the snapshot from before this change) - diff them * with `compareFn` to compute added / removed. Does NOT fire on * construction nor on a setter call that yields an equivalent set * (element-wise compared via `compareFn`, order-sensitive). * `null` (default) = no listener. * - `meta.source` says who initiated the change: `'user'` for a pointer or * keyboard interaction inside the widget (an option toggle, a tag's * remove button, the clear button, the choose-all row), `'api'` for any * programmatic call. See {@link LLSelectChangeMeta}. * @group Events */ onChange: ((chosenItems: readonly T[], previousChosenItems: readonly T[], meta: LLSelectChangeMeta) => void) | null; /** * Render the trigger's content ELEMENT without subclassing - the setting * equivalent of overriding `renderTriggerContent`. Receives the chosen items * + items (same convention as `createItemContentElFn`): * - `HTMLElement` - inserted into the trigger as-is; you own it. Use this for * real markup such as tag chips. * - fn returns `null` - use the default for this render (count summary / tags). * - setting is `null` (default) - always use that default rendering. * The DEFAULT `renderTriggerContent` checks it first; a subclass override * replaces that default entirely and may ignore the setting - override * wins, per DESIGN.md "Customization model". * @group Trigger */ createTriggerContentElFn: ((ctx: LLSelectMultipleTriggerContext) => HTMLElement | null) | null; /** * Trigger display mode. * - `'count'` (default): a summary like "3 / 10 selected". * - `'tags'`: one removable chip per chosen item; its x button removes it. * `createTriggerContentElFn` overrides both (full control wins). * @group Trigger */ triggerDisplay: LLSelectTriggerDisplay; /** * Item -> the visible content ELEMENT of its tag chip in `'tags'` mode, * without subclassing. Mirrors `createItemContentElFn` (the chip is to the * trigger what the option content is to the row): * - Return an `HTMLElement` and the library inserts it as the chip's content; * the library still owns the chip container + the remove (x) button + aria. * - `null` (setting default, or returned for an item) = plain text from * `itemToString`. * - The remove button's accessible name comes from * `itemToTagRemoveButtonAriaLabel` (default `Remove `) - * that is what AT is guaranteed to announce. The chip is a generic `` (ARIA prohibits naming it), so * for icon-only content include your own (visually hidden) text if the * chip should be announced as more than its remove button. See * `docs/llm/A11Y.md` "Tags". * @group Trigger */ createTagContentElFn: ((item: T) => HTMLElement | null) | null; /** * Icon ELEMENT of each tag's remove (x) button in `'tags'` mode, mirroring * `createTriggerClearButtonContentElFn` (the clear button's icon hook). The library always owns the * button, its click (removes the item + `stopPropagation`), `tabindex="-1"`, and * the `aria-label` accessible name (from `itemToTagRemoveButtonAriaLabel`); * this only fills the decorative icon. * - Return an `HTMLElement` / `SVGElement`: appended inside the button as its icon. * - `null` (setting default, or returned for an item): no icon - the theme * draws the x via its CSS glyph (`.llselect-tag-remove-button:empty::before`). * @group Trigger */ createTagRemoveButtonContentElFn: ((item: T) => HTMLElement | SVGElement | null) | null; /** * Hide the rows of chosen items from the popup list. * - Default `false`: chosen rows stay listed and show their state. * - While `true`, choosing an item removes its row at once and unchoosing * puts it back. Tags, the trigger and `getChosenItems` are unaffected. * - When every item is chosen, the popup shows the no-results element. * - With `chooseAllRow`, the visible subset is always fully unchosen, so * the row acts as "choose everything still listed", its tri-state never * reaches all-chosen, and it disappears with the last actionable row. * - Internals: a `getVisibleItems` subtraction. The default `compareFn` * uses a Set lookup; a custom `compareFn` costs O(visible x chosen). * Either cost is paid once per change of the list or the chosen set - * the result is cached between changes. `toggleItem` swaps its O(1) row * replace for a full rebuild. * @group Items */ hideChosenRows: boolean; /** * Whether the popup shows a choose-all row (the industry's "select all") * as the first option of the listbox. * - Default `false`. * - Activating the row (Enter / click) runs `toggleAllVisible`: it toggles * the visible enabled subset (the matching subset while a filter query * is active). The public `chooseAll` / `unchooseAll` / `toggleAll` keep * their whole-list semantics. * - The row is tri-state (none / some / all chosen), carried by the * counting text's numbers and the `data-chosen-state` CSS hook. * - Its accessible name comes from `uiTranslationPack.chooseAllRowText`. * - See `docs/llm/A11Y.md` "Choose-all". * @group Choose-all */ chooseAllRow: boolean; /** * The choose-all row's visible content ELEMENT, without subclassing - e.g. * a tri-state SVG checkbox (`createOutlinedCheckboxSvgEl`) + the counting text. Mirrors * `createItemContentElFn`. Only used with `chooseAllRow: true`. * - Receives the tri-state and the counts of the visible enabled subset. * - Return an `HTMLElement`: inserted as the row's content; the accessible * name stays pinned to `uiTranslationPack.chooseAllRowText` via `aria-label`, so * icon-only content is still announced with the counts. * - `null` (setting default, or returned): the default content - just the * plain counting text; its numbers carry the tri-state. The library * ships no default indicator (consistent with items and the arrow); * passing this setting is how one (e.g. `createOutlinedCheckboxSvgEl`) * gets added. See DESIGN.md "Choose-all default: plain counting text". * @group Choose-all */ createChooseAllRowContentElFn: ((chosenState: LLSelectChosenState, chosenCount: number, totalCount: number) => HTMLElement | null) | null; } /** * Constructor-time settings input for {@link LLSelectMultiple}. * Every field is optional; missing fields use defaults. * @group Settings * @category Multiple */ export type LLSelectMultipleSettingsInput = LLSelectSettingsInputOf>; /** * Multi-selection select. Clicking an item toggles its membership in the * chosen-items set and keeps the popup open. Each item DOM gets * `aria-selected="true|false"`; the popup list gets * `aria-multiselectable="true"`. * * Default trigger display is a count summary ("3 / 10 selected" / "All N * selected" / placeholder when empty). Pass `createTriggerContentElFn` (or * subclass `renderTriggerContent`) to customise (e.g. tag chips). * * @typeParam T - item type. * @typeParam GroupKey - group key type of `itemToGroupKeyFn`; see * {@link LLSelectBase}. * @typeParam S - resolved settings type, for subclasses extending the * settings bag; see {@link LLSelectBase}. * @group Select classes */ export declare class LLSelectMultiple = LLSelectMultipleSettings> extends LLSelectBase { /** * Currently chosen items, in insertion order. * @group State (protected) */ protected chosenItems: readonly T[]; /** * Build the control inside `targetEl`. * - Settings are resolved once here; missing fields get defaults. * - They are frozen afterwards, except `placeholder` and `uiTranslationPack`, * which have runtime setters; the rule is at {@link LLSelectBaseSettings}. * - This plain form infers `T` from a typed callback in `settings` whose * signature contains `T` (`itemToStringFn: (u: User) => ...`). With no * such callback, pass `T` explicitly: `new LLSelectMultiple(...)`. * @group Lifecycle */ constructor(targetEl: HTMLElement, settings?: LLSelectMultipleSettingsInput); /** * Subclass form. `subclassSettings` is the typed pass-through for subclasses * that extend the settings bag further; see `LLSelectBase`'s `S` param. * @group Lifecycle */ constructor(targetEl: HTMLElement, settings?: LLSelectSettingsInputOf, subclassSettings?: Omit>); /** * Return the currently chosen items (insertion order). * @group Selection */ getChosenItems(): readonly T[]; /** * Replace the entire chosen-items list. * - The input is copied, and duplicates (per `compareFn`) collapse to * their first occurrence: the chosen items are a set. * - Fires `onChange` only when the new list differs from the current one. * The comparison is order-sensitive: chosen order is visible state * (tags render in it). * - It ignores disabled state: it can add and drop disabled items, unlike * the `choose*` bulk ops. Assigning to a native `