export function createDropdownMenu({ anchorEl, items = [], className = '', minWidth = 210, offset = 7, align = 'right', onOpen = null, onClose = null, } = {}) { let portal = null; const close = () => { if (!portal) return; portal.remove(); portal = null; anchorEl?.setAttribute('aria-expanded', 'false'); onClose?.(); }; const position = () => { if (!portal || !anchorEl) return; const rect = anchorEl.getBoundingClientRect(); const margin = 10; const menuWidth = portal.offsetWidth || minWidth; const desiredLeft = align === 'left' ? rect.left : rect.right - menuWidth; const left = Math.max(margin, Math.min(window.innerWidth - menuWidth - margin, desiredLeft)); let top = rect.bottom + offset; const menuHeight = portal.offsetHeight || 180; if (top + menuHeight > window.innerHeight - margin) { top = Math.max(margin, rect.top - menuHeight - offset); } portal.style.left = `${Math.round(left)}px`; portal.style.top = `${Math.round(top)}px`; }; const open = () => { if (!anchorEl || portal) return; const menu = document.createElement('div'); menu.className = `dm-head-menu dm-head-menu--portal shared-dropdown-menu ${className}`.trim(); menu.setAttribute('role', 'menu'); menu.style.minWidth = `${minWidth}px`; items.forEach((item) => { if (item?.divider) { const divider = document.createElement('div'); divider.className = 'channel-menu-divider'; menu.append(divider); return; } const btn = document.createElement('button'); btn.type = 'button'; btn.className = `dm-head-menu-item shared-dropdown-menu__item${item?.selected ? ' is-selected' : ''}${item?.danger ? ' destructive' : ''}`; btn.setAttribute('role', 'menuitem'); if (item?.iconHtml) { const icon = document.createElement('span'); icon.className = 'shared-dropdown-menu__icon'; icon.innerHTML = item.iconHtml; btn.append(icon); } else if (item?.iconSrc) { const icon = document.createElement('img'); icon.src = item.iconSrc; icon.alt = ''; icon.setAttribute('aria-hidden', 'true'); btn.append(icon); } const label = document.createElement('span'); label.textContent = String(item?.label || ''); btn.append(label); btn.addEventListener('click', (event) => { event.preventDefault(); event.stopPropagation(); close(); item?.action?.(); }); menu.append(btn); }); menu.addEventListener('click', (event) => event.stopPropagation()); document.body.append(menu); portal = menu; anchorEl.setAttribute('aria-expanded', 'true'); onOpen?.(); position(); }; const toggle = () => { if (portal) close(); else open(); }; const onAnchorClick = (event) => { event.preventDefault(); event.stopPropagation(); toggle(); }; const onOutsideClick = (event) => { if (!portal) return; if (portal.contains(event.target) || anchorEl?.contains(event.target)) return; close(); }; const onKeydown = (event) => { if (event.key !== 'Escape' || !portal) return; close(); anchorEl?.focus(); }; anchorEl?.setAttribute('aria-haspopup', 'menu'); anchorEl?.setAttribute('aria-expanded', 'false'); anchorEl?.addEventListener('click', onAnchorClick); document.addEventListener('click', onOutsideClick); document.addEventListener('keydown', onKeydown); window.addEventListener('resize', position, { passive: true }); window.addEventListener('scroll', position, { passive: true, capture: true }); return { open, close, toggle, position, destroy() { close(); anchorEl?.removeEventListener('click', onAnchorClick); document.removeEventListener('click', onOutsideClick); document.removeEventListener('keydown', onKeydown); window.removeEventListener('resize', position); window.removeEventListener('scroll', position, true); }, }; }