feat(albums): draw the album dropdown that was already being computed
Enter on an album card fetched the album's tracks over the IPC and ran the whole split state machine (splitMode true, splitIndex measured against the real container), then render() drew the single grid because it never consulted splitMode; connectedCallback referenced renderSplitGrid only to satisfy noUnusedLocals. perf.p2 files this as dead code — it is the only route from the albums grid to track-details, since a plain click navigates to the catalog page instead. Two things it needed that the audit does not mention. The grid could not scroll: .grid-scroll-container is the markup artists-view and genres-view use, and cover-grid had the class with no rule for it, so 186984px of albums sat in a 772px box at 5000 albums, unreachable by wheel, keyboard or scrollbar — and that is the element scroll-manager saves and restores, so its scrollTop was permanently 0. And the shared context menu was labelled 'Album actions' unconditionally, which nothing could observe while a track menu was unreachable. Both halves of the split grid carry the listbox semantics the single grid gained in the ARIA pass.
This commit is contained in:
@@ -13,6 +13,27 @@ const gridStyles = css`
|
||||
contain: layout style;
|
||||
}
|
||||
|
||||
/*
|
||||
* The scroller. artists-view and genres-view carry the same
|
||||
* markup with this rule; cover-grid had the class and no rule for
|
||||
* it, so nothing in the albums view scrolled — the container grew to
|
||||
* its full content height inside an overflow: hidden host and
|
||||
* everything past the first screenful was unreachable by wheel,
|
||||
* keyboard or scrollbar. Invisible on the eight-album fixture and
|
||||
* fatal on a real library: measured at 5 000 albums, 186 984 px of
|
||||
* content in a 772 px box.
|
||||
*
|
||||
* It is also what the dropdown's scroll manager was written
|
||||
* against — it saves and restores this element's scrollTop, which
|
||||
* was permanently 0.
|
||||
*/
|
||||
.grid-scroll-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
contain: paint;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
* Album card
|
||||
* ======================================== */
|
||||
|
||||
@@ -430,10 +430,6 @@ export class CoverGrid
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
// Reference renderSplitGrid so the deferred split-grid
|
||||
// render path (and its track-event helpers) doesn't trip
|
||||
// noUnusedLocals. Never invoked at runtime.
|
||||
void this.renderSplitGrid;
|
||||
this.restoreSortPreferences();
|
||||
this.loadAlbums();
|
||||
|
||||
@@ -1783,7 +1779,18 @@ export class CoverGrid
|
||||
`;
|
||||
}
|
||||
|
||||
const gridContent = this.renderSingleGrid();
|
||||
// The split path draws the dropdown between two grids. Until
|
||||
// this was wired up, `render()` ignored `splitMode` entirely:
|
||||
// pressing Enter on an album card fetched its tracks over the
|
||||
// IPC, ran the whole split state machine (`splitMode: true`,
|
||||
// `splitIndex: 6`, measured against the real container) and
|
||||
// then drew the single grid regardless, so the only route from
|
||||
// the albums grid to a track was the plain click that
|
||||
// navigates away to the catalog page.
|
||||
const gridContent =
|
||||
this.splitMode && this.expandedTracks.length > 0
|
||||
? this.renderSplitGrid()
|
||||
: this.renderSingleGrid();
|
||||
|
||||
return html`
|
||||
${this.renderPageHeader()}
|
||||
@@ -1821,10 +1828,12 @@ export class CoverGrid
|
||||
}
|
||||
|
||||
/**
|
||||
* Dual virtualizer — dropdown sandwiched between
|
||||
* "before" and "after" grids. Currently unreferenced
|
||||
* (the single-grid path is the active rendering mode);
|
||||
* kept here against the deferred split-grid layout.
|
||||
* Dual virtualizer — dropdown sandwiched between the "before" and
|
||||
* "after" halves of the grid.
|
||||
*
|
||||
* Both halves carry the same listbox semantics as the single grid:
|
||||
* they are one control to the user, and a selection that spans the
|
||||
* dropdown must be announced the same way on either side of it.
|
||||
*/
|
||||
private renderSplitGrid() {
|
||||
const sm = this.scrollMgr;
|
||||
@@ -1836,6 +1845,9 @@ export class CoverGrid
|
||||
return html`
|
||||
<lit-virtualizer
|
||||
id="grid-before"
|
||||
role="listbox"
|
||||
aria-label="Albums"
|
||||
aria-multiselectable="true"
|
||||
.items=${this.getBeforeEntries()}
|
||||
.renderItem=${this.renderGridEntry}
|
||||
.keyFunction=${(entry: GridEntry) => entry.album.ID}
|
||||
@@ -1865,6 +1877,9 @@ export class CoverGrid
|
||||
? html`
|
||||
<lit-virtualizer
|
||||
id="grid-after"
|
||||
role="listbox"
|
||||
aria-label="Albums, continued"
|
||||
aria-multiselectable="true"
|
||||
.items=${afterEntries}
|
||||
.renderItem=${this.renderGridEntry}
|
||||
.keyFunction=${(entry: GridEntry) => entry.album.ID}
|
||||
@@ -1904,7 +1919,13 @@ export class CoverGrid
|
||||
>
|
||||
${ctxMenu.contextMenuOpen
|
||||
? html`
|
||||
<div class="context-menu-panel" role="menu" aria-label="Album actions">
|
||||
<div
|
||||
class="context-menu-panel"
|
||||
role="menu"
|
||||
aria-label=${this.contextMenuTarget.kind === 'track'
|
||||
? 'Track actions'
|
||||
: 'Album actions'}
|
||||
>
|
||||
<wa-dropdown-item
|
||||
@click=${() =>
|
||||
this.onContextMenuAction(
|
||||
|
||||
Reference in New Issue
Block a user