diff --git a/frontend/src/components/cover-grid/cover-grid.ts b/frontend/src/components/cover-grid/cover-grid.ts index ff5be86..de9e5e3 100644 --- a/frontend/src/components/cover-grid/cover-grid.ts +++ b/frontend/src/components/cover-grid/cover-grid.ts @@ -416,8 +416,9 @@ export class CoverGrid extends LitElement { * Resize-aware scroll preservation * * When the container width changes (e.g. queue panel - * open/close/resize), the CSS grid reflows and the - * absolute scroll position becomes stale. + * open/close, drag-resize, or window resize), the CSS + * grid reflows and the absolute scroll position + * becomes stale. * * We compute the fractional album index at the * viewport center before the resize, then after the @@ -440,11 +441,13 @@ export class CoverGrid extends LitElement { /** * Wire up a ResizeObserver on the scroll container. * - * Uses a debounce pattern to handle animated - * transitions (e.g. the queue panel's 250ms width - * animation). The center album index is captured on - * the first resize event, then restored once resizing - * settles. + * When the column count changes (e.g. queue panel + * open/close), scroll position is corrected + * synchronously in the same frame to avoid flicker. + * For continuous resizes that stay within the same + * column breakpoint (e.g. dragging the queue panel + * handle), a debounce ensures a final correction + * once resizing settles. */ private setupResizeObserver() { const container = this.scrollContainer; @@ -465,10 +468,34 @@ export class CoverGrid extends LitElement { this.currentColumnCount = this.getColumnCount(); + /** Restore scroll so the same album stays + * at the viewport center after a reflow. */ + const restoreScroll = () => { + const pending = + this.pendingCenterIndex; + + this.pendingCenterIndex = null; + + if (!pending) return; + + const newColumns = + this.getColumnCount(); + const newRow = + pending.index / newColumns; + const newCenterY = + GRID_PADDING + + newRow * rowStep; + + container.scrollTop = + newCenterY - + pending.viewportHeight / 2; + + this.currentColumnCount = newColumns; + }; + this.resizeObserver = new ResizeObserver(() => { // Capture on the first event using the - // pre-resize column count stored before - // the animation started. + // pre-resize column count. if (this.pendingCenterIndex === null) { const centerY = container.scrollTop + @@ -485,38 +512,31 @@ export class CoverGrid extends LitElement { }; } - // Reset the debounce timer on every event - // so we wait for the animation to finish. + const newColumns = this.getColumnCount(); + + if (newColumns !== this.currentColumnCount) { + // Column count changed — correct + // scroll immediately to avoid flicker. + if (this.resizeDebounceTimer !== null) { + clearTimeout( + this.resizeDebounceTimer, + ); + this.resizeDebounceTimer = null; + } + + restoreScroll(); + + return; + } + + // Same column count — debounce for a + // final adjustment once resizing settles. if (this.resizeDebounceTimer !== null) { clearTimeout(this.resizeDebounceTimer); } this.resizeDebounceTimer = setTimeout( - () => { - const pending = - this.pendingCenterIndex; - - this.pendingCenterIndex = null; - - if (!pending) return; - - const newColumns = - this.getColumnCount(); - const newRow = - pending.index / newColumns; - const newCenterY = - GRID_PADDING + - newRow * rowStep; - - container.scrollTop = - newCenterY - - pending.viewportHeight / 2; - - // Update for the next resize - // cycle. - this.currentColumnCount = - newColumns; - }, + restoreScroll, 100, ); }); diff --git a/frontend/src/components/queue-panel/queue-panel.ts b/frontend/src/components/queue-panel/queue-panel.ts index 329f95c..136fca7 100644 --- a/frontend/src/components/queue-panel/queue-panel.ts +++ b/frontend/src/components/queue-panel/queue-panel.ts @@ -44,7 +44,6 @@ export class QueuePanel extends LitElement { width: 0; overflow: hidden; background-color: #212529; - transition: width 0.25s ease-in-out; display: flex; flex-direction: row; } @@ -295,9 +294,6 @@ export class QueuePanel extends LitElement { private handleMouseDown = (e: MouseEvent) => { e.preventDefault(); this.isDragging = true; - - // Disable transition during drag for instant feedback. - this.style.transition = 'none'; }; private handleMouseMove = (e: MouseEvent) => { @@ -318,9 +314,6 @@ export class QueuePanel extends LitElement { if (!this.isDragging) return; this.isDragging = false; - - // Re-enable transition after drag ends. - this.style.removeProperty('transition'); }; override render() {