diff --git a/src/lib/components/channel/Channel.svelte b/src/lib/components/channel/Channel.svelte index 8ed982f2d6..a050ec4695 100644 --- a/src/lib/components/channel/Channel.svelte +++ b/src/lib/components/channel/Channel.svelte @@ -534,6 +534,7 @@ {#if threadId !== null} { threadId = null; }} diff --git a/src/lib/components/chat/ChatControls.svelte b/src/lib/components/chat/ChatControls.svelte index 4a200775b3..dc97d63baf 100644 --- a/src/lib/components/chat/ChatControls.svelte +++ b/src/lib/components/chat/ChatControls.svelte @@ -325,6 +325,7 @@ {#if visible} showControls.set(false)} onRequestClose={() => showControls.set(false)} panelStyle={$mobile ? panelStyle : ''} diff --git a/src/lib/components/comments/Comments.svelte b/src/lib/components/comments/Comments.svelte index da81d75982..2bf3fea69c 100644 --- a/src/lib/components/comments/Comments.svelte +++ b/src/lib/components/comments/Comments.svelte @@ -83,7 +83,7 @@ {:else if $mobile} - (view = null)}> + (view = null)}>
(view = null)} />
{:else} diff --git a/src/lib/components/common/CodeEditorModal.svelte b/src/lib/components/common/CodeEditorModal.svelte index 7343a0cb0a..8b278b63dc 100644 --- a/src/lib/components/common/CodeEditorModal.svelte +++ b/src/lib/components/common/CodeEditorModal.svelte @@ -26,7 +26,7 @@ }); - +
{}; export let onRequestClose: (() => void) | null = null; + export let ariaLabel: string | undefined = undefined; const requestClose = () => (onRequestClose ? onRequestClose() : (show = false)); let modalElement = null; let mounted = false; + let focusTrap: FocusTrap.FocusTrap | null = null; const handleKeyDown = (event: KeyboardEvent) => { if ( @@ -34,16 +37,67 @@ return modals.length && modals[modals.length - 1] === modalElement; }; + let handleOutsideInteraction: ((event: Event) => void) | undefined; + let handleModalFocusIn: (() => void) | undefined; + const release = () => { + focusTrap?.deactivate(); + focusTrap = null; + if (handleOutsideInteraction) { + document.removeEventListener('pointerdown', handleOutsideInteraction, true); + window.removeEventListener('focusin', handleOutsideInteraction, true); + } + if (handleModalFocusIn) modalElement?.removeEventListener('focusin', handleModalFocusIn); + }; + onMount(() => { mounted = true; }); $: if (show && modalElement) { document.body.appendChild(modalElement); + if (!focusTrap) { + focusTrap = FocusTrap.createFocusTrap(modalElement, { + escapeDeactivates: false, + allowOutsideClick: true, + fallbackFocus: modalElement + }); + focusTrap.activate(); + + handleOutsideInteraction = (e) => { + if ( + focusTrap && + modalElement && + e.target instanceof Node && + !modalElement.contains(e.target) && + (e.type === 'pointerdown' || + (e.target instanceof Element && + e.target.closest('[role="menu"], [role="listbox"], [role="combobox"]'))) + ) { + focusTrap.pause(); + } else if ( + focusTrap && + modalElement && + e.target instanceof Element && + modalElement.compareDocumentPosition(e.target) & Node.DOCUMENT_POSITION_PRECEDING && + !e.target.closest('[data-sonner-toast]') + ) { + focusTrap.unpause(); + } + }; + handleModalFocusIn = () => { + if (focusTrap) { + focusTrap.unpause(); + } + }; + document.addEventListener('pointerdown', handleOutsideInteraction, true); + window.addEventListener('focusin', handleOutsideInteraction, true); + modalElement.addEventListener('focusin', handleModalFocusIn); + } window.addEventListener('keydown', handleKeyDown); document.body.style.overflow = 'hidden'; } else if (modalElement) { onClose(); + release(); window.removeEventListener('keydown', handleKeyDown); if (document.body.contains(modalElement)) { @@ -53,6 +107,7 @@ } onDestroy(() => { + release(); window.removeEventListener('keydown', handleKeyDown); show = false; if (modalElement) { @@ -69,6 +124,10 @@ {#if show}