diff --git a/src/components/PlayerBar.svelte b/src/components/PlayerBar.svelte index 7920cf1..69e3d6c 100644 --- a/src/components/PlayerBar.svelte +++ b/src/components/PlayerBar.svelte @@ -215,25 +215,58 @@ } // ---- the overflow menu ------------------------------------------------- + // The popup renders OUTSIDE the footer and position:fixed (the SaveMenu + // idiom): the bar is overflow:hidden, so an absolute menu inside it got + // amputated at the bar's top edge — and what poked out still lost to the + // Now Playing panel's z-indexed layers. Fixed + measured coordinates + // escape both the clip and the stacking war. It must NOT live inside + // .extras: that element's centering transform would become a fixed + // element's containing block and hand it right back to the clip. let menuOpen = $state(false); let menuWrap = $state(null); + let menuEl = $state(null); + let menuPos = $state({ right: 0, bottom: 0 }); + function toggleMenu() { + if (!menuOpen && menuWrap) { + const r = menuWrap.getBoundingClientRect(); + // Right-aligned over the button, opening upward — the same seat + // the old absolute placement (bottom + right of the wrap) gave it. + menuPos = { + right: Math.max(8, window.innerWidth - r.right - 6), + bottom: window.innerHeight - r.top + 6, + }; + } + menuOpen = !menuOpen; + } $effect(() => { if (!menuOpen) return; const away = (e) => { - if (menuWrap && !menuWrap.contains(e.target)) menuOpen = false; + const inside = + (menuWrap && menuWrap.contains(e.target)) || + (menuEl && menuEl.contains(e.target)); + if (!inside) menuOpen = false; }; const esc = (e) => e.key === "Escape" && (menuOpen = false); + // The menu is anchored to coordinates measured at open; a resize + // moves the anchor out from under them, so it closes instead. + const close = () => (menuOpen = false); window.addEventListener("pointerdown", away); window.addEventListener("keydown", esc); + window.addEventListener("resize", close); return () => { window.removeEventListener("pointerdown", away); window.removeEventListener("keydown", esc); + window.removeEventListener("resize", close); }; }); function openQueue() { menuOpen = false; showNowPlaying(); // the queue lives in the panel; always reveal it } + function openLyrics() { + menuOpen = false; + ui.lyricsFull = true; // the fullscreen overlay (LyricsOverlay.svelte) + } // ---- Save to library, from the ⋯ menu --------------------------------- // Only a stream can be saved; a single queued stream has one destination @@ -1224,40 +1257,10 @@ title="More" aria-haspopup="menu" aria-expanded={menuOpen} - onclick={() => (menuOpen = !menuOpen)} + onclick={toggleMenu} > - {#if menuOpen} - - {/if} + + {#if canSave} + + {#if savingAlbum} + + {/if} + {/if} + + + + + +{/if} +