import { Controller } from "@hotwired/stimulus"; // https://getbootstrap.com/docs/4.6/components/dropdowns/#single-button export default class extends Controller { static targets = ["dropdown", "button", "item"]; // Al iniciar el controlador connect() { // Llevar la cuenta del item con foco this.data.set("item", -1); // Gestionar las teclas this.keydownEvent = this.keydown.bind(this); this.element.addEventListener("keydown", this.keydownEvent); // Gestionar el foco this.focusinEvent = this.focusin.bind(this); } // Al eliminar el controlador (al pasar a otra página) disconnect() { // Eliminar la gestión de teclas this.element.removeEventListener("keydown", this.keydownEvent); // Eliminar la gestión del foco document.removeEventListener("focusin", this.focusinEvent); } // Mostrar u ocultar toggle(event) { (this.buttonTarget.ariaExpanded === "false") ? this.show() : this.hide(); } // Mostrar show() { this.buttonTarget.ariaExpanded = "true"; this.element.classList.add("show"); this.dropdownTarget.classList.add("show"); // Activar la gestión del foco document.addEventListener("focusin", this.focusinEvent); } // Ocultar hide() { this.buttonTarget.ariaExpanded = "false"; this.element.classList.remove("show"); this.dropdownTarget.classList.remove("show"); // Volver al inicio el foco de items this.data.set("item", -1); // Desactivar la gestión del foco document.removeEventListener("focusin", this.focusinEvent); } // Gestionar el foco focusin(event) { const item = this.itemTargets.find(x => x === event.target); // Si el foco se coloca sobre elementos del controlador, no hacer // nada if (event.target === this.buttonTarget || item) { // Si es un item, el comportamiento de las flechas verticales y el // Tab tiene que ser igual if (item) this.data.set("item", this.itemTargets.indexOf(item)); return; } // De lo contrario, ocultar this.hide(); } // Gestionar las teclas keydown(event) { const initial = parseInt(this.data.get("item")); let item = initial; switch (event.keyCode) { case 27: // Esc cierra el menú y devuelve el foco this.hide(); this.buttonTarget.focus(); break; case 38: // Moverse hacia arriba con tope en el primer item if (item > -1) item--; break; case 40: // Moverse hacia abajo con tope en el último ítem, si el // dropdown estaba cerrado, abrirlo. if (item === -1) this.show(); if (item <= this.itemTargets.length) item++; break; } // Si cambió la posición del ítem, darle foco y actualizar el // contador. if (initial !== item) { this.itemTargets[item]?.focus(); this.data.set("item", item); } } }