Files
fizzy/app/javascript/controllers/navigable_list_controller.js
T
2025-06-02 18:04:14 +02:00

87 lines
2.0 KiB
JavaScript

import { Controller } from "@hotwired/stimulus"
import { nextFrame } from "helpers/timing_helpers"
export default class extends Controller {
static targets = [ "item" ]
static values = { selectionAttribute: { type: String, default: "aria-current" } }
connect() {
this.selectLast()
}
// Actions
navigate(event) {
if (this.itemTargets.includes(event.target)) {
this.#keyHandlers[event.key]?.call(this, event)
}
}
select({ target }) {
this.#setCurrentFrom(target)
}
selectCurrentOrLast(event) {
if (this.currentItem) {
this.#setCurrentFrom(this.currentItem)
} else {
this.selectLast()
}
}
selectLast() {
this.#setCurrentFrom(this.itemTargets[this.itemTargets.length - 1])
}
#selectPrevious() {
if (this.currentItem.previousElementSibling) {
this.#setCurrentFrom(this.currentItem.previousElementSibling)
}
}
#selectNext() {
if (this.currentItem.nextElementSibling) {
this.#setCurrentFrom(this.currentItem.nextElementSibling)
}
}
async #setCurrentFrom(element) {
const selectedItem = this.itemTargets.find(item => item.contains(element))
if (selectedItem) {
this.#clearSelection()
selectedItem.setAttribute(this.selectionAttributeValue, "true")
this.currentItem = selectedItem
await nextFrame()
this.currentItem.focus()
}
}
#clearSelection() {
for (const item of this.itemTargets) {
item.removeAttribute(this.selectionAttributeValue)
}
}
#handleArrowKey(event, fn) {
if (event.shiftKey || event.metaKey || event.ctrlKey) { return }
fn.call()
event.preventDefault()
}
#keyHandlers = {
ArrowDown(event) {
this.#handleArrowKey(event, this.#selectNext.bind(this))
},
ArrowUp(event) {
this.#handleArrowKey(event, this.#selectPrevious.bind(this))
},
ArrowRight(event) {
this.#handleArrowKey(event, this.#selectNext.bind(this))
},
ArrowLeft(event) {
this.#handleArrowKey(event, this.#selectPrevious.bind(this))
}
}
}