diff --git a/src/lib/utils/Popper.svelte b/src/lib/utils/Popper.svelte index 24927541d..1cd19a682 100644 --- a/src/lib/utils/Popper.svelte +++ b/src/lib/utils/Popper.svelte @@ -34,6 +34,9 @@ const dispatch = createEventDispatcher(); + let focusable: boolean; + $: focusable = trigger === 'focus'; + let clickable: boolean; $: clickable = trigger === 'click'; @@ -49,32 +52,34 @@ let contentEl: HTMLElement; let triggerEls: HTMLElement[] = []; - let _blocked: boolean = false; // management of the race condition between focusin and click events - const block = () => ((_blocked = true), setTimeout(() => (_blocked = false), 250)); - const showHandler = (ev: Event) => { if (referenceEl === undefined) console.error('trigger undefined'); if (!reference && triggerEls.includes(ev.target as HTMLElement) && referenceEl !== ev.target) { referenceEl = ev.target as HTMLElement; - block(); + if (open) return; // If the popper is already open after the reference element has changed } - if (clickable && ev.type === 'focusin' && !open) block(); - open = clickable && ev.type === 'click' && !_blocked ? !open : true; + + open = ev.type === 'click' ? !open : true; }; const hasHover = (el: Element) => el.matches(':hover'); const hasFocus = (el: Element) => el.contains(document.activeElement); - const px = (n: number | undefined) => (n != null ? `${n}px` : ''); + const px = (n: number | undefined) => (n ? `${n}px` : ''); const hideHandler = (ev: Event) => { - if (activeContent) { + if (activeContent && hoverable) { + const elements = [referenceEl, floatingEl, ...triggerEls].filter(Boolean); + // Add a delay before hiding the floating element to account for hoverable elements. + // This ensures that the floating element does not hide immediately when the mouse + // moves from the reference element to the floating element. setTimeout(() => { - const elements = [referenceEl, floatingEl, ...triggerEls].filter(Boolean); - if (ev.type === 'mouseleave' && elements.some(hasHover)) return; - if (ev.type === 'focusout' && elements.some(hasFocus)) return; - open = false; + if (ev.type === 'mouseleave' && !elements.some(hasHover)) { + open = false; + } }, 100); - } else open = false; + } else { + open = false; + } }; let arrowSide: Side; @@ -120,8 +125,8 @@ onMount(() => { const events: [string, any, boolean][] = [ - ['focusin', showHandler, true], - ['focusout', hideHandler, true], + ['focusin', showHandler, focusable], + ['focusout', hideHandler, focusable], ['click', showHandler, clickable], ['mouseenter', showHandler, hoverable], ['mouseleave', hideHandler, hoverable] @@ -144,14 +149,14 @@ if (referenceEl === document.body) { console.error(`Popup reference not found: '${reference}'`); } else { - referenceEl.addEventListener('focusout', hideHandler); + if (focusable) referenceEl.addEventListener('focusout', hideHandler); if (hoverable) referenceEl.addEventListener('mouseleave', hideHandler); } } else { referenceEl = triggerEls[0]; } - document.addEventListener('click', closeOnClickOutside); + if (clickable) document.addEventListener('click', closeOnClickOutside); return () => { // This is onDestroy function @@ -160,10 +165,12 @@ for (const [name, handler] of events) element.removeEventListener(name, handler); } }); + if (referenceEl) { referenceEl.removeEventListener('focusout', hideHandler); referenceEl.removeEventListener('mouseleave', hideHandler); } + document.removeEventListener('click', closeOnClickOutside); }; }); @@ -202,7 +209,7 @@ {/if} {#if referenceEl} - + {#if arrow}
{/if}