From 0bf928d6365ea0dc35b5e60a2f54ae1c6e056ae9 Mon Sep 17 00:00:00 2001 From: InstaZDLL Date: Sat, 3 Oct 2026 05:16:09 +0200 Subject: [PATCH] fix(ui): keep a context menu open while its own list scrolls The menu closed on every scroll it captured, the playlist list inside the Add to a playlist submenu included, so the list could not be scrolled at all. Only a scroll outside the menu closes it now, and the list no longer hands its scroll on to the page once it reaches the end. --- docs/features/playlists.md | 2 +- src/components/common/ContextMenu.tsx | 17 +++++++++++++---- src/components/common/TrackContextMenu.tsx | 4 +++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/docs/features/playlists.md b/docs/features/playlists.md index 80415d1c3..5fcd3826c 100644 --- a/docs/features/playlists.md +++ b/docs/features/playlists.md @@ -23,7 +23,7 @@ The sidebar's two pinned rows — Liked tracks and Recently played — open the A **server** playlist gets one item, Open. Every other action takes a local rowid it does not have, and its playback runs through the view's own remote path rather than `player_play_tracks` — so the alternative was a menu with every item greyed out, which is worse than the inert right-click this replaced. -The grid is virtualized, so the menu state lives in [`PlaylistGrid`](../../src/components/views/library/PlaylistGrid.tsx) rather than in a card — a card can unmount under an open menu. `ContextMenu` portals to `body` and closes on scroll, so that unmount is harmless. +The grid is virtualized, so the menu state lives in [`PlaylistGrid`](../../src/components/views/library/PlaylistGrid.tsx) rather than in a card — a card can unmount under an open menu. `ContextMenu` portals to `body` and closes when the page scrolls, so that unmount is harmless. A scroll inside the menu itself — the playlist list of "Add to a playlist" — leaves it open. ## Reordering diff --git a/src/components/common/ContextMenu.tsx b/src/components/common/ContextMenu.tsx index 00c9761ff..ed77cf16b 100644 --- a/src/components/common/ContextMenu.tsx +++ b/src/components/common/ContextMenu.tsx @@ -178,18 +178,27 @@ export function ContextMenu({ if (target?.closest("[data-context-menu]")) return; onClose(); }; + // Close when the page scrolls — keeps the menu glued to its anchor + // without chasing the page. Not when the scroll is inside the menu + // itself: the listener captures every scroll, and the playlist + // submenu's own list closed the menu the moment it moved. + const onScroll = (e: Event) => { + const target = e.target; + if (target instanceof Element && target.closest("[data-context-menu]")) { + return; + } + onClose(); + }; // `mousedown` instead of `click` so dragging doesn't accidentally // dismiss before the user releases. window.addEventListener("keydown", onKey); window.addEventListener("mousedown", onMouseDown); - // Close on scroll — keeps the menu glued to its anchor without - // chasing the page. - window.addEventListener("scroll", onClose, true); + window.addEventListener("scroll", onScroll, true); window.addEventListener("resize", onClose); return () => { window.removeEventListener("keydown", onKey); window.removeEventListener("mousedown", onMouseDown); - window.removeEventListener("scroll", onClose, true); + window.removeEventListener("scroll", onScroll, true); window.removeEventListener("resize", onClose); }; }, [onClose, menuItems]); diff --git a/src/components/common/TrackContextMenu.tsx b/src/components/common/TrackContextMenu.tsx index 521259cfa..d550b012a 100644 --- a/src/components/common/TrackContextMenu.tsx +++ b/src/components/common/TrackContextMenu.tsx @@ -139,7 +139,9 @@ export function TrackContextMenu({ icon={} label={t("trackActions.addToPlaylist")} > -
+ {/* `overscroll-contain`: reaching the end of the list must not + scroll the page behind it, which closes the menu. */} +
{playlists.length === 0 ? (
{t("trackActions.noPlaylists")}