diff --git a/frontend/src/components/artists-view/artists-view.ts b/frontend/src/components/artists-view/artists-view.ts index 2049b44..f76003b 100644 --- a/frontend/src/components/artists-view/artists-view.ts +++ b/frontend/src/components/artists-view/artists-view.ts @@ -78,6 +78,10 @@ export class ArtistsView extends LitElement { @query('#playlist-submenu') private playlistSubmenuPopup!: HTMLElement; + private submenuCloseTimer: ReturnType< + typeof setTimeout + > | null = null; + // ----- Close handlers ----- private closeHandler = () => @@ -695,7 +699,24 @@ export class ArtistsView extends LitElement { * Playlist submenu * ================================================================ */ + private clearSubmenuCloseTimer() { + if (this.submenuCloseTimer !== null) { + clearTimeout(this.submenuCloseTimer); + this.submenuCloseTimer = null; + } + } + + private scheduleSubmenuClose = () => { + this.clearSubmenuCloseTimer(); + this.submenuCloseTimer = setTimeout(() => { + this.submenuCloseTimer = null; + this.closePlaylistSubmenu(); + }, 150); + }; + private async showPlaylistSubmenu() { + this.clearSubmenuCloseTimer(); + if (this.playlistSubmenuOpen) return; const artist = this.contextMenuArtist; @@ -729,6 +750,8 @@ export class ArtistsView extends LitElement { } private closePlaylistSubmenu() { + this.clearSubmenuCloseTimer(); + if (!this.playlistSubmenuOpen) return; this.playlistSubmenuOpen = false; @@ -868,6 +891,8 @@ export class ArtistsView extends LitElement { this.onContextMenuAction( 'play', )} + @mouseenter=${() => + this.closePlaylistSubmenu()} > + this.closePlaylistSubmenu()} > + this.closePlaylistSubmenu()} > - this.showPlaylistSubmenu()} + @mouseenter=${() => { + this.clearSubmenuCloseTimer(); + void this.showPlaylistSubmenu(); + }} + @mouseleave=${this + .scheduleSubmenuClose} @click=${( e: Event, ) => { @@ -935,16 +968,23 @@ export class ArtistsView extends LitElement { > ${this.playlistSubmenuOpen ? html` - - e.stopPropagation()} - > +
+ this.clearSubmenuCloseTimer()} + @mouseleave=${this + .scheduleSubmenuClose} + > + + e.stopPropagation()} + > +
` : nothing} diff --git a/frontend/src/components/cover-grid/cover-grid.ts b/frontend/src/components/cover-grid/cover-grid.ts index 906b234..2a82aff 100644 --- a/frontend/src/components/cover-grid/cover-grid.ts +++ b/frontend/src/components/cover-grid/cover-grid.ts @@ -158,6 +158,10 @@ export class CoverGrid extends LitElement { typeof setTimeout > | null = null; + private submenuCloseTimer: ReturnType< + typeof setTimeout + > | null = null; + private closeHandler = () => this.closeContextMenu(); private mousedownCloseHandler = ( @@ -3139,7 +3143,24 @@ export class CoverGrid extends LitElement { } } + private clearSubmenuCloseTimer() { + if (this.submenuCloseTimer !== null) { + clearTimeout(this.submenuCloseTimer); + this.submenuCloseTimer = null; + } + } + + private scheduleSubmenuClose = () => { + this.clearSubmenuCloseTimer(); + this.submenuCloseTimer = setTimeout(() => { + this.submenuCloseTimer = null; + this.closePlaylistSubmenu(); + }, 150); + }; + private async showPlaylistSubmenu() { + this.clearSubmenuCloseTimer(); + if (this.playlistSubmenuOpen) return; if (this.contextMenuTarget.kind === 'track') { @@ -3174,6 +3195,8 @@ export class CoverGrid extends LitElement { } private closePlaylistSubmenu() { + this.clearSubmenuCloseTimer(); + if (!this.playlistSubmenuOpen) return; this.playlistSubmenuOpen = false; @@ -3548,6 +3571,8 @@ export class CoverGrid extends LitElement { this.onContextMenuAction( 'play', )} + @mouseenter=${() => + this.closePlaylistSubmenu()} > + this.closePlaylistSubmenu()} > + this.closePlaylistSubmenu()} > - this.showPlaylistSubmenu()} + @mouseenter=${() => { + this.clearSubmenuCloseTimer(); + void this.showPlaylistSubmenu(); + }} + @mouseleave=${this + .scheduleSubmenuClose} @click=${(e: Event) => { e.stopPropagation(); void this.showPlaylistSubmenu(); @@ -3609,6 +3642,8 @@ export class CoverGrid extends LitElement { this.onContextMenuAction( 'track-details', )} + @mouseenter=${() => + this.closePlaylistSubmenu()} > ${this.playlistSubmenuOpen ? html` - +
+ this.clearSubmenuCloseTimer()} + @mouseleave=${this + .scheduleSubmenuClose} + > + e.stopPropagation()} - > + > +
` : nothing} diff --git a/frontend/src/components/playlist-view/playlist-view.ts b/frontend/src/components/playlist-view/playlist-view.ts index 62f9c7f..a5ff672 100644 --- a/frontend/src/components/playlist-view/playlist-view.ts +++ b/frontend/src/components/playlist-view/playlist-view.ts @@ -198,6 +198,10 @@ export class PlaylistView @query('track-details') private trackDetailsDialog!: TrackDetails; + private submenuCloseTimer: ReturnType< + typeof setTimeout + > | null = null; + private closeContextMenuHandler = () => { this.closeContextMenu(); this.closePlaylistContextMenu(); @@ -1464,7 +1468,24 @@ export class PlaylistView } } + private clearSubmenuCloseTimer() { + if (this.submenuCloseTimer !== null) { + clearTimeout(this.submenuCloseTimer); + this.submenuCloseTimer = null; + } + } + + private scheduleSubmenuClose = () => { + this.clearSubmenuCloseTimer(); + this.submenuCloseTimer = setTimeout(() => { + this.submenuCloseTimer = null; + this.closePlaylistSubmenu(); + }, 150); + }; + private async showPlaylistSubmenu() { + this.clearSubmenuCloseTimer(); + if (this.playlistSubmenuOpen) return; this.playlistSubmenuOpen = true; @@ -1490,6 +1511,8 @@ export class PlaylistView } private closePlaylistSubmenu() { + this.clearSubmenuCloseTimer(); + if (!this.playlistSubmenuOpen) return; this.playlistSubmenuOpen = false; @@ -1841,6 +1864,8 @@ export class PlaylistView this.onContextMenuAction( 'play', )} + @mouseenter=${() => + this.closePlaylistSubmenu()} > + this.closePlaylistSubmenu()} > + this.closePlaylistSubmenu()} > + this.closePlaylistSubmenu()} > - this.showPlaylistSubmenu()} + @mouseenter=${() => { + this.clearSubmenuCloseTimer(); + void this.showPlaylistSubmenu(); + }} + @mouseleave=${this + .scheduleSubmenuClose} @click=${(e: Event) => { e.stopPropagation(); void this.showPlaylistSubmenu(); @@ -1912,6 +1947,8 @@ export class PlaylistView this.onContextMenuAction( 'track-details', )} + @mouseenter=${() => + this.closePlaylistSubmenu()} > - e.stopPropagation()} - >
+
+ this.clearSubmenuCloseTimer()} + @mouseleave=${this + .scheduleSubmenuClose} + > + + e.stopPropagation()} + > +
` : nothing} diff --git a/frontend/src/components/queue-panel/queue-panel.ts b/frontend/src/components/queue-panel/queue-panel.ts index b4b420e..fa1db8a 100644 --- a/frontend/src/components/queue-panel/queue-panel.ts +++ b/frontend/src/components/queue-panel/queue-panel.ts @@ -103,6 +103,10 @@ export class QueuePanel } }; + private submenuCloseTimer: ReturnType< + typeof setTimeout + > | null = null; + private closeContextMenuHandler = () => this.closeContextMenu(); @@ -772,7 +776,24 @@ export class QueuePanel } } + private clearSubmenuCloseTimer() { + if (this.submenuCloseTimer !== null) { + clearTimeout(this.submenuCloseTimer); + this.submenuCloseTimer = null; + } + } + + private scheduleSubmenuClose = () => { + this.clearSubmenuCloseTimer(); + this.submenuCloseTimer = setTimeout(() => { + this.submenuCloseTimer = null; + this.closePlaylistSubmenu(); + }, 150); + }; + private async showPlaylistSubmenu() { + this.clearSubmenuCloseTimer(); + if (this.playlistSubmenuOpen) return; this.playlistSubmenuOpen = true; @@ -796,6 +817,8 @@ export class QueuePanel } private closePlaylistSubmenu() { + this.clearSubmenuCloseTimer(); + if (!this.playlistSubmenuOpen) return; this.playlistSubmenuOpen = false; @@ -1417,6 +1440,8 @@ export class QueuePanel this.onContextMenuAction( 'play', )} + @mouseenter=${() => + this.closePlaylistSubmenu()} > + this.closePlaylistSubmenu()} > - this.showPlaylistSubmenu()} + @mouseenter=${() => { + this.clearSubmenuCloseTimer(); + void this.showPlaylistSubmenu(); + }} + @mouseleave=${this + .scheduleSubmenuClose} @click=${(e: Event) => { e.stopPropagation(); void this.showPlaylistSubmenu(); @@ -1464,6 +1495,8 @@ export class QueuePanel this.onContextMenuAction( 'track-details', )} + @mouseenter=${() => + this.closePlaylistSubmenu()} > - e.stopPropagation()} - > +
+ this.clearSubmenuCloseTimer()} + @mouseleave=${this + .scheduleSubmenuClose} + > + + e.stopPropagation()} + > +
` : nothing} diff --git a/frontend/src/components/track-list/track-list.ts b/frontend/src/components/track-list/track-list.ts index f1b779d..fefad59 100644 --- a/frontend/src/components/track-list/track-list.ts +++ b/frontend/src/components/track-list/track-list.ts @@ -104,6 +104,10 @@ export class TrackList extends LitElement implements SelectionHost { private lastActiveTrackPath: string | null = null; + private submenuCloseTimer: ReturnType< + typeof setTimeout + > | null = null; + private closeHandler = () => this.closeContextMenu(); private mousedownCloseHandler = ( @@ -1248,7 +1252,24 @@ export class TrackList extends LitElement implements SelectionHost { } } + private clearSubmenuCloseTimer() { + if (this.submenuCloseTimer !== null) { + clearTimeout(this.submenuCloseTimer); + this.submenuCloseTimer = null; + } + } + + private scheduleSubmenuClose = () => { + this.clearSubmenuCloseTimer(); + this.submenuCloseTimer = setTimeout(() => { + this.submenuCloseTimer = null; + this.closePlaylistSubmenu(); + }, 150); + }; + private async showPlaylistSubmenu() { + this.clearSubmenuCloseTimer(); + if (this.playlistSubmenuOpen) return; this.playlistSubmenuOpen = true; @@ -1271,6 +1292,8 @@ export class TrackList extends LitElement implements SelectionHost { } private closePlaylistSubmenu() { + this.clearSubmenuCloseTimer(); + if (!this.playlistSubmenuOpen) return; this.playlistSubmenuOpen = false; @@ -1672,25 +1695,32 @@ export class TrackList extends LitElement implements SelectionHost {
this.onContextMenuAction('play')} + @mouseenter=${() => this.closePlaylistSubmenu()} > Play this.onContextMenuAction('add-to-queue')} + @mouseenter=${() => this.closePlaylistSubmenu()} > Add to Queue this.onContextMenuAction('play-next')} + @mouseenter=${() => this.closePlaylistSubmenu()} > Play Next this.showPlaylistSubmenu()} + @mouseenter=${() => { + this.clearSubmenuCloseTimer(); + void this.showPlaylistSubmenu(); + }} + @mouseleave=${this.scheduleSubmenuClose} @click=${(e: Event) => { e.stopPropagation(); void this.showPlaylistSubmenu(); @@ -1707,6 +1737,8 @@ export class TrackList extends LitElement implements SelectionHost { this.onContextMenuAction( 'track-details', )} + @mouseenter=${() => + this.closePlaylistSubmenu()} > ${this.playlistSubmenuOpen && this.selection.hasSelection ? html` - e.stopPropagation()} - > +
+ this.clearSubmenuCloseTimer()} + @mouseleave=${this.scheduleSubmenuClose} + > + e.stopPropagation()} + > +
` : nothing}