mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
fix(ui): in-page browse virtual lists and cover load priority (#783)
* fix(ui): in-page browse virtual lists and cover load priority Align virtualizer scrollMargin with the in-page scroll viewport so artist/album rows do not vanish on deep scroll after #731. Resolve CachedImage IntersectionObserver root to the nearest scrolling pane so network fetch slots favor visible covers; throttle Artists infinite scroll to one page per visibleCount update. * chore(release): CHANGELOG and credits for PR #783
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
// @vitest-environment jsdom
|
||||
import { describe, expect, it, beforeEach } from 'vitest';
|
||||
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../../constants/appScroll';
|
||||
import { resolveIntersectionScrollRoot } from './resolveIntersectionScrollRoot';
|
||||
|
||||
describe('resolveIntersectionScrollRoot', () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
it('prefers the nearest scrolling ancestor', () => {
|
||||
const outer = document.createElement('div');
|
||||
const scroller = document.createElement('div');
|
||||
const img = document.createElement('img');
|
||||
Object.defineProperty(scroller, 'scrollHeight', { value: 2000, configurable: true });
|
||||
Object.defineProperty(scroller, 'clientHeight', { value: 400, configurable: true });
|
||||
scroller.style.overflowY = 'auto';
|
||||
outer.appendChild(scroller);
|
||||
scroller.appendChild(img);
|
||||
document.body.appendChild(outer);
|
||||
|
||||
expect(resolveIntersectionScrollRoot(img)).toBe(scroller);
|
||||
});
|
||||
|
||||
it('falls back to mainstage in-page viewport class', () => {
|
||||
const inpage = document.createElement('div');
|
||||
inpage.className = 'mainstage-inpage-scroll__viewport';
|
||||
const img = document.createElement('img');
|
||||
inpage.appendChild(img);
|
||||
document.body.appendChild(inpage);
|
||||
|
||||
expect(resolveIntersectionScrollRoot(img)).toBe(inpage);
|
||||
});
|
||||
|
||||
it('falls back to app main scroll viewport id', () => {
|
||||
const main = document.createElement('div');
|
||||
main.id = APP_MAIN_SCROLL_VIEWPORT_ID;
|
||||
const img = document.createElement('img');
|
||||
main.appendChild(img);
|
||||
document.body.appendChild(main);
|
||||
|
||||
expect(resolveIntersectionScrollRoot(img)).toBe(main);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../../constants/appScroll';
|
||||
|
||||
/**
|
||||
* Scroll container for `IntersectionObserver` priority scoring on cover art.
|
||||
* Prefer the nearest scrolling ancestor (in-page browse, queue, route viewport);
|
||||
* fall back to known viewport class hooks, then the main route scroll element.
|
||||
*/
|
||||
export function resolveIntersectionScrollRoot(node: HTMLElement): Element | null {
|
||||
let parent = node.parentElement;
|
||||
while (parent) {
|
||||
const { overflowY } = window.getComputedStyle(parent);
|
||||
if (
|
||||
(overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay')
|
||||
&& parent.scrollHeight > parent.clientHeight + 2
|
||||
) {
|
||||
return parent;
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
return (
|
||||
(node.closest('.mainstage-inpage-scroll__viewport') as HTMLElement | null)
|
||||
?? (node.closest('.app-shell-route-scroll__viewport') as HTMLElement | null)
|
||||
?? (document.getElementById(APP_MAIN_SCROLL_VIEWPORT_ID) as HTMLElement | null)
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user