- 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>
99 lines
3.0 KiB
JavaScript
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();
|
|
};
|