mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 06:25:41 +00:00
fix(ui): overlay scrollbars, resizer hit-test, and Linux mini wheel (#255)
Add OverlayScrollArea with shared thumb metrics and drag handling; size the thumb against the rail track height so panel insets cannot push it past the visible rail. Route scroll uses a stable viewport id; Genres restores scroll and infinite-scroll observation against that viewport (merged with upstream virtualized genres list and lazy routes behind Suspense). Suppress queue resizer activation when the pointer targets the main-route overlay scrollbar; disable the resizer while dragging the thumb and use a grabbing cursor on the body. Apply WebKitGTK smooth wheel to the mini webview as well as main; the mini window reapplies the persisted setting after auth store hydration.
This commit is contained in:
@@ -9418,47 +9418,19 @@ html.no-compositing .fs-seekbar-played {
|
||||
}
|
||||
|
||||
|
||||
.mini-queue-wrap {
|
||||
.mini-queue-wrap.overlay-scroll {
|
||||
flex: 1 1 auto;
|
||||
position: relative;
|
||||
background: var(--bg-card);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.mini-queue {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
.mini-queue.overlay-scroll__viewport {
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
box-sizing: border-box;
|
||||
/* Hide native scrollbar — we render an overlay thumb beside it. */
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.mini-queue::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mini-queue__thumb {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 3px;
|
||||
width: 4px;
|
||||
border-radius: 2px;
|
||||
background: var(--text-muted);
|
||||
opacity: 0;
|
||||
transition: opacity 0.18s ease;
|
||||
pointer-events: none;
|
||||
will-change: transform;
|
||||
}
|
||||
.mini-queue-wrap:hover .mini-queue__thumb,
|
||||
.mini-queue.is-scrolling ~ .mini-queue__thumb {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.mini-queue__empty {
|
||||
@@ -9490,10 +9462,6 @@ html.no-compositing .fs-seekbar-played {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.mini-queue-wrap--drop-active .mini-queue {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
|
||||
.mini-queue__item--current {
|
||||
background: var(--accent-dim);
|
||||
color: var(--accent);
|
||||
|
||||
+135
-3
@@ -197,6 +197,13 @@
|
||||
|
||||
.resizer-queue { right: calc(var(--queue-width) - 3px); }
|
||||
|
||||
/* While dragging an overlay scrollbar thumb, hide queue resizer so the
|
||||
pointer does not pick up col-resize / queue resize (thumb sits under the
|
||||
resizer hit box near the main/queue edge). */
|
||||
body.is-overlay-scrollbar-thumb-drag .resizer {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
/* In native fullscreen the resizer sits flush against the screen edge and
|
||||
would interfere with OS edge-swipe gestures — hide it entirely. */
|
||||
.app-shell[data-fullscreen] .resizer { display: none; }
|
||||
@@ -242,11 +249,22 @@
|
||||
|
||||
.sidebar-nav {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar-nav-scroll.overlay-scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.sidebar-nav-scroll .overlay-scroll__viewport.sidebar-nav-viewport {
|
||||
padding: var(--space-1) var(--space-3) var(--space-4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Library scope: Navidrome-style dropdown trigger + fixed panel (portal) */
|
||||
@@ -1086,6 +1104,28 @@
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Main route stack: scroll + overlay thumb live in the inner viewport, not the
|
||||
outer .content-body, so sticky headers still anchor to the scrolling element. */
|
||||
.content-body.app-shell-route-host {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
contain: none;
|
||||
}
|
||||
|
||||
.app-shell-route-scroll.overlay-scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.app-shell-route-scroll .app-shell-route-scroll__viewport {
|
||||
contain: paint;
|
||||
}
|
||||
|
||||
/* Sticky page header: keeps page title + filter/search bar visible while the
|
||||
body scrolls. Negative horizontal margins (+ matching padding) make the
|
||||
background flush with the scroll container edges so the sticky block looks
|
||||
@@ -1907,9 +1947,101 @@ html[data-platform="windows"] .player-bar.floating {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.queue-list {
|
||||
flex: 1;
|
||||
/* ─── OverlayScrollArea (shared: queue, mini, sidebar, menus) ─────────────── */
|
||||
.overlay-scroll {
|
||||
--overlay-rail-top: 0px;
|
||||
--overlay-rail-bottom: 0px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.overlay-scroll--rail-inset-none {
|
||||
--overlay-rail-top: 0px;
|
||||
--overlay-rail-bottom: 0px;
|
||||
}
|
||||
|
||||
.overlay-scroll--rail-inset-mini {
|
||||
--overlay-rail-top: 4px;
|
||||
--overlay-rail-bottom: 4px;
|
||||
}
|
||||
|
||||
.overlay-scroll--rail-inset-panel {
|
||||
--overlay-rail-top: var(--space-2);
|
||||
--overlay-rail-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.overlay-scroll--viewport-scroll-auto .overlay-scroll__viewport {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
|
||||
.overlay-scroll__viewport {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
box-sizing: border-box;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.overlay-scroll__viewport::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.overlay-scroll__rail {
|
||||
position: absolute;
|
||||
top: var(--overlay-rail-top);
|
||||
right: 0;
|
||||
bottom: var(--overlay-rail-bottom);
|
||||
width: 14px;
|
||||
z-index: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.overlay-scroll__thumb {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 2px;
|
||||
width: 3px;
|
||||
border-radius: 2px;
|
||||
background: var(--text-muted);
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity 0.18s ease,
|
||||
width 0.2s ease,
|
||||
border-radius 0.2s ease;
|
||||
pointer-events: none;
|
||||
will-change: transform;
|
||||
cursor: grab;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.overlay-scroll__viewport.is-scrolling ~ .overlay-scroll__rail .overlay-scroll__thumb,
|
||||
.overlay-scroll:hover .overlay-scroll__rail .overlay-scroll__thumb {
|
||||
opacity: 0.55;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.overlay-scroll__rail:hover .overlay-scroll__thumb,
|
||||
.overlay-scroll__rail:has(.overlay-scroll__thumb.is-thumb-dragging) .overlay-scroll__thumb {
|
||||
width: 9px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.overlay-scroll__rail .overlay-scroll__thumb.is-thumb-dragging {
|
||||
opacity: 0.9;
|
||||
pointer-events: auto;
|
||||
cursor: grabbing;
|
||||
transition: opacity 0.18s ease;
|
||||
}
|
||||
|
||||
.queue-list-wrap.overlay-scroll {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.queue-list.overlay-scroll__viewport {
|
||||
padding: var(--space-2);
|
||||
}
|
||||
|
||||
|
||||
@@ -3706,6 +3706,13 @@ body.is-dragging * {
|
||||
cursor: col-resize !important;
|
||||
}
|
||||
|
||||
/* Overlay scrollbar thumb drag — override queue resizer cursor (z-index). */
|
||||
body.is-overlay-scrollbar-thumb-drag,
|
||||
body.is-overlay-scrollbar-thumb-drag * {
|
||||
user-select: none !important;
|
||||
cursor: grabbing !important;
|
||||
}
|
||||
|
||||
/* ─── Mouse-event DnD cursor (psy-dragging) ──────────────────────
|
||||
Applied by DragDropProvider while a mouse-based drag is active.
|
||||
Overrides cursor globally to "grabbing" and prevents text-
|
||||
|
||||
Reference in New Issue
Block a user