1import { Controller } from "@hotwired/stimulus" 2 3// Vertical accordion filter: inline search + clear-all per filter group 4export default class extends Controller { 5 static targets = ["searchInput", "option", "clearBtn"] 6 static values = { filterName: String } 7 8 connect() { 9 this.toggleClearBtn() 10 } 11 12 // Search/filter option rows client-side (no server request) 13 search(event) { 14 const query = event.currentTarget.value.toLowerCase().trim() 15 this.optionTargets.forEach(option => { 16 const text = option.querySelector("label")?.textContent?.toLowerCase() || "" 17 option.classList.toggle("d-none", query.length > 0 && !text.includes(query)) 18 }) 19 } 20 21 // Called on each checkbox change â show/hide the "Borrar" button 22 toggleClearBtn() { 23 const hasChecked = this.element.querySelectorAll("input.filter-inputs:checked, input.filter-inputs-mobile:checked").length > 0 24 const isHorizontal = Boolean(this.element.closest('.horizontal-filter-container')) 25 26 if (this.hasClearBtnTarget) { 27 this.clearBtnTargets.forEach(btn => { 28 if (isHorizontal) { 29 btn.classList.remove("d-none") 30 btn.disabled = !hasChecked 31 btn.setAttribute("aria-disabled", (!hasChecked).toString()) 32 } else { 33 btn.classList.toggle("d-none", !hasChecked) 34 btn.disabled = false 35 btn.removeAttribute("aria-disabled") 36 } 37 }) 38 } 39 } 40 41 // Deselect all checkboxes in this filter group and update the badge counter 42 clearAll(event) { 43 event.preventDefault() 44 45 this.element.querySelectorAll("input.filter-inputs, input.filter-inputs-mobile").forEach(input => { 46 input.checked = false 47 }) 48 49 // Hide the local badge counter (avoid duplicate IDs between desktop/mobile) 50 const counter = this.element.closest('.accordion-item, .dropdown')?.querySelector(`#${this.filterNameValue}_filter_counter`) || 51 this.element.closest('form')?.querySelector(`#${this.filterNameValue}_filter_counter`) || 52 document.getElementById(`${this.filterNameValue}_filter_counter`) 53 if (counter) { 54 counter.textContent = "0" 55 counter.classList.add("d-none") 56 } 57 58 // Enable the submit button so the user can apply the cleared selection. 59 // Use this.element scope first to avoid matching the sibling desktop/mobile 60 // panel when both turbo frames coexist in the DOM. 61 const submitBtn = this.element.querySelector(`#submit_${this.filterNameValue}_mobile, #submit_${this.filterNameValue}`) || 62 document.getElementById(`submit_${this.filterNameValue}_mobile`) || 63 document.getElementById(`submit_${this.filterNameValue}`) 64 if (submitBtn) submitBtn.disabled = false 65 66 // Hide the clear button itself 67 if (this.hasClearBtnTarget) { 68 const isHorizontal = Boolean(this.element.closest('.horizontal-filter-container')) 69 this.clearBtnTargets.forEach(btn => { 70 if (isHorizontal) { 71 btn.classList.remove("d-none") 72 btn.disabled = true 73 btn.setAttribute("aria-disabled", "true") 74 } else { 75 btn.classList.add("d-none") 76 } 77 }) 78 } 79 80 // Clear the search input if present and restore all hidden options 81 if (this.hasSearchInputTarget) { 82 this.searchInputTarget.value = "" 83 this.optionTargets.forEach(option => option.classList.remove("d-none")) 84 } 85 } 86};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.