analytics/assets/js/liveview/combo-box.js

78 lines
2.0 KiB
JavaScript
Raw Normal View History

// Courtesy of Benjamin von Polheim:
// https://blog.devgenius.io/build-a-performat-autocomplete-using-phoenix-liveview-and-alpine-js-8bcbbed17ba7
export default (id) => ({
isOpen: false,
id: id,
focus: null,
setFocus(f) {
this.focus = f;
},
initFocus() {
if (this.focus === null) {
this.setFocus(this.leastFocusableIndex())
}
},
open() {
this.initFocus()
this.isOpen = true
},
suggestionsCount() {
return this.$refs.suggestions?.querySelectorAll('li').length
},
hasCreatableOption() {
return this.$refs.suggestions?.querySelector('li').classList.contains("creatable")
},
leastFocusableIndex() {
if (this.suggestionsCount() === 0) {
return 0
}
return this.hasCreatableOption() ? 0 : 1
},
maxFocusableIndex() {
return this.hasCreatableOption() ? this.suggestionsCount() - 1 : this.suggestionsCount()
},
nextFocusableIndex() {
const currentFocus = this.focus
return currentFocus + 1 > this.maxFocusableIndex() ? this.leastFocusableIndex() : currentFocus + 1
},
prevFocusableIndex() {
const currentFocus = this.focus
return currentFocus - 1 >= this.leastFocusableIndex() ? currentFocus - 1 : this.maxFocusableIndex()
},
close(e) {
// Pressing Escape should not propagate to window,
// so we'll only close the suggestions pop-up
if (this.isOpen && e.key === "Escape") {
e.stopPropagation()
}
this.isOpen = false
},
select() {
this.$refs[`dropdown-${this.id}-option-${this.focus}`]?.click()
this.close()
document.getElementById(this.id).blur()
},
scrollTo(idx) {
this.$refs[`dropdown-${this.id}-option-${idx}`]?.scrollIntoView(
{ block: 'nearest', behavior: 'smooth', inline: 'start' }
)
},
focusNext() {
const nextIndex = this.nextFocusableIndex()
if (!this.isOpen) this.open()
this.setFocus(nextIndex)
this.scrollTo(nextIndex)
},
focusPrev() {
const prevIndex = this.prevFocusableIndex()
if (!this.isOpen) this.open()
this.setFocus(prevIndex)
this.scrollTo(prevIndex)
}
})