40 lines
1.4 KiB
JavaScript
40 lines
1.4 KiB
JavaScript
import { html } from 'htm/preact'
|
|
|
|
// The listbox half of the ARIA combobox pattern. It owns no state: the parent tells
|
|
// it which item is active and receives the selected item back through `onSelect`.
|
|
//
|
|
// Two deliberate departures from the usual rules:
|
|
//
|
|
// 1. The options are <li role="option">, not <button>. The combobox pattern requires
|
|
// a listbox of options, and the keyboard is handled by the input that carries
|
|
// `role="combobox"` (arrows move, Enter selects, Escape closes). This is the
|
|
// reference accessible pattern, not a clickable div.
|
|
// 2. Selection listens to mousedown rather than click, because click fires after the
|
|
// input has already lost focus and closed the list. Preventing the default of
|
|
// mousedown also keeps the focus on the input, so typing can continue.
|
|
export function SuggestionList({ id, items, activeIndex, onSelect }) {
|
|
if (!items || items.length === 0) {
|
|
return null
|
|
}
|
|
return html`
|
|
<ul class="suggestions" id=${id} role="listbox">
|
|
${items.map(
|
|
(item, index) => html`
|
|
<li
|
|
class="suggestion"
|
|
key=${item.id ?? index}
|
|
id=${`${id}-${index}`}
|
|
role="option"
|
|
aria-selected=${index === activeIndex}
|
|
onMouseDown=${(event) => {
|
|
event.preventDefault()
|
|
onSelect(item)
|
|
}}
|
|
>
|
|
${item.name}
|
|
</li>
|
|
`
|
|
)}
|
|
</ul>
|
|
`
|
|
}
|