From 7bfc4bb2c25249d3922acd54d5bc516db52c8519 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 26 Jul 2026 18:02:33 -0400 Subject: [PATCH] refac --- src/lib/components/common/DropdownSub.svelte | 84 +++++++++++++++----- 1 file changed, 66 insertions(+), 18 deletions(-) diff --git a/src/lib/components/common/DropdownSub.svelte b/src/lib/components/common/DropdownSub.svelte index 8e4b35d717..daa15766d0 100644 --- a/src/lib/components/common/DropdownSub.svelte +++ b/src/lib/components/common/DropdownSub.svelte @@ -14,8 +14,34 @@ export let sideOffset = 8; let open = false; - let triggerEl; - let contentEl; + let triggerEl: HTMLElement | null = null; + let contentEl: HTMLElement | null = null; + + function trigger(node: HTMLElement) { + triggerEl = (node.firstElementChild as HTMLElement | null) || node; + + async function handleClick(event: MouseEvent) { + event.preventDefault(); + await openSub(true); + } + + async function handleKeydown(event: KeyboardEvent) { + if (!['Enter', ' ', 'ArrowRight'].includes(event.key)) return; + + event.preventDefault(); + await openSub(true); + } + + node.addEventListener('click', handleClick); + node.addEventListener('keydown', handleKeydown); + + return { + destroy() { + node.removeEventListener('click', handleClick); + node.removeEventListener('keydown', handleKeydown); + } + }; + } function positionContent() { if (!triggerEl || !contentEl) return; @@ -29,7 +55,7 @@ contentEl.style.paddingRight = '0'; // Inherit min-width from parent dropdown container (apply to inner content) - const innerContent = contentEl.firstElementChild; + const innerContent = contentEl.firstElementChild as HTMLElement | null; const parentContainer = triggerEl.closest('[class*="rounded"]')?.parentElement; if (parentContainer && innerContent) { const parentWidth = parentContainer.offsetWidth; @@ -71,28 +97,48 @@ contentEl.style.top = `${top}px`; } - async function handleMouseEnter() { + async function openSub(focus = false) { open = true; await tick(); positionContent(); // Re-position after transition starts rendering real dimensions setTimeout(positionContent, 50); + + if (focus) { + contentEl?.focus(); + } } - function handleMouseLeave(event) { + async function handleMouseEnter() { + await openSub(); + } + + function handleContentKeydown(event: KeyboardEvent) { + if (event.key !== 'Escape') return; + + event.stopPropagation(); + open = false; + triggerEl?.focus(); + } + + function handleMouseLeave(event: MouseEvent) { + const relatedTarget = event.relatedTarget as Node | null; + // Don't close if moving to the sub-content (including its bridge padding) - if (contentEl?.contains(event.relatedTarget)) return; - if (triggerEl?.contains(event.relatedTarget)) return; + if (relatedTarget && contentEl?.contains(relatedTarget)) return; + if (relatedTarget && triggerEl?.contains(relatedTarget)) return; open = false; } - function handleContentMouseLeave(event) { - if (triggerEl?.contains(event.relatedTarget)) return; - if (contentEl?.contains(event.relatedTarget)) return; + function handleContentMouseLeave(event: MouseEvent) { + const relatedTarget = event.relatedTarget as Node | null; + + if (relatedTarget && triggerEl?.contains(relatedTarget)) return; + if (relatedTarget && contentEl?.contains(relatedTarget)) return; open = false; } - function portal(node) { + function portal(node: HTMLElement) { document.body.appendChild(node); return { destroy() { @@ -107,19 +153,21 @@ -
+
{#if open} -
+