ComboBox
A text field with a list of suggestions, which shows the items that match what was typed.
Selected: none.
<script>
import { $bind, $signal } from 'brumate/signals';
import Button from '@brumate/ui/Button.brm';
import ComboBox from '@brumate/ui/ComboBox.brm';
import Input from '@brumate/ui/Input.brm';
import Label from '@brumate/ui/Label.brm';
import ListBox from '@brumate/ui/ListBox.brm';
import ListBoxItem from '@brumate/ui/ListBoxItem.brm';
import Popover from '@brumate/ui/Popover.brm';
import type { Key } from '@brumate/ui/collection.ts';
let animal = $signal<Key | null>(null);
const animals = ['Aardvark', 'Cat', 'Dog', 'Kangaroo', 'Koala', 'Panda', 'Penguin', 'Snake'];
</script>
<ComboBox class="ui-select" name="animal" selectedKey=$bind(animal)>
<Label>Favorite animal</Label>
<div class="ui-combobox-box">
<Input class="ui-input" placeholder="Type to search" />
<Button class="ui-button ui-select-button" />
</div>
<Popover class="ui-popover ui-select-popover">
<ListBox class="ui-listbox">
#for (name of animals by name) {
<ListBoxItem id=(name.toLowerCase()) class="ui-option">{name}</ListBoxItem>
}
</ListBox>
</Popover>
</ComboBox>
<p>Selected: {animal ?? 'none'}.</p>
<ComboBox name="animal" selectedKey=$bind(animal)>
<Label>Favorite animal</Label>
<Input />
<Button />
<Popover>
<ListBox>
<ListBoxItem id="cat">Cat</ListBoxItem>
<ListBoxItem id="dog">Dog</ListBoxItem>
</ListBox>
</Popover>
</ComboBox>
- A
ComboBoxholds aLabel, anInput, aButton, aPopoverwith a ListBox, and can hold aTextwithslot="description"and aFieldError. selectedKeyis the key of the selected item, ornull.inputValueis the text in the input. Each starts its own state, or binds with$bind. See State.onSelectionChange,onInputChangeandonOpenChangeget each change.- The ComboBox shows
data-open,data-disabled,data-invalidanddata-required.
Typing and choosing
- Typing opens the list, and it shows the items whose text contains what was typed, in any case. The list closes when no item matches.
defaultFilterdecides when an item matches, such as(text, input) => text.startsWith(input).menuTrigger="focus"opens the list when the input gets focus, and"manual"opens it only with the keys and the Button.- ArrowDown and ArrowUp open the list with all of its items, and move through its items, and go round at the ends. Focus stays in the input, and
aria-activedescendantnames the active item. The pointer makes an item active too. - Enter and a press pick an item. Its text goes in the input, and the list closes. With no active item, Enter closes the list as Escape does. While the list is closed, Enter sends the form.
- The Button opens and closes the list, with all of its items, and focus stays in the input. It is out of the order of Tab, as the keys of the input do the same.
Closing
- Escape closes the list, and puts the text of the selected item back in the input, or clears it when nothing is selected. It never clears the selection. An Escape with nothing to undo goes on to the element around, such as a Dialog.
- Focus that leaves the field does the same.
allowsCustomValuekeeps what was typed. Text that is not the text of the selected item clears the selection.isRequiredmakes the input required, witharia-required.
Forms
name sends the key of the selected item with its form, or the text of the input with allowsCustomValue. formValue="key" or "text" chooses. The value goes with the form also before the code of the page loads. A server error for the name in a Form shows in the FieldError until the selection changes.
How it filters
The items are on the page while the list is open, so the ComboBox hides those that do not match, and the keys skip hidden items. A section with no item left is hidden too. To filter the items yourself, such as from a server, bind inputValue and render only the items that you want in a #for.