@llselect/core
Version:
Low-level select: a minimal, flexible, framework-agnostic replacement for the native HTML select element.
168 lines • 7.85 kB
TypeScript
import { LLSelectBase, type LLSelectBaseSettings, type LLSelectChangeMeta, type LLSelectSettingsInputOf } from './base.js';
/**
* Context passed to {@link LLSelectSingleSettings.createTriggerContentElFn}.
* @group Settings
* @category Single
*/
export interface LLSelectSingleTriggerContext<T> {
chosenItem: T | undefined;
items: readonly T[];
}
/**
* Resolved (defaults applied) settings for {@link LLSelectSingle}: the base
* settings plus the single-mode fields - the runtime type of `this.settings`,
* one bag built complete in the constructor.
* @group Settings
* @category Single
*/
export interface LLSelectSingleSettings<T, GroupKey = string> extends LLSelectBaseSettings<T, GroupKey> {
/**
* Fired when the chosen item actually changes (compared via `compareFn`).
* Receives the new value and the PREVIOUS one (the snapshot from before
* this change); `undefined` means "no selection" on either side. Does NOT
* fire on construction nor on `setChosenItem` with an equivalent item.
* `null` (default) = no listener.
* - `meta.source` says who initiated the change: `'user'` for a pointer or
* keyboard interaction inside the widget, `'api'` for any programmatic
* call. See {@link LLSelectChangeMeta}.
* @group Events
*/
onChange: ((chosenItem: T | undefined, previousChosenItem: T | undefined, meta: LLSelectChangeMeta) => void) | null;
/**
* Render the trigger's content ELEMENT without subclassing - the setting
* equivalent of overriding `renderTriggerContent`. Receives the chosen item
* + items (same convention as `createItemContentElFn`):
* - `HTMLElement` - inserted into the trigger as-is; you own it. Use this for
* real markup (icon + text, etc.).
* - fn returns `null` - use the default for this render: the chosen item's
* `itemToString`, or the placeholder when nothing is chosen.
* - 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: LLSelectSingleTriggerContext<T>) => HTMLElement | null) | null;
}
/**
* Constructor-time settings input for {@link LLSelectSingle}.
* Every field is optional; missing fields use defaults.
* @group Settings
* @category Single
*/
export type LLSelectSingleSettingsInput<T, GroupKey = string> = LLSelectSettingsInputOf<LLSelectSingleSettings<T, GroupKey>>;
/**
* Single-selection select. Picking an item replaces any prior chosen item
* and closes the popup. Use `setChosenItem(undefined)` to clear the selection.
*
* @typeParam T - item type. Supply your own `compareFn` for non-primitive `T`.
* @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 LLSelectSingle<T = unknown, GroupKey = string, S extends LLSelectSingleSettings<T, GroupKey> = LLSelectSingleSettings<T, GroupKey>> extends LLSelectBase<T, GroupKey, S> {
/**
* Currently chosen item, or `undefined` if none.
* @group State (protected)
*/
protected chosenItem: T | undefined;
/**
* 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 LLSelectSingle<string>(...)`.
* @group Lifecycle
*/
constructor(targetEl: HTMLElement, settings?: LLSelectSingleSettingsInput<T, GroupKey>);
/**
* 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<S>, subclassSettings?: Omit<S, keyof LLSelectSingleSettings<T, GroupKey>>);
/**
* Return the currently chosen item, or `undefined` if none.
* @group Selection
*/
getChosenItem(): T | undefined;
/**
* Set the chosen item programmatically.
* - `undefined` clears the choice.
* - Fires `onChange` only when the item actually differs from the current
* one (compared via `compareFn`).
* - Accepts an item that is not (yet) in the items list, for async data
* flows. If a later `setItems` does not include it, it is dropped
* automatically.
* - It does not check disabled state: a disabled item can be chosen
* programmatically. Native `<select>` behaves the same.
* @group Selection
*/
setChosenItem(item: T | undefined): void;
/**
* Orchestrator: composes `syncEmptyStateToDom` + `commitTriggerContentToDom`
* to (re)build the trigger from state; touches no DOM directly.
* - `createTriggerContentElFn` is tried first; if it returns `null` or is
* unset, the default applies.
* - The default is the chosen item's string, or the placeholder when
* nothing is chosen.
* @group Subclassing: rendering
*/
protected renderTriggerContent(): void;
/**
* No selection iff `chosenItem` is unset. Drives the trigger's `data-empty`.
* @group Subclassing: semantics
*/
protected isEmpty(): boolean;
/**
* Mark the chosen option `aria-selected="true"`, the rest `"false"` (APG select-only).
* @group Subclassing: rendering
*/
protected createItemEl(item: T, index: number): HTMLElement;
/**
* Pick this item as the chosen item and close the popup.
* @group Subclassing: reactions
*/
protected onItemActivated(item: T): void;
/**
* Clear button empties the single selection to `undefined`.
* @group Subclassing: semantics
*/
protected clearSelection(): void;
/**
* On open, focus the chosen item (if present and enabled), else the first
* enabled item. Indices are into `getVisibleItems()` (the rendered list).
* @group Subclassing: focus
*/
protected focusInitial(): void;
/**
* Closed-state typeahead searches relative to the CHOSEN item, like a
* native `<select>`: typing its initial cycles to the next match.
* - Returns `-1` when nothing is chosen, or the chosen item left the list;
* the search then starts from the top.
* @group Subclassing: focus
*/
protected computeTypeaheadClosedStartIndex(list: readonly T[]): number;
/**
* Re-match the chosen item against the new list after `setItems`.
* - If the list no longer holds it (by `compareFn`), it is dropped and
* `onChange` fires.
* - If the list holds a compareFn-equal but DIFFERENT object (`track by`
* style reload: same key, fresh fields), the stored reference is swapped
* to the list's object. The logical value did not change, so `onChange`
* does not fire.
* - The trigger content re-renders after every `setItems`, because a custom
* `createTriggerContentElFn` receives `items`.
* - The arrow re-renders only when the chosen item is dropped, because that
* runs the whole trigger.
* @group Subclassing: reactions
*/
protected onItemsChanged(): void;
private areEqual;
private fireChange;
}
//# sourceMappingURL=single.d.ts.map