@zag-js/listbox
Version:
Core logic for the listbox widget implemented as a state machine
307 lines (304 loc) • 9.14 kB
TypeScript
import { CollectionItem, ListCollection, GridCollection, SelectionMode, Selection } from '@zag-js/collection';
import { Machine, EventObject, Service } from '@zag-js/core';
import { TypeaheadState } from '@zag-js/dom-query';
import { PropTypes, RequiredBy, DirectionProperty, CommonProperties } from '@zag-js/types';
interface ValueChangeDetails<T extends CollectionItem = CollectionItem> {
value: string[];
items: T[];
}
interface HighlightChangeDetails<T extends CollectionItem = CollectionItem> {
highlightedValue: string | null;
highlightedItem: T | null;
highlightedIndex: number;
}
interface ScrollToIndexDetails {
index: number;
immediate?: boolean | undefined;
getElement: () => HTMLElement | null;
}
interface SelectionDetails {
value: string;
}
type ElementIds = Partial<{
root: string;
content: string;
label: string;
item: (id: string | number) => string;
itemGroup: (id: string | number) => string;
itemGroupLabel: (id: string | number) => string;
}>;
interface ListboxProps<T extends CollectionItem = CollectionItem> extends DirectionProperty, CommonProperties {
/**
* The orientation of the listbox.
* @default "vertical"
*/
orientation?: "horizontal" | "vertical" | undefined;
/**
* The item collection
*/
collection: ListCollection<T> | GridCollection<T>;
/**
* The ids of the elements in the listbox. Useful for composition.
*/
ids?: ElementIds | undefined;
/**
* Whether the listbox is disabled
*/
disabled?: boolean | undefined;
/**
* Whether to disallow selecting all items when `meta+a` is pressed
*/
disallowSelectAll?: boolean | undefined;
/**
* The callback fired when the highlighted item changes.
*/
onHighlightChange?: ((details: HighlightChangeDetails<T>) => void) | undefined;
/**
* The callback fired when the selected item changes.
*/
onValueChange?: ((details: ValueChangeDetails<T>) => void) | undefined;
/**
* The controlled keys of the selected items
*/
value?: string[] | undefined;
/**
* The initial default value of the listbox when rendered.
* Use when you don't need to control the value of the listbox.
*
* @default []
*/
defaultValue?: string[] | undefined;
/**
* The controlled key of the highlighted item
*/
highlightedValue?: string | null | undefined;
/**
* The initial value of the highlighted item when opened.
* Use when you don't need to control the highlighted value of the listbox.
*/
defaultHighlightedValue?: string | null | undefined;
/**
* Whether to loop the keyboard navigation through the options
* @default false
*/
loopFocus?: boolean | undefined;
/**
* How multiple selection should behave in the listbox.
*
* - `single`: The user can select a single item.
* - `multiple`: The user can select multiple items without using modifier keys.
* - `extended`: The user can select multiple items by using modifier keys.
*
* @default "single"
*/
selectionMode?: SelectionMode | undefined;
/**
* Function to scroll to a specific index
*/
scrollToIndexFn?: ((details: ScrollToIndexDetails) => void) | undefined;
/**
* Whether to select the item when it is highlighted
*/
selectOnHighlight?: boolean | undefined;
/**
* Whether to disallow empty selection
*/
deselectable?: boolean | undefined;
/**
* Whether to enable typeahead on the listbox
*/
typeahead?: boolean | undefined;
/**
* Function called when an item is selected
*/
onSelect?: ((details: SelectionDetails) => void) | undefined;
}
type PropsWithDefault = "collection" | "selectionMode";
interface ListboxSchema<T extends CollectionItem = CollectionItem> {
state: "idle";
props: RequiredBy<ListboxProps<T>, PropsWithDefault>;
context: {
value: string[];
highlightedValue: string | null;
highlightedItem: T | null;
selectedItemMap: Map<string, T>;
focused: boolean;
};
computed: {
hasSelectedItems: boolean;
isTypingAhead: boolean;
isInteractive: boolean;
selection: Selection;
multiple: boolean;
selectedItems: T[];
valueAsString: string;
};
refs: {
typeahead: TypeaheadState;
focusVisible: boolean;
inputState: {
autoHighlight: boolean;
focused: boolean;
};
};
action: string;
guard: string;
effect: string;
event: EventObject;
}
type ListboxService<T extends CollectionItem = CollectionItem> = Service<ListboxSchema<T>>;
type ListboxMachine<T extends CollectionItem = CollectionItem> = Machine<ListboxSchema<T>>;
interface ItemProps<T extends CollectionItem = CollectionItem> {
/**
* The item to render
*/
item: T;
/**
* Whether to highlight the item on hover
*/
highlightOnHover?: boolean | undefined;
}
interface ItemState {
/**
* The underlying value of the item
*/
value: string;
/**
* Whether the item is disabled
*/
disabled: boolean;
/**
* Whether the item is selected
*/
selected: boolean;
/**
* Whether the item is highlighted
* @deprecated Use `focused` and `focusVisible` instead
*/
highlighted: boolean;
/**
* Whether the item is focused
*/
focused: boolean;
/**
* Whether the item is focus visible
*/
focusVisible: boolean;
}
interface ItemGroupProps {
id: string;
}
interface ItemGroupLabelProps {
htmlFor: string;
}
interface InputProps {
/**
* Whether to automatically highlight the item when typing
* @default false
*/
autoHighlight?: boolean | undefined;
/**
* Determines how keyboard conflicts in the input are resolved.
* - "caret": keep native text-editing behavior
* - "navigate": forward supported keys to listbox navigation
* @default "caret"
*/
keyboardPriority?: "caret" | "navigate" | undefined;
}
interface ListboxApi<T extends PropTypes = PropTypes, V extends CollectionItem = CollectionItem> {
/**
* Whether the select value is empty
*/
empty: boolean;
/**
* The value of the highlighted item
*/
highlightedValue: string | null;
/**
* The highlighted item
*/
highlightedItem: V | null;
/**
* Function to highlight a value
*/
highlightValue: (value: string) => void;
/**
* Function to highlight the first value
*/
highlightFirst: VoidFunction;
/**
* Function to highlight the last value
*/
highlightLast: VoidFunction;
/**
* Function to highlight the next value
*/
highlightNext: VoidFunction;
/**
* Function to highlight the previous value
*/
highlightPrevious: VoidFunction;
/**
* Function to clear the highlighted value
*/
clearHighlightedValue: VoidFunction;
/**
* The selected items
*/
selectedItems: V[];
/**
* Whether there's a selected option
*/
hasSelectedItems: boolean;
/**
* The selected item keys
*/
value: string[];
/**
* The string representation of the selected items
*/
valueAsString: string;
/**
* Function to select a value
*/
selectValue: (value: string) => void;
/**
* Function to select all values.
*
* **Note**: This should only be called when the selectionMode is `multiple` or `extended`.
* Otherwise, an exception will be thrown.
*/
selectAll: VoidFunction;
/**
* Function to set the value of the select
*/
setValue: (value: string[]) => void;
/**
* Function to clear the value of the select.
* If a value is provided, it will only clear that value, otherwise, it will clear all values.
*/
clearValue: (value?: string) => void;
/**
* Returns the state of a select item
*/
getItemState: (props: ItemProps) => ItemState;
/**
* Function to toggle the select
*/
collection: ListCollection<V>;
/**
* Whether the select is disabled
*/
disabled: boolean;
getInputProps: (props?: InputProps) => T["input"];
getRootProps: () => T["element"];
getLabelProps: () => T["label"];
getValueTextProps: () => T["element"];
getContentProps: () => T["element"];
getItemProps: (props: ItemProps) => T["element"];
getItemTextProps: (props: ItemProps) => T["element"];
getItemIndicatorProps: (props: ItemProps) => T["element"];
getItemGroupProps: (props: ItemGroupProps) => T["element"];
getItemGroupLabelProps: (props: ItemGroupLabelProps) => T["element"];
}
export type { ElementIds, HighlightChangeDetails, InputProps, ItemGroupLabelProps, ItemGroupProps, ItemProps, ItemState, ListboxApi, ListboxMachine, ListboxProps, ListboxSchema, ListboxService, ScrollToIndexDetails, SelectionDetails, ValueChangeDetails };