82 lines
2.4 KiB
JavaScript
82 lines
2.4 KiB
JavaScript
import { useState } from 'preact/hooks'
|
|
import { html } from 'htm/preact'
|
|
import { TextField } from '#tofu/components/atoms/TextField.js'
|
|
import { SuggestionList } from '#tofu/components/atoms/SuggestionList.js'
|
|
|
|
// Combobox over the dish name. Suggestions arrive as a prop: this molecule never
|
|
// reads a store, a service or an index. It owns UI state only (open, activeIndex).
|
|
// Nothing is ever blocked: an empty suggestion list simply means free text.
|
|
export function DishNameField({ id, label, value, suggestions = [], onInput, onSelect }) {
|
|
const [open, setOpen] = useState(false)
|
|
const [activeIndex, setActiveIndex] = useState(-1)
|
|
|
|
const listId = `${id}-suggestions`
|
|
const expanded = open && suggestions.length > 0
|
|
const activeId = expanded && activeIndex >= 0 ? `${listId}-${activeIndex}` : null
|
|
|
|
function close() {
|
|
setOpen(false)
|
|
setActiveIndex(-1)
|
|
}
|
|
|
|
function handleInput(nextValue) {
|
|
setOpen(true)
|
|
setActiveIndex(-1)
|
|
onInput(nextValue)
|
|
}
|
|
|
|
function selectSuggestion(suggestion) {
|
|
close()
|
|
onSelect(suggestion)
|
|
}
|
|
|
|
function handleKeyDown(event) {
|
|
if (event.key === 'Escape') {
|
|
close()
|
|
return
|
|
}
|
|
if (event.key === 'Enter') {
|
|
// Only intercept Enter when an item is highlighted; otherwise the form submits.
|
|
if (!expanded || activeIndex < 0) return
|
|
event.preventDefault()
|
|
selectSuggestion(suggestions[activeIndex])
|
|
return
|
|
}
|
|
if (suggestions.length === 0) return
|
|
if (event.key === 'ArrowDown') {
|
|
event.preventDefault()
|
|
setOpen(true)
|
|
setActiveIndex(expanded ? (activeIndex + 1) % suggestions.length : 0)
|
|
return
|
|
}
|
|
if (event.key === 'ArrowUp') {
|
|
event.preventDefault()
|
|
setOpen(true)
|
|
setActiveIndex(expanded && activeIndex > 0 ? activeIndex - 1 : suggestions.length - 1)
|
|
}
|
|
}
|
|
|
|
return html`
|
|
<${TextField}
|
|
id=${id}
|
|
label=${label}
|
|
value=${value}
|
|
onInput=${handleInput}
|
|
autocomplete="off"
|
|
role="combobox"
|
|
aria-expanded=${expanded ? 'true' : 'false'}
|
|
aria-controls=${listId}
|
|
aria-autocomplete="list"
|
|
aria-activedescendant=${activeId}
|
|
onKeyDown=${handleKeyDown}
|
|
onBlur=${() => close()}
|
|
>
|
|
<${SuggestionList}
|
|
id=${listId}
|
|
items=${expanded ? suggestions : []}
|
|
activeIndex=${activeIndex}
|
|
onSelect=${selectSuggestion}
|
|
/>
|
|
<//>
|
|
`
|
|
}
|