PageSourceSearch

https://rozdilnianskahromada.gov.ua/js/subdomain/modern/filter-menu.js

js rozdilnianskahromada.gov.ua collected 2026-09-25 04:26:43 UTC 6,289 bytes, 158 lines download raw bytes

1export function filterMenuInit() {
2    initClearButton()
3    changeIconOnOpen()
4    addTagStyling()
5    syncSelectedTagChips()
6}
7
8function initClearButton() {
9    const form = document.querySelector(".filter-form")
10    if (!form) return
11    const clearButton = form.querySelector(".button-filter-clear")
12    if (!clearButton) return
13
14    clearButton.addEventListener('click', () => {
15        const checkboxes = form.querySelectorAll('input[type="checkbox"]:not(.hidden)');
16
17        checkboxes.forEach(checkbox => {
18            checkbox.checked = false;
19        });
20    })
21}
22
23function changeIconOnOpen() {
24    const form = document.querySelector(".filter-form")
25    if (!form) return
26    const filterToggle = form.querySelector("#filter-toggle")
27    if (!filterToggle) return
28
29    if (filterToggle.checked) {
30        form.querySelector(".filter-closed").classList.add("hidden")
31        form.querySelector(".filter-open").classList.remove("hidden")
32    } else {
33        form.querySelector(".filter-closed").classList.remove("hidden")
34        form.querySelector(".filter-open").classList.add("hidden")
35    }
36
37    filterToggle.addEventListener("change", () => {
38        if (filterToggle.checked) {
39            form.querySelector(".filter-closed").classList.add("hidden")
40            form.querySelector(".filter-open").classList.remove("hidden")
41        } else {
42            form.querySelector(".filter-closed").classList.remove("hidden")
43            form.querySelector(".filter-open").classList.add("hidden")
44        }
45    })
46}
47
48function addTagStyling() {
49    const form = document.querySelector(".filter-form")
50    if (!form) return
51
52    const labelsToStyle = form.querySelectorAll(".label-tag")
53
54    function updateLabelClasses() {
55        labelsToStyle.forEach(label => {
56            const attrValue = label.getAttribute('for');
57            const checkbox = form.querySelector(`input[type="checkbox"][id="${attrValue}"]`);
58            
59            // selected = filled, unselected = outlined (same as the deployed site)
60            if (checkbox && checkbox.checked) {
61                label.classList.add("bg-colored-80");
62                label.classList.add("border-transparent");
63                label.classList.remove("border-black");
64            } else {
65                label.classList.remove("bg-colored-80");
66                label.classList.remove("border-transparent");
67                label.classList.add("border-black");
68            }
69        });
70    }
71
72    form.querySelectorAll('input[type="checkbox"]:not(.hidden)').forEach(checkbox => {
73        checkbox.addEventListener('change', updateLabelClasses);
74    });
75
76    updateLabelClasses();
77
78}
79
80// selected tags → removable chips under the tag row; the tags[] checkboxes remain the source of truth
81function syncSelectedTagChips() {
82    const form = document.querySelector(".filter-form")
83    if (!form) return
84    const strip = form.querySelector(".filter-selected-tags")
85    if (!strip) return
86    const template = strip.querySelector(".filter-tag-chip-template")
87    if (!template) return
88
89    const clearAll = strip.querySelector(".filter-selected-tags-clear")
90    const status = form.querySelector(".filter-selected-tags-status")
91    const tagInputs = () => form.querySelectorAll('input[type="checkbox"][name="tags[]"]')
92
93    function announce(message) {
94        if (!status) return
95        status.textContent = ""
96        // re-set after a tick so identical consecutive messages are still announced
97        setTimeout(() => { status.textContent = message }, 50)
98    }
99
100    // after a chip is removed its button is gone; move focus to the neighbouring chip,
101    // then the clear button, and finally the visible submit button when the strip hides
102    function focusAfterRemoval(index) {
103        const chips = strip.querySelectorAll(".filter-tag-chip")
104        const next = chips[index] || chips[chips.length - 1]
105        if (next) return next.focus()
106        const submit = Array.from(form.querySelectorAll('button[type="submit"]')).find(button => button.offsetParent !== null)
107        if (submit) submit.focus()
108    }
109
110    function render() {
111        strip.querySelectorAll(".filter-tag-chip").forEach(chip => chip.remove())
112
113        const checked = Array.from(tagInputs()).filter(input => input.checked)
114        checked.forEach((input, index) => {
115            const chip = template.content.firstElementChild.cloneNode(true)
116            chip.dataset.tag = input.value
117            chip.setAttribute("aria-label", `Прибрати тему ${input.value}`)
118            chip.querySelector(".filter-tag-chip-name").textContent = input.value
119            chip.addEventListener("click", () => {
120                uncheck([input])
121                announce(`Тему «${input.value}» прибрано`)
122                focusAfterRemoval(index)
123            })
124            // chips go before the "Скинути все" button, in checkbox order
125            strip.insertBefore(chip, clearAll || template)
126        })
127
128        strip.classList.toggle("hidden", checked.length === 0)
129        strip.classList.toggle("flex", checked.length > 0)
130        // desktop: the chips take the place of the two quick-pick tags, and the tag area gets a fixed width so the trigger stays put
131        form.querySelectorAll(".label-tag").forEach(label => label.classList.toggle("md:block", checked.length === 0))
132        const area = form.querySelector(".filter-tags-area")
133        if (area) area.classList.toggle("md:w-xl", checked.length > 0)
134    }
135
136    function uncheck(inputs) {
137        inputs.forEach(input => {
138            input.checked = false
139            // notify addTagStyling() so the quick-pick label drops its selected border
140            input.dispatchEvent(new Event("change", { bubbles: true }))
141        })
142        render()
143    }
144
145    tagInputs().forEach(input => input.addEventListener("change", render))
146
147    if (clearAll) clearAll.addEventListener("click", () => {
148        uncheck(Array.from(tagInputs()))
149        announce("Усі обрані теми скинуто")
150        focusAfterRemoval(0)
151    })
152
153    // mobile "Скинути" unchecks everything without firing change events
154    const clearButton = form.querySelector(".button-filter-clear")
155    if (clearButton) clearButton.addEventListener("click", render)
156
157    render()
158}

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.