feat(109): checkbox multiselect filters
Phase 109 complete: - Add checkbox dropdown enhancement for statistics multi-select filters - Preserve GET contract for channels[] and status_groups[] - Update PAUL plan context to read .paul/codebase docs Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
164
public/assets/js/modules/checkbox-multiselect.js
Normal file
164
public/assets/js/modules/checkbox-multiselect.js
Normal file
@@ -0,0 +1,164 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var ENHANCED_ATTR = 'data-checkbox-multiselect-enhanced';
|
||||
|
||||
function optionList(select) {
|
||||
return Array.prototype.slice.call(select.options).filter(function (option) {
|
||||
return option.value !== '';
|
||||
});
|
||||
}
|
||||
|
||||
function selectedOptions(select) {
|
||||
return optionList(select).filter(function (option) {
|
||||
return option.selected;
|
||||
});
|
||||
}
|
||||
|
||||
function selectedLabel(select) {
|
||||
var selectedCount = selectedOptions(select).length;
|
||||
if (selectedCount === 0) {
|
||||
return select.dataset.emptyLabel || 'Nic nie wybrano';
|
||||
}
|
||||
|
||||
var suffix = selectedCount === 1
|
||||
? (select.dataset.selectedLabelSingular || 'zaznaczono')
|
||||
: (select.dataset.selectedLabelPlural || 'zaznaczono');
|
||||
|
||||
return selectedCount + ' ' + suffix;
|
||||
}
|
||||
|
||||
function setOpen(wrapper, isOpen) {
|
||||
wrapper.classList.toggle('is-open', isOpen);
|
||||
wrapper.querySelector('.checkbox-multiselect__trigger').setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
||||
}
|
||||
|
||||
function updateAllState(select, allCheckbox) {
|
||||
var options = optionList(select);
|
||||
var selectedCount = selectedOptions(select).length;
|
||||
|
||||
allCheckbox.checked = options.length > 0 && selectedCount === options.length;
|
||||
allCheckbox.indeterminate = selectedCount > 0 && selectedCount < options.length;
|
||||
}
|
||||
|
||||
function syncTrigger(select, wrapper) {
|
||||
wrapper.querySelector('.checkbox-multiselect__value').textContent = selectedLabel(select);
|
||||
updateAllState(select, wrapper.querySelector('.checkbox-multiselect__all'));
|
||||
}
|
||||
|
||||
function createCheckbox(option, select, wrapper, index) {
|
||||
var row = document.createElement('label');
|
||||
row.className = 'checkbox-multiselect__option';
|
||||
|
||||
var checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.checked = option.selected;
|
||||
checkbox.value = option.value;
|
||||
checkbox.dataset.optionIndex = String(index);
|
||||
|
||||
var text = document.createElement('span');
|
||||
text.textContent = option.text;
|
||||
|
||||
checkbox.addEventListener('change', function () {
|
||||
option.selected = checkbox.checked;
|
||||
syncTrigger(select, wrapper);
|
||||
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
});
|
||||
|
||||
row.appendChild(checkbox);
|
||||
row.appendChild(text);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
function enhanceSelect(select) {
|
||||
if (select.hasAttribute(ENHANCED_ATTR)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var wrapper = document.createElement('div');
|
||||
wrapper.className = 'checkbox-multiselect';
|
||||
|
||||
var trigger = document.createElement('button');
|
||||
trigger.type = 'button';
|
||||
trigger.className = 'checkbox-multiselect__trigger';
|
||||
trigger.setAttribute('aria-haspopup', 'listbox');
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
|
||||
var value = document.createElement('span');
|
||||
value.className = 'checkbox-multiselect__value';
|
||||
value.textContent = selectedLabel(select);
|
||||
|
||||
var arrow = document.createElement('span');
|
||||
arrow.className = 'checkbox-multiselect__arrow';
|
||||
arrow.setAttribute('aria-hidden', 'true');
|
||||
|
||||
var dropdown = document.createElement('div');
|
||||
dropdown.className = 'checkbox-multiselect__dropdown';
|
||||
dropdown.setAttribute('role', 'listbox');
|
||||
|
||||
var allRow = document.createElement('label');
|
||||
allRow.className = 'checkbox-multiselect__option checkbox-multiselect__option--all';
|
||||
|
||||
var allCheckbox = document.createElement('input');
|
||||
allCheckbox.type = 'checkbox';
|
||||
allCheckbox.className = 'checkbox-multiselect__all';
|
||||
|
||||
var allText = document.createElement('span');
|
||||
allText.textContent = select.dataset.allLabel || 'Wszystkie';
|
||||
|
||||
allCheckbox.addEventListener('change', function () {
|
||||
var shouldSelect = allCheckbox.checked;
|
||||
optionList(select).forEach(function (option) {
|
||||
option.selected = shouldSelect;
|
||||
});
|
||||
wrapper.querySelectorAll('.checkbox-multiselect__option input:not(.checkbox-multiselect__all)').forEach(function (checkbox) {
|
||||
checkbox.checked = shouldSelect;
|
||||
});
|
||||
syncTrigger(select, wrapper);
|
||||
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
});
|
||||
|
||||
allRow.appendChild(allCheckbox);
|
||||
allRow.appendChild(allText);
|
||||
dropdown.appendChild(allRow);
|
||||
|
||||
optionList(select).forEach(function (option, index) {
|
||||
dropdown.appendChild(createCheckbox(option, select, wrapper, index));
|
||||
});
|
||||
|
||||
trigger.appendChild(value);
|
||||
trigger.appendChild(arrow);
|
||||
wrapper.appendChild(trigger);
|
||||
wrapper.appendChild(dropdown);
|
||||
|
||||
select.parentNode.insertBefore(wrapper, select);
|
||||
wrapper.appendChild(select);
|
||||
select.setAttribute(ENHANCED_ATTR, '1');
|
||||
|
||||
trigger.addEventListener('click', function () {
|
||||
setOpen(wrapper, !wrapper.classList.contains('is-open'));
|
||||
});
|
||||
|
||||
wrapper.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Escape') {
|
||||
setOpen(wrapper, false);
|
||||
trigger.focus();
|
||||
}
|
||||
});
|
||||
|
||||
syncTrigger(select, wrapper);
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (event) {
|
||||
document.querySelectorAll('.checkbox-multiselect.is-open').forEach(function (wrapper) {
|
||||
if (!wrapper.contains(event.target)) {
|
||||
setOpen(wrapper, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('select[data-checkbox-multiselect]').forEach(enhanceSelect);
|
||||
});
|
||||
}());
|
||||
Reference in New Issue
Block a user