forked from Keelrleek/SoulStream
player bar: the overflow menu out from under the bar's own clip, and Lyrics wired for real
This commit is contained in:
1 parent
df3d15553a
commit
f4f26d6d34
1 file changed
+79
-36
@@ -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}
|
||||
>
|
||||
<Icon name="more-horizontal" size={17} />
|
||||
</button>
|
||||
{#if menuOpen}
|
||||
<div class="menu" role="menu">
|
||||
<button role="menuitem" onclick={openQueue}>
|
||||
<Icon name="playlist" size={15} /> Queue
|
||||
</button>
|
||||
<!-- Save's new home. The bar's download button became the
|
||||
expand toggle, and this menu is where everything past
|
||||
Save was always going to live. Only a stream can be
|
||||
saved, so the items appear only for one. -->
|
||||
{#if canSave}
|
||||
<button role="menuitem" onclick={() => saveFromMenu("track")}>
|
||||
<Icon name="music" size={15} /> Save this track
|
||||
</button>
|
||||
{#if savingAlbum}
|
||||
<button role="menuitem" onclick={() => saveFromMenu("album")}>
|
||||
<Icon name="library" size={15} /> Save whole album
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
<button role="menuitem" disabled title="Not built yet">
|
||||
<Icon name="lyrics" size={15} /> Lyrics <span class="soon">soon</span>
|
||||
</button>
|
||||
<button role="menuitem" disabled title="Not built yet">
|
||||
<Icon name="panel-right" size={15} /> Mini player <span class="soon">soon</span>
|
||||
</button>
|
||||
<button role="menuitem" disabled title="Not built yet">
|
||||
<Icon name="volume" size={15} /> Output device <span class="soon">soon</span>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<button class="vol-icon" title={player.muted ? "Unmute" : "Mute"} onclick={toggleMute}>
|
||||
@@ -1283,6 +1286,45 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Outside the footer on purpose: see the note at menuOpen. -->
|
||||
{#if menuOpen}
|
||||
<div
|
||||
class="menu"
|
||||
role="menu"
|
||||
bind:this={menuEl}
|
||||
style="right: {menuPos.right}px; bottom: {menuPos.bottom}px;"
|
||||
>
|
||||
<button role="menuitem" onclick={openQueue}>
|
||||
<Icon name="playlist" size={15} /> Queue
|
||||
</button>
|
||||
<!-- Save's new home. The bar's download button became the expand
|
||||
toggle, and this menu is where everything past Save was always
|
||||
going to live. Only a stream can be saved, so the items appear
|
||||
only for one. -->
|
||||
{#if canSave}
|
||||
<button role="menuitem" onclick={() => saveFromMenu("track")}>
|
||||
<Icon name="music" size={15} /> Save this track
|
||||
</button>
|
||||
{#if savingAlbum}
|
||||
<button role="menuitem" onclick={() => saveFromMenu("album")}>
|
||||
<Icon name="library" size={15} /> Save whole album
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
<!-- Enabled only with a track: the overlay closes itself the moment
|
||||
player.track is empty, so opening it then would just blink. -->
|
||||
<button role="menuitem" disabled={!currentItem} onclick={openLyrics}>
|
||||
<Icon name="lyrics" size={15} /> Lyrics
|
||||
</button>
|
||||
<button role="menuitem" disabled title="Not built yet">
|
||||
<Icon name="panel-right" size={15} /> Mini player <span class="soon">soon</span>
|
||||
</button>
|
||||
<button role="menuitem" disabled title="Not built yet">
|
||||
<Icon name="volume" size={15} /> Output device <span class="soon">soon</span>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
/* FOUR blocks, absolutely placed, so the collapsed row and the expanded
|
||||
stack are the same layout at two sets of coordinates and the bar can
|
||||
@@ -1708,10 +1750,11 @@
|
||||
place-items: center;
|
||||
}
|
||||
.menu {
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
right: -6px;
|
||||
z-index: 40;
|
||||
/* Fixed positioning escapes the bar's overflow clipping and every
|
||||
panel stacking context (the SaveMenu idiom, same tier). The
|
||||
coordinates come measured from the trigger at open. */
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
|
||||
Reference in new issue
Block a user