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 docs.css.
export function UseListboxSelectionExample({
multiple = false,
}: UseListboxSelectionExampleProps) {
const [values, setValues] = useState<string[]>([]);
const [, setOpen] = useState(true);
const sel = useListboxSelection<string>({
values,
setValues,
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 |
|---|---|---|
values | readonly Value[] | The selected values; [] when nothing is selected. |
setValues | (next: Value[]) => void | Called with the next selection array (a one-element array on a single-select pick). |
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. |
normalizeSelectionProps#
normalizeSelectionProps is the pure helper that maps Select's and Combobox's public
value / defaultValue / onValueChange props (discriminated on multiple,
see Select's Usage section) onto the array-shaped
values / setValues that useListboxSelection consumes. Reach for it when
building a custom control that wants the same single/multiple discriminated
prop contract as the built-in components, feeding the result into your own
useListboxSelection call.
import { normalizeSelectionProps } from 'hono-preact-ui';
import type { SelectionProps, NormalizedSelection } from 'hono-preact-ui';
function normalizeSelectionProps<Value>(
p: SelectionProps<Value>
): NormalizedSelection<Value>;
NormalizedSelection<Value> always represents selection as an array:
values/defaultValues are readonly Value[] (undefined values means
uncontrolled), and onValuesChange wraps the caller's onValueChange so a
single-mode caller still gets Value | null ([] -> null) while a
multiple-mode caller gets the Value[] it passed in. Memoize the result
per render: a single-mode value wraps into a fresh array each call, which
would otherwise churn downstream callback identities.