UNPKG

basecoat-css

Version:

Tailwind CSS for Basecoat components

157 lines (134 loc) 4.83 kB
(() => { const initDropdownMenu = (dropdownMenuComponent) => { const trigger = dropdownMenuComponent.querySelector(':scope > button'); const popover = dropdownMenuComponent.querySelector(':scope > [data-popover]'); const menu = popover.querySelector('[role="menu"]'); if (!trigger || !menu || !popover) { const missing = []; if (!trigger) missing.push('trigger'); if (!menu) missing.push('menu'); if (!popover) missing.push('popover'); console.error(`Dropdown menu initialisation failed. Missing element(s): ${missing.join(', ')}`, dropdownMenuComponent); return; } let menuItems = []; let activeIndex = -1; const closePopover = (focusOnTrigger = true) => { if (trigger.getAttribute('aria-expanded') === 'false') return; trigger.setAttribute('aria-expanded', 'false'); trigger.removeAttribute('aria-activedescendant'); popover.setAttribute('aria-hidden', 'true'); if (focusOnTrigger) { trigger.focus(); } setActiveItem(-1); }; const openPopover = () => { document.dispatchEvent(new CustomEvent('basecoat:popover', { detail: { source: dropdownMenuComponent } })); trigger.setAttribute('aria-expanded', 'true'); popover.setAttribute('aria-hidden', 'false'); menuItems = Array.from(menu.querySelectorAll('[role^="menuitem"]')).filter(item => !item.hasAttribute('disabled') && item.getAttribute('aria-disabled') !== 'true' ); if (menuItems.length > 0) { setActiveItem(0); } }; const setActiveItem = (index) => { if (activeIndex > -1 && menuItems[activeIndex]) { menuItems[activeIndex].classList.remove('active'); } activeIndex = index; if (activeIndex > -1 && menuItems[activeIndex]) { const activeItem = menuItems[activeIndex]; activeItem.classList.add('active'); trigger.setAttribute('aria-activedescendant', activeItem.id); } else { trigger.removeAttribute('aria-activedescendant'); } }; trigger.addEventListener('click', () => { const isExpanded = trigger.getAttribute('aria-expanded') === 'true'; if (isExpanded) { closePopover(); } else { openPopover(); } }); dropdownMenuComponent.addEventListener('keydown', (event) => { const isExpanded = trigger.getAttribute('aria-expanded') === 'true'; if (event.key === 'Escape') { if (isExpanded) closePopover(); return; } if (!isExpanded) { if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(event.key)) { event.preventDefault(); openPopover(); } return; } if (menuItems.length === 0) return; let nextIndex = activeIndex; switch (event.key) { case 'ArrowDown': event.preventDefault(); nextIndex = Math.min(activeIndex + 1, menuItems.length - 1); break; case 'ArrowUp': event.preventDefault(); nextIndex = Math.max(activeIndex - 1, 0); break; case 'Home': event.preventDefault(); nextIndex = 0; break; case 'End': event.preventDefault(); nextIndex = menuItems.length - 1; break; case 'Enter': case ' ': event.preventDefault(); menuItems[activeIndex]?.click(); closePopover(); return; } if (nextIndex !== activeIndex) { setActiveItem(nextIndex); } }); menu.addEventListener('click', (event) => { if (event.target.closest('[role^="menuitem"]')) { closePopover(); } }); document.addEventListener('click', (event) => { if (!dropdownMenuComponent.contains(event.target)) { closePopover(); } }); document.addEventListener('basecoat:popover', (event) => { if (event.detail.source !== dropdownMenuComponent) { closePopover(false); } }); dropdownMenuComponent.dataset.dropdownMenuInitialized = true; }; document.querySelectorAll('.dropdown-menu:not([data-dropdown-menu-initialized])').forEach(initDropdownMenu); const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType !== Node.ELEMENT_NODE) return; if (node.matches('.dropdown-menu:not([data-dropdown-menu-initialized])')) { initDropdownMenu(node); } node.querySelectorAll('.dropdown-menu:not([data-dropdown-menu-initialized])').forEach(initDropdownMenu); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })();