104 lines
3.3 KiB
TypeScript
104 lines
3.3 KiB
TypeScript
// Reusable checkbox dropdown filter (multi-select) — ported from the original
|
||
// createDropdown(). Expects markup: #<id>-dropdown, #<id>-btn, #<id>-btn-text,
|
||
// #<id>-panel already present in the DOM.
|
||
|
||
export interface Dropdown {
|
||
setValues(values: string[], label: string): void;
|
||
getSelected(): string[];
|
||
onChange(fn: () => void): void;
|
||
}
|
||
|
||
export function createDropdown(id: string): Dropdown {
|
||
const root = document.getElementById(id + '-dropdown') as HTMLElement;
|
||
const btn = document.getElementById(id + '-btn') as HTMLElement;
|
||
const btnText = document.getElementById(id + '-btn-text') as HTMLElement;
|
||
const panel = document.getElementById(id + '-panel') as HTMLElement;
|
||
|
||
let allValues: string[] = [];
|
||
let selected = new Set<string>();
|
||
let defaultLabel = '';
|
||
let onChange: () => void = () => {};
|
||
|
||
function updateBtnText(): void {
|
||
if (selected.size === 0) btnText.textContent = defaultLabel;
|
||
else if (selected.size === allValues.length) btnText.textContent = 'Все (' + allValues.length + ')';
|
||
else if (selected.size <= 2) btnText.textContent = Array.from(selected).join(', ');
|
||
else btnText.textContent = 'Выбрано: ' + selected.size;
|
||
}
|
||
|
||
function syncCheckboxes(): void {
|
||
panel.querySelectorAll<HTMLInputElement>('input[type=checkbox]').forEach((cb) => {
|
||
cb.checked = selected.has(cb.value);
|
||
});
|
||
}
|
||
|
||
function renderPanel(): void {
|
||
panel.innerHTML = '';
|
||
const actions = document.createElement('div');
|
||
actions.className = 'dp-actions';
|
||
const selAll = document.createElement('a');
|
||
selAll.textContent = 'Выбрать все';
|
||
selAll.addEventListener('click', () => {
|
||
selected = new Set(allValues);
|
||
syncCheckboxes();
|
||
updateBtnText();
|
||
onChange();
|
||
});
|
||
const selNone = document.createElement('a');
|
||
selNone.textContent = 'Сбросить';
|
||
selNone.addEventListener('click', () => {
|
||
selected.clear();
|
||
syncCheckboxes();
|
||
updateBtnText();
|
||
onChange();
|
||
});
|
||
actions.appendChild(selAll);
|
||
actions.appendChild(selNone);
|
||
panel.appendChild(actions);
|
||
|
||
for (const v of allValues) {
|
||
const label = document.createElement('label');
|
||
const cb = document.createElement('input');
|
||
cb.type = 'checkbox';
|
||
cb.value = v;
|
||
cb.checked = selected.has(v);
|
||
cb.addEventListener('change', () => {
|
||
if (cb.checked) selected.add(v);
|
||
else selected.delete(v);
|
||
updateBtnText();
|
||
onChange();
|
||
});
|
||
label.appendChild(cb);
|
||
label.appendChild(document.createTextNode(v));
|
||
panel.appendChild(label);
|
||
}
|
||
}
|
||
|
||
btn.addEventListener('click', (e) => {
|
||
e.stopPropagation();
|
||
document.querySelectorAll('.dropdown.open').forEach((d) => {
|
||
if (d !== root) d.classList.remove('open');
|
||
});
|
||
root.classList.toggle('open');
|
||
});
|
||
document.addEventListener('click', (e) => {
|
||
if (!root.contains(e.target as Node)) root.classList.remove('open');
|
||
});
|
||
|
||
return {
|
||
setValues(values: string[], label: string): void {
|
||
allValues = values;
|
||
defaultLabel = label;
|
||
selected = new Set();
|
||
updateBtnText();
|
||
renderPanel();
|
||
},
|
||
getSelected(): string[] {
|
||
return Array.from(selected);
|
||
},
|
||
onChange(fn: () => void): void {
|
||
onChange = fn;
|
||
},
|
||
};
|
||
}
|