Files
breadcrumb-the-shire/web/js/components/app-multiselect-cascade.js
fs 25370a1a55 add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00

99 lines
3.0 KiB
JavaScript

import { getMultiSelectInstance } from './app-multiselect-init.js';
import { warnOnce } from '../core/app-dom.js';
const parseValueList = (value) =>
String(value || '')
.split(',')
.map((item) => item.trim())
.filter(Boolean);
const setDisabledState = (option, disabled) => {
option.dataset.disabled = disabled ? '1' : '0';
option.classList.toggle('is-disabled', disabled);
option.setAttribute('aria-disabled', disabled ? 'true' : 'false');
option.style.pointerEvents = disabled ? 'none' : '';
if (!option._cascadeGuard) {
option.addEventListener(
'click',
(event) => {
if (option.dataset.disabled === '1') {
event.preventDefault();
event.stopImmediatePropagation();
}
},
true
);
option._cascadeGuard = true;
}
};
const toggleSelectAll = (instance, disabled) => {
const selectAll = instance?.element?.querySelector('.multi-select-all');
if (!selectAll) {return;}
selectAll.classList.toggle('is-disabled', disabled);
selectAll.setAttribute('aria-disabled', disabled ? 'true' : 'false');
selectAll.style.pointerEvents = disabled ? 'none' : '';
};
export const initMultiSelectCascade = ({
parent,
child,
map = {},
mode = 'disable',
clearInvalid = true,
}) => {
const parentInput = typeof parent === 'string' ? document.querySelector(parent) : parent;
const childInput = typeof child === 'string' ? document.querySelector(child) : child;
if (!parentInput || !childInput) {
if (!parentInput) {
warnOnce('UI_EL_MISSING', `Missing multiselect parent: ${parent}`, { module: 'multiselect-cascade' });
}
if (!childInput) {
warnOnce('UI_EL_MISSING', `Missing multiselect child: ${child}`, { module: 'multiselect-cascade' });
}
return;
}
const childInstance = getMultiSelectInstance(childInput);
if (!childInstance) {
warnOnce('UI_INIT_MISSING', 'Missing multiselect instance for child', { module: 'multiselect-cascade' });
return;
}
const apply = () => {
const selectedParents = parseValueList(parentInput.value);
const allowAll = selectedParents.length === 0;
const allowed = new Set();
if (!allowAll) {
selectedParents.forEach((id) => {
const entries = map[id] || [];
entries.forEach((entry) => allowed.add(String(entry)));
});
}
if (clearInvalid && !allowAll) {
const selected = childInstance.selectedValues || [];
selected.forEach((value) => {
if (!allowed.has(String(value))) {
childInstance.unselect(value);
}
});
}
const options = childInstance.element.querySelectorAll('.multi-select-option');
let disabledCount = 0;
options.forEach((option) => {
const isAllowed = allowAll || allowed.has(String(option.dataset.value || ''));
const disabled = mode === 'disable' ? !isAllowed : false;
if (disabled) {disabledCount += 1;}
setDisabledState(option, disabled);
});
toggleSelectAll(childInstance, disabledCount > 0);
};
parentInput.addEventListener('change', apply);
apply();
};