feat(helpdesk): align module with core list/drawer standards
- add helpdesk module pages, services, settings and tests - standardize debtor list on drawer/grid contracts and robust filter drawer behavior - add helpdesk aside panel navigation and settings visibility provider - switch primary list slug to helpdesk/debitor and remove helpdesk/search compatibility - include required core contract updates for list contracts and detail/drawer integration
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
@layer components {
|
||||
/* Detail page layout — aside panel, card sections, sticky titlebar integration. */
|
||||
.app-details-container aside {
|
||||
.app-details-container > aside {
|
||||
padding-top: var(--app-spacing);
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
padding: calc(var(--app-spacing) * 2) calc(var(--app-spacing) * 2)
|
||||
calc(var(--app-spacing) * 2) calc(var(--app-spacing) * 0);
|
||||
} */
|
||||
.app-details-container:has(aside) {
|
||||
.app-details-container:has(> aside) {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 2fr) minmax(0, 300px);
|
||||
min-height: 82vh;
|
||||
@@ -158,10 +158,10 @@
|
||||
.app-details-container.is-aside-collapsed {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
.app-details-container.is-aside-collapsed aside {
|
||||
.app-details-container.is-aside-collapsed > aside {
|
||||
display: none;
|
||||
}
|
||||
.app-details-container aside {
|
||||
.app-details-container > aside {
|
||||
border-left: 1px solid var(--app-border);
|
||||
padding: calc(var(--app-spacing) * 2);
|
||||
border-top: none;
|
||||
|
||||
@@ -36,6 +36,8 @@ export function initFilterDrawer(options = {}) {
|
||||
const applyBaseLabel = applyButton ? String(applyButton.textContent || '').trim() : '';
|
||||
const openButtonBaseLabels = new WeakMap();
|
||||
const panel = drawer.querySelector('[data-filter-drawer-panel]') || drawer;
|
||||
const originalParent = drawer.parentNode;
|
||||
const originalNextSibling = drawer.nextSibling;
|
||||
|
||||
const cleanupFns = [];
|
||||
const bind = (target, eventName, handler, bindOptions = undefined) => {
|
||||
@@ -47,6 +49,29 @@ export function initFilterDrawer(options = {}) {
|
||||
let lockedScrollY = 0;
|
||||
let lastTrigger = null;
|
||||
|
||||
const ensureDrawerInBody = () => {
|
||||
if (!document.body || !drawer.isConnected || drawer.parentNode === document.body) {
|
||||
return;
|
||||
}
|
||||
document.body.appendChild(drawer);
|
||||
};
|
||||
|
||||
const restoreDrawerPosition = () => {
|
||||
if (!(originalParent instanceof Node) || !drawer.isConnected) {
|
||||
return;
|
||||
}
|
||||
if (!originalParent.isConnected || drawer.parentNode === originalParent) {
|
||||
return;
|
||||
}
|
||||
if (originalNextSibling && originalNextSibling.parentNode === originalParent) {
|
||||
originalParent.insertBefore(drawer, originalNextSibling);
|
||||
return;
|
||||
}
|
||||
originalParent.appendChild(drawer);
|
||||
};
|
||||
|
||||
ensureDrawerInBody();
|
||||
|
||||
if (!drawer.hasAttribute('role')) {
|
||||
drawer.setAttribute('role', 'dialog');
|
||||
}
|
||||
@@ -115,6 +140,9 @@ export function initFilterDrawer(options = {}) {
|
||||
};
|
||||
|
||||
const setOpen = (nextOpen) => {
|
||||
if (nextOpen) {
|
||||
ensureDrawerInBody();
|
||||
}
|
||||
isOpen = nextOpen;
|
||||
drawer.hidden = !nextOpen;
|
||||
drawer.setAttribute('aria-hidden', nextOpen ? 'false' : 'true');
|
||||
@@ -148,6 +176,21 @@ export function initFilterDrawer(options = {}) {
|
||||
focusFirstField();
|
||||
};
|
||||
|
||||
const normalizeForcedClose = () => {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
isOpen = false;
|
||||
unlockPageScroll();
|
||||
document.body.classList.remove('filter-drawer-open');
|
||||
openButtons.forEach((button) => {
|
||||
button.setAttribute('aria-expanded', 'false');
|
||||
});
|
||||
if (typeof onClose === 'function') {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const open = (trigger = null) => {
|
||||
if (isOpen) {
|
||||
return;
|
||||
@@ -254,6 +297,23 @@ export function initFilterDrawer(options = {}) {
|
||||
};
|
||||
bind(document, 'keydown', onKeyDown);
|
||||
|
||||
const documentObserver = new MutationObserver(() => {
|
||||
if (!drawer.isConnected || drawer.hidden) {
|
||||
normalizeForcedClose();
|
||||
}
|
||||
});
|
||||
if (document.body) {
|
||||
documentObserver.observe(document.body, { childList: true, subtree: true });
|
||||
cleanupFns.push(() => documentObserver.disconnect());
|
||||
}
|
||||
const drawerAttributeObserver = new MutationObserver(() => {
|
||||
if (drawer.hidden) {
|
||||
normalizeForcedClose();
|
||||
}
|
||||
});
|
||||
drawerAttributeObserver.observe(drawer, { attributes: true, attributeFilter: ['hidden'] });
|
||||
cleanupFns.push(() => drawerAttributeObserver.disconnect());
|
||||
|
||||
const setApplyEnabled = (enabled) => {
|
||||
if (!applyButton) {
|
||||
return;
|
||||
@@ -314,6 +374,9 @@ export function initFilterDrawer(options = {}) {
|
||||
if (isOpen) {
|
||||
close('discard');
|
||||
}
|
||||
unlockPageScroll();
|
||||
document.body.classList.remove('filter-drawer-open');
|
||||
restoreDrawerPosition();
|
||||
delete drawer.dataset.filterDrawerBound;
|
||||
delete drawer._filterDrawerApi;
|
||||
};
|
||||
|
||||
@@ -1370,6 +1370,35 @@ const normalizeListMode = (value) => {
|
||||
return 'drawer';
|
||||
};
|
||||
|
||||
const resolveDrawerRoot = (gridConfig, filterOptions = {}) => {
|
||||
const explicitRoot = filterOptions?.drawerRoot;
|
||||
if (explicitRoot) {
|
||||
return resolveDomQueryElement(explicitRoot) || document;
|
||||
}
|
||||
|
||||
const containerEl = gridConfig?.container instanceof Element
|
||||
? gridConfig.container
|
||||
: null;
|
||||
if (!containerEl) {
|
||||
return document;
|
||||
}
|
||||
|
||||
const drawerSelector = String(filterOptions?.drawer?.drawerSelector || '[data-filter-drawer]').trim() || '[data-filter-drawer]';
|
||||
const openSelector = String(filterOptions?.drawer?.openSelector || '[data-filter-drawer-open]').trim() || '[data-filter-drawer-open]';
|
||||
|
||||
let current = containerEl;
|
||||
while (current && current !== document.documentElement) {
|
||||
const hasDrawer = Boolean(current.querySelector(drawerSelector));
|
||||
const hasOpenButton = Boolean(current.querySelector(openSelector));
|
||||
if (hasDrawer && hasOpenButton) {
|
||||
return current;
|
||||
}
|
||||
current = current.parentElement;
|
||||
}
|
||||
|
||||
return containerEl.parentElement || document;
|
||||
};
|
||||
|
||||
/**
|
||||
* Standardized list-page bootstrap:
|
||||
* - builds grid filters from schema (or uses explicit filters)
|
||||
@@ -1425,7 +1454,7 @@ export function initStandardListPage(options = {}) {
|
||||
const chipUiConfig = filterOptions.chips && typeof filterOptions.chips === 'object'
|
||||
? filterOptions.chips
|
||||
: {};
|
||||
const drawerRoot = filterOptions.drawerRoot || document;
|
||||
const drawerRoot = resolveDrawerRoot(gridConfig, filterOptions);
|
||||
const domLabels = readDomLabelDefaults({
|
||||
drawerRoot,
|
||||
chipsSelector: chipUiConfig.container || '[data-active-filter-chips]',
|
||||
|
||||
Reference in New Issue
Block a user