|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MenuSystem {
|
|
|
constructor() {
|
|
|
this.menus = new Map();
|
|
|
this.activeMenu = null;
|
|
|
this.init();
|
|
|
}
|
|
|
|
|
|
init() {
|
|
|
this.setupDropdownMenus();
|
|
|
this.setupContextMenus();
|
|
|
this.setupMobileMenus();
|
|
|
this.setupSubmenus();
|
|
|
this.setupKeyboardNavigation();
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setupDropdownMenus() {
|
|
|
document.querySelectorAll('[data-menu-trigger]').forEach(trigger => {
|
|
|
const menuId = trigger.dataset.menuTrigger;
|
|
|
const menu = document.querySelector(`[data-menu="${menuId}"]`);
|
|
|
|
|
|
if (!menu) return;
|
|
|
|
|
|
|
|
|
menu.style.display = 'block';
|
|
|
menu.style.visibility = 'hidden';
|
|
|
|
|
|
this.menus.set(menuId, { trigger, menu, type: 'dropdown' });
|
|
|
|
|
|
trigger.addEventListener('click', (e) => {
|
|
|
e.stopPropagation();
|
|
|
this.toggleMenu(menuId);
|
|
|
});
|
|
|
|
|
|
|
|
|
menu.querySelectorAll('.menu-item').forEach(item => {
|
|
|
item.addEventListener('click', (e) => {
|
|
|
e.stopPropagation();
|
|
|
const action = item.dataset.action;
|
|
|
if (action) {
|
|
|
this.handleMenuAction(action);
|
|
|
}
|
|
|
this.closeMenu(menu);
|
|
|
});
|
|
|
});
|
|
|
});
|
|
|
|
|
|
|
|
|
document.addEventListener('click', (e) => {
|
|
|
if (!e.target.closest('[data-menu]') && !e.target.closest('[data-menu-trigger]')) {
|
|
|
this.closeAllMenus();
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setupContextMenus() {
|
|
|
document.querySelectorAll('[data-context-menu]').forEach(element => {
|
|
|
const menuId = element.dataset.contextMenu;
|
|
|
const menu = document.querySelector(`[data-context-menu-target="${menuId}"]`);
|
|
|
|
|
|
if (!menu) return;
|
|
|
|
|
|
element.addEventListener('contextmenu', (e) => {
|
|
|
e.preventDefault();
|
|
|
this.showContextMenu(menu, e.clientX, e.clientY);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
|
|
|
document.addEventListener('click', () => {
|
|
|
document.querySelectorAll('[data-context-menu-target]').forEach(menu => {
|
|
|
menu.classList.remove('context-menu-open');
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setupMobileMenus() {
|
|
|
const mobileMenuToggle = document.querySelector('[data-mobile-menu-toggle]');
|
|
|
const mobileMenu = document.querySelector('[data-mobile-menu]');
|
|
|
|
|
|
if (mobileMenuToggle && mobileMenu) {
|
|
|
mobileMenuToggle.addEventListener('click', () => {
|
|
|
mobileMenu.classList.toggle('mobile-menu-open');
|
|
|
mobileMenuToggle.classList.toggle('mobile-menu-active');
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setupSubmenus() {
|
|
|
document.querySelectorAll('[data-submenu-trigger]').forEach(trigger => {
|
|
|
const submenu = trigger.nextElementSibling;
|
|
|
if (!submenu || !submenu.classList.contains('submenu')) return;
|
|
|
|
|
|
trigger.addEventListener('mouseenter', () => {
|
|
|
this.showSubmenu(submenu, trigger);
|
|
|
});
|
|
|
|
|
|
trigger.addEventListener('mouseleave', () => {
|
|
|
setTimeout(() => {
|
|
|
if (!submenu.matches(':hover')) {
|
|
|
this.hideSubmenu(submenu);
|
|
|
}
|
|
|
}, 200);
|
|
|
});
|
|
|
|
|
|
submenu.addEventListener('mouseleave', () => {
|
|
|
this.hideSubmenu(submenu);
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setupKeyboardNavigation() {
|
|
|
document.addEventListener('keydown', (e) => {
|
|
|
|
|
|
if (e.key === 'Escape') {
|
|
|
this.closeAllMenus();
|
|
|
}
|
|
|
|
|
|
|
|
|
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
|
const activeMenu = document.querySelector('.menu-open, .context-menu-open');
|
|
|
if (activeMenu) {
|
|
|
e.preventDefault();
|
|
|
this.navigateMenu(activeMenu, e.key === 'ArrowDown' ? 1 : -1);
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
toggleMenu(menuId) {
|
|
|
const menuData = this.menus.get(menuId);
|
|
|
if (!menuData) return;
|
|
|
|
|
|
const { menu, trigger } = menuData;
|
|
|
|
|
|
|
|
|
if (this.activeMenu && this.activeMenu !== menu) {
|
|
|
this.closeMenu(this.activeMenu);
|
|
|
}
|
|
|
|
|
|
|
|
|
if (menu.classList.contains('menu-open')) {
|
|
|
this.closeMenu(menu);
|
|
|
} else {
|
|
|
this.openMenu(menu, trigger);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
openMenu(menu, trigger) {
|
|
|
menu.style.visibility = 'visible';
|
|
|
menu.classList.add('menu-open');
|
|
|
trigger?.classList.add('menu-trigger-active');
|
|
|
this.activeMenu = menu;
|
|
|
|
|
|
|
|
|
this.animateMenuIn(menu, trigger);
|
|
|
}
|
|
|
|
|
|
closeMenu(menu) {
|
|
|
menu.classList.remove('menu-open');
|
|
|
const trigger = Array.from(this.menus.values()).find(m => m.menu === menu)?.trigger;
|
|
|
trigger?.classList.remove('menu-trigger-active');
|
|
|
|
|
|
if (this.activeMenu === menu) {
|
|
|
this.activeMenu = null;
|
|
|
}
|
|
|
|
|
|
|
|
|
this.animateMenuOut(menu);
|
|
|
}
|
|
|
|
|
|
closeAllMenus() {
|
|
|
document.querySelectorAll('.menu-open, .context-menu-open').forEach(menu => {
|
|
|
this.closeMenu(menu);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
showContextMenu(menu, x, y) {
|
|
|
|
|
|
document.querySelectorAll('[data-context-menu-target]').forEach(m => {
|
|
|
m.classList.remove('context-menu-open');
|
|
|
});
|
|
|
|
|
|
menu.style.left = `${x}px`;
|
|
|
menu.style.top = `${y}px`;
|
|
|
menu.classList.add('context-menu-open');
|
|
|
this.activeMenu = menu;
|
|
|
|
|
|
this.animateMenuIn(menu);
|
|
|
}
|
|
|
|
|
|
showSubmenu(submenu, trigger) {
|
|
|
const triggerRect = trigger.getBoundingClientRect();
|
|
|
submenu.style.top = `${triggerRect.top}px`;
|
|
|
submenu.style.left = `${triggerRect.right + 8}px`;
|
|
|
submenu.classList.add('submenu-open');
|
|
|
}
|
|
|
|
|
|
hideSubmenu(submenu) {
|
|
|
submenu.classList.remove('submenu-open');
|
|
|
}
|
|
|
|
|
|
navigateMenu(menu, direction) {
|
|
|
const items = menu.querySelectorAll('.menu-item:not(.disabled)');
|
|
|
if (items.length === 0) return;
|
|
|
|
|
|
let currentIndex = Array.from(items).findIndex(item => item.classList.contains('menu-item-active'));
|
|
|
|
|
|
if (currentIndex === -1) {
|
|
|
currentIndex = direction > 0 ? 0 : items.length - 1;
|
|
|
} else {
|
|
|
currentIndex += direction;
|
|
|
if (currentIndex < 0) currentIndex = items.length - 1;
|
|
|
if (currentIndex >= items.length) currentIndex = 0;
|
|
|
}
|
|
|
|
|
|
items.forEach((item, index) => {
|
|
|
item.classList.toggle('menu-item-active', index === currentIndex);
|
|
|
});
|
|
|
|
|
|
items[currentIndex]?.focus();
|
|
|
}
|
|
|
|
|
|
animateMenuIn(menu, trigger) {
|
|
|
menu.style.opacity = '0';
|
|
|
menu.style.transform = 'translateY(-10px) scale(0.95)';
|
|
|
menu.style.pointerEvents = 'none';
|
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
menu.style.transition = 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)';
|
|
|
menu.style.opacity = '1';
|
|
|
menu.style.transform = 'translateY(0) scale(1)';
|
|
|
menu.style.pointerEvents = 'auto';
|
|
|
});
|
|
|
}
|
|
|
|
|
|
animateMenuOut(menu) {
|
|
|
menu.style.transition = 'all 0.15s cubic-bezier(0.4, 0, 0.2, 1)';
|
|
|
menu.style.opacity = '0';
|
|
|
menu.style.transform = 'translateY(-10px) scale(0.95)';
|
|
|
|
|
|
setTimeout(() => {
|
|
|
menu.style.pointerEvents = 'none';
|
|
|
menu.style.visibility = 'hidden';
|
|
|
}, 150);
|
|
|
}
|
|
|
|
|
|
handleMenuAction(action) {
|
|
|
switch(action) {
|
|
|
case 'theme-light':
|
|
|
document.body.setAttribute('data-theme', 'light');
|
|
|
break;
|
|
|
case 'theme-dark':
|
|
|
document.body.setAttribute('data-theme', 'dark');
|
|
|
break;
|
|
|
case 'settings':
|
|
|
|
|
|
const settingsBtn = document.querySelector('[data-nav="page-settings"]');
|
|
|
if (settingsBtn) settingsBtn.click();
|
|
|
break;
|
|
|
default:
|
|
|
console.log('Menu action:', action);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
if (document.readyState === 'loading') {
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
window.menuSystem = new MenuSystem();
|
|
|
});
|
|
|
} else {
|
|
|
window.menuSystem = new MenuSystem();
|
|
|
}
|
|
|
|
|
|
|