Tofu/app/components/atoms/SuggestionList.js
2026-09-18 01:41:02 +02:00

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>
`
}