From 9ce907dc28d7e5097e686e5c8b5a8c1a25e825fc Mon Sep 17 00:00:00 2001 From: Phil LaFayette Date: Fri, 26 Jun 2026 17:07:40 -0500 Subject: [PATCH 1/6] Overlay Phase 2: migrate at-risk anchored overlays to the native top layer MentionDropdown, ReactionBar (hover tooltip -> manual popover, hover lifecycle preserved), ArtifactDetailHeader dropdowns, EmojiPicker, ProfileCard, NotificationPolicyMenu, ReadReceipt now open via /use:topLayer (Popover API), dropping raw position:fixed/absolute + z-index. MessageInput passes the textarea as the MentionDropdown anchor. Each keeps its existing outside-click/keyboard dismiss handlers so jsdom unit coverage holds (the action capability-guards the native calls). --- .../components/ArtifactDetailHeader.svelte | 24 +++- web/src/components/EmojiPicker.svelte | 46 +++++-- web/src/components/MentionDropdown.svelte | 26 +++- web/src/components/MessageInput.svelte | 9 +- .../components/NotificationPolicyMenu.svelte | 28 ++++- web/src/components/ProfileCard.svelte | 53 +++++--- web/src/components/ReactionBar.svelte | 117 ++++++++++++------ web/src/components/ReadReceipt.svelte | 30 ++++- web/tests/reaction-bar.spec.js | 16 ++- 9 files changed, 258 insertions(+), 91 deletions(-) diff --git a/web/src/components/ArtifactDetailHeader.svelte b/web/src/components/ArtifactDetailHeader.svelte index 1b7515f..90e230f 100644 --- a/web/src/components/ArtifactDetailHeader.svelte +++ b/web/src/components/ArtifactDetailHeader.svelte @@ -36,6 +36,7 @@ Check, } from 'lucide-svelte'; import { formatTime, getParticipantColor } from '../lib/utils.js'; + import { topLayer } from '../lib/top-layer.svelte.js'; let { artifact, @@ -387,6 +388,14 @@ aria-label="Compare-from version" aria-activedescendant={compareActiveId} onkeydown={handleCompareListboxKeydown} + use:topLayer={{ + anchor: compareTriggerEl, + placement: 'bottom-start', + offset: 4, + dismiss: 'manual', + trapInitialFocus: false, + restoreFocus: false, + }} > {#each compareOptions as v, idx (v.version)} {@const pc = getParticipantColor(v.author?.key ?? '')} @@ -441,6 +450,14 @@ aria-label="Artifact version" aria-activedescendant={primaryActiveId} onkeydown={handlePrimaryListboxKeydown} + use:topLayer={{ + anchor: primaryTriggerEl, + placement: 'bottom-start', + offset: 4, + dismiss: 'manual', + trapInitialFocus: false, + restoreFocus: false, + }} > {#each versions as v, idx (v.version)} {@const pc = getParticipantColor(v.author?.key ?? '')} @@ -674,10 +691,8 @@ } .artifact-version-dropdown { - position: absolute; - top: 100%; - left: 0; - margin-top: 4px; + /* Position + top-layer promotion owned by use:topLayer (sets position: + fixed + left/top inline, anchored to the trigger button); no z-index. */ width: 320px; max-height: 240px; overflow-y: auto; @@ -685,7 +700,6 @@ border: 1px solid var(--border); border-radius: var(--radius-sm); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); - z-index: 10; animation: panelIn 0.15s cubic-bezier(0.16, 1, 0.3, 1) both; } diff --git a/web/src/components/EmojiPicker.svelte b/web/src/components/EmojiPicker.svelte index 7cb984f..3f945f1 100644 --- a/web/src/components/EmojiPicker.svelte +++ b/web/src/components/EmojiPicker.svelte @@ -6,6 +6,7 @@ --> -
{ if (e.key === 'Escape') onClose(); }} role="presentation"> + { if (e.key === 'Escape') onClose(); }} + onmousedown={handleOutsideMousedown} +/> + +
e.stopPropagation()} - onkeydown={(e) => { if (e.key === 'Escape') onClose(); }} role="dialog" aria-label="Emoji picker" aria-modal="true" @@ -301,21 +321,23 @@
- +