| |
| |
| |
| |
| |
|
|
|
|
| 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();
|
| }
|
|
|
|
|