useListboxSelection#
useListboxSelection is the selection core shared by Select and Combobox. It
owns single- and multi-select value tracking, an option registry that resolves
display labels in DOM order (so a closed control can show the selected label
without rendering its list), and hidden form-field serialization. Reach for it
when building a custom listbox-style control that needs the same selection
semantics as the built-in components.
See also: useListNavigation, Select, Combobox.
Demo#
- Apple
- Banana
- Cherry
- Date
selected: (none)
import { useListboxSelection } from 'hono-preact-ui';
import { useId, useLayoutEffect, useState } from 'preact/hooks';
const FRUITS = ['Apple', 'Banana', 'Cherry', 'Date'];
// A small option row that registers itself in the selection's label registry and
// reflects/toggles its own selected state. Prop types for isSelected/toggle/
// register mirror the actual hook return types (all accept unknown) so no cast
// is needed.
function Option(props: {
value: string;
isSelected: (v: unknown) => boolean;
toggle: (v: unknown) => void;
register: (id: string, value: unknown, label: string) => () => void;
}) {
const { value, isSelected, toggle, register } = props;
const id = useId();
useLayoutEffect(() => register(id, value, value), [id, value, register]);
const selected = isSelected(value);
return (
<li
id={id}
role="option"
aria-selected={selected}
data-selected={selected ? '' : undefined}
class="docs-listboxsel-option"
onClick={() => toggle(value)}
>
{value}
</li>
);
}
interface UseListboxSelectionExampleProps {
multiple?: boolean;
}
// The selection core shared by Select and Combobox: single/multi value tracking,
// a label registry resolving display labels in DOM order, and hidden form-field
// serialization. Styling: .docs-listboxsel* in root.css.
export function UseListboxSelectionExample({
multiple = false,
}: UseListboxSelectionExampleProps) {
const [value, setValue] = useState<string | string[] | undefined>(undefined);
const [, setOpen] = useState(true);
const sel = useListboxSelection<string>({
value,
setValue: (next) => setValue(next),
multiple,
setOpen,
name: 'fruit',
});
return (
<>
<ul
role="listbox"
aria-multiselectable={multiple}
class="docs-listboxsel-list"
>
{FRUITS.map((f) => (
<Option
key={f}
value={f}
isSelected={sel.isSelected}
toggle={sel.toggle}
register={sel.registerOption}
/>
))}
</ul>
<p class="docs-listboxsel-readout">
selected: <strong>{sel.selectedLabels().join(', ') || '(none)'}</strong>
</p>
{sel.hiddenFields}
</>
);
}
Signature#
import { useListboxSelection } from 'hono-preact-ui';
function useListboxSelection<Value = string>(
opts: UseListboxSelectionOptions<Value>
): ListboxSelection;
Options#
| Option | Type | Notes |
|---|---|---|
value | Value | Value[] | undefined | The controlled selected value(s). |
setValue | (next: Value | Value[]) => void | Called with the next selection: a bare Value in single-select, a Value[] in multi-select. |
multiple | boolean | Whether multiple options can be selected. |
setOpen | (open: boolean) => void | Called to close the control after a single-select choice. |
isValueEqual | (a: Value, b: Value) => boolean | Optional. Custom equality; defaults to Object.is. |
serializeValue | (value: Value) => string | Optional. Serializes a value for the hidden form field. |
itemToString | (value: Value) => string | Optional. Resolves a display label when no option is registered for a value. |
name | string | Optional. Hidden form-field name; enables native form submission. |
disabled | boolean | Optional. Disables selection. |
Result#
| Member | Type | Notes |
|---|---|---|
isSelected | (optionValue: unknown) => boolean | Whether a value is currently selected. |
toggle | (optionValue: unknown) => void | Select or deselect a value (closes the control in single-select). |
registerOption | (id, value, label) => () => void | Register an option in the label registry; returns a cleanup fn. |
selectedLabels | () => string[] | Selected labels in registry (DOM) order. |
selectedItems | () => OptionEntry[] | Selected options in value order, labels resolved via the registry. |
labelFor | (value: unknown) => string | Resolve a single value's display label. |
optionCount | number | Number of currently-registered options. |
hiddenFields | ComponentChild[] | null | Hidden <input>s for native form submission, or null. |