95 lines
3.3 KiB
JavaScript
95 lines
3.3 KiB
JavaScript
function wireAdminDropdowns() {
|
||
const reposition = (toggle, menu) => {
|
||
const rect = toggle.getBoundingClientRect();
|
||
const gutter = 8;
|
||
const menuWidth = menu.offsetWidth || 220;
|
||
const menuHeight = menu.offsetHeight || 0;
|
||
|
||
// Prefer placing the menu to the right of the toggle; clamp within viewport.
|
||
let left = rect.right + gutter;
|
||
if (left + menuWidth > window.innerWidth - gutter) {
|
||
left = Math.max(gutter, window.innerWidth - menuWidth - gutter);
|
||
}
|
||
|
||
let top = Math.max(gutter, Math.min(rect.top, window.innerHeight - menuHeight - gutter));
|
||
|
||
menu.style.position = 'fixed';
|
||
menu.style.transform = 'none';
|
||
menu.style.top = `${top}px`;
|
||
menu.style.left = `${left}px`;
|
||
menu.style.right = 'auto';
|
||
menu.style.minWidth = `${Math.max(rect.width, 160)}px`;
|
||
menu.style.maxWidth = '260px';
|
||
menu.style.display = 'block';
|
||
menu.dataset.dropdownOpen = '1';
|
||
menu.classList.add('show');
|
||
};
|
||
|
||
const resetMenu = (menu) => {
|
||
menu.style.position = '';
|
||
menu.style.transform = '';
|
||
menu.style.top = '';
|
||
menu.style.left = '';
|
||
menu.style.right = '';
|
||
menu.style.minWidth = '';
|
||
menu.style.maxWidth = '';
|
||
menu.style.display = '';
|
||
menu.classList.remove('show');
|
||
delete menu.dataset.dropdownOpen;
|
||
};
|
||
|
||
document.querySelectorAll('.admin-nav .dropdown-toggle').forEach((toggle) => {
|
||
if (toggle.dataset.dropdownWired) return;
|
||
toggle.dataset.dropdownWired = '1';
|
||
const menu = toggle.nextElementSibling;
|
||
if (!menu) return;
|
||
|
||
toggle.addEventListener('shown.bs.dropdown', () => reposition(toggle, menu));
|
||
toggle.addEventListener('hide.bs.dropdown', () => resetMenu(menu));
|
||
|
||
window.addEventListener('scroll', () => {
|
||
if (menu.dataset.dropdownOpen) reposition(toggle, menu);
|
||
}, { passive: true });
|
||
window.addEventListener('resize', () => {
|
||
if (menu.dataset.dropdownOpen) reposition(toggle, menu);
|
||
});
|
||
});
|
||
}
|
||
|
||
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||
wireAdminDropdowns();
|
||
} else {
|
||
document.addEventListener('DOMContentLoaded', wireAdminDropdowns);
|
||
}
|
||
|
||
// 1) Delegate clicks on any .toggle-icon, now or in the future:
|
||
document.addEventListener('click', function(e) {
|
||
// Find the closest .toggle-icon ancestor of the click target, if any
|
||
if (!e.target.classList.contains('toggle-icon')) return;
|
||
var icon = e.target;
|
||
|
||
// Prevent clicks on the icon from bubbling further if you want
|
||
e.stopPropagation();
|
||
|
||
// Locate its .card-body.collapsable in the same card
|
||
var card = icon.closest('.card');
|
||
if (!card) return;
|
||
var body = card.querySelector('.card-body.collapsable');
|
||
if (!body) return;
|
||
|
||
// Toggle the 'show' class and swap +/– text
|
||
if (body.classList.contains('show')) {
|
||
// Collapse: remove show, update icon, remove from set
|
||
body.classList.remove('show');
|
||
icon.textContent = '+';
|
||
icon.setAttribute('aria-expanded', 'false');
|
||
openCards.add(key);
|
||
} else {
|
||
// Expand: add show, update icon, add to set
|
||
body.classList.add('show');
|
||
icon.textContent = '–';
|
||
icon.setAttribute('aria-expanded', 'true');
|
||
openCards.add(key);
|
||
}
|
||
});
|