feat(switcher): rebuild the playlist switcher as an overlay popover

The switcher's mat-menu becomes a CDK connected overlay
(app-playlist-switcher-panel), 440px wide, with a dimmed, blurred
backdrop; only its list scrolls and row menus open above it.

- Header with the source count, Add playlist and All sources.
- Search by name or host, autofocused; the type filter is one segmented
  control (All / M3U / Xtream / Stalker with counts) instead of three
  checkboxes, remembered between openings (the old checkbox setting is
  migrated). The search is no longer kept between openings.
- "Recent" (the three most recently opened, kept across restarts) above
  "All" in the Sources page order, as compact source rows: the current
  one tinted with a check. Each row's ⋯ menu holds Refresh, Playlist
  info, Account info, Check again and Remove (with Undo); the footer's
  info, account and add actions moved there and to the header.
- Keyboard: a listbox driven by aria-activedescendant from the search
  field. Arrow keys move and wrap, Enter opens, Shift+F10 opens the
  row's menu, Escape closes and returns focus to the trigger. Hints in
  the footer.
- Deleting from a row menu moves focus to the toast's Undo, since the
  row (and its focus) left the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-12 22:34:22 +02:00
1 parent 106414c118
commit 9feab4f83b
49 files changed
+1987 -1813

No files matched your search

+4 -7
View File
@@ -61,18 +61,15 @@ async function openPlaylistDetailsDialog(page: Page, title: string) {
await playlistSettingsButton.click();
} else {
await page
.locator('app-playlist-switcher .playlist-switcher-trigger')
.locator('[data-test-id="playlist-switcher-trigger"]')
.click();
const switcherMenu = page.getByRole('menu').filter({ hasText: title });
const sourceRow = switcherMenu
.locator('.playlist-item')
const sourceRow = page
.locator('app-playlist-switcher-panel [role="option"]')
.filter({ hasText: title })
.first();
await expect(sourceRow).toBeVisible();
await sourceRow
.getByRole('button', { name: 'Playlist actions' })
.click();
await sourceRow.locator('.source-row__more').click();
await page.getByRole('menuitem', { name: 'Playlist info' }).click();
}