mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 21:26:15 -08:00
fix(switcher): keep the search field and type filter whole with many sources
The switcher popover is a height-capped flex column. With a long list (hundreds of sources) the browser shrank every part in proportion to its size, not only the list: the type filter, which clips its overflow, collapsed to 2px and showed no labels, and the search field dropped from 36px to its 21px text line. The header, search, type filter, empty state and footer are now flex: none, so only the list shrinks and scrolls. The search field now matches the header and side-panel search fields: the shared --app-search-* tokens, a 10px radius, an 18px icon, the same text size and focus halo. The Electron E2E opens the switcher in a 320px-tall window and checks the search height, the type toggles and the scrolling list; it fails without the fix (search 21px). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8f36c55e72
commit
7f462208b1
3 files changed
+68
-12
No files matched your search
@@ -530,6 +530,41 @@ test.describe('Electron Playlist Switcher', () => {
|
||||
await expect(panel).toHaveCount(0);
|
||||
await expect(trigger).toBeFocused();
|
||||
|
||||
// In a short window only the list shrinks and scrolls; the
|
||||
// search field and the type filter keep their height.
|
||||
const viewport = page.viewportSize();
|
||||
await page.setViewportSize({
|
||||
width: viewport?.width ?? 1280,
|
||||
height: 320,
|
||||
});
|
||||
await page.keyboard.press('ControlOrMeta+p');
|
||||
await expect(search).toBeFocused();
|
||||
const chrome = await panel.evaluate((host) => {
|
||||
const height = (element: Element | null) =>
|
||||
element?.getBoundingClientRect().height ?? 0;
|
||||
const list = host.querySelector('.switcher__list');
|
||||
return {
|
||||
search: height(host.querySelector('.switcher__search')),
|
||||
toggles: Array.from(
|
||||
host.querySelectorAll('.mat-button-toggle'),
|
||||
height
|
||||
),
|
||||
listScrolls:
|
||||
!!list && list.scrollHeight > list.clientHeight,
|
||||
};
|
||||
});
|
||||
expect(chrome.search, JSON.stringify(chrome)).toBeCloseTo(36, 0);
|
||||
expect(chrome.toggles.length, JSON.stringify(chrome)).toBe(3);
|
||||
for (const toggle of chrome.toggles) {
|
||||
expect(toggle, JSON.stringify(chrome)).toBeGreaterThanOrEqual(
|
||||
30
|
||||
);
|
||||
}
|
||||
expect(chrome.listScrolls, JSON.stringify(chrome)).toBe(true);
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(panel).toHaveCount(0);
|
||||
if (viewport) await page.setViewportSize(viewport);
|
||||
|
||||
await openSources(page);
|
||||
await expect(
|
||||
page.locator('app-workspace-sources app-source-row')
|
||||
|
||||
@@ -1386,7 +1386,11 @@ in `@iptvnator/playlist/shared/util` (`source-display`, `source-format`,
|
||||
the highlighted row's menu, Escape closes and returns focus to the
|
||||
trigger. The compact row's ⋯ is pointer-only (`aria-hidden`, not
|
||||
focusable), since an option may not contain a control. "Recent" lists the
|
||||
three most recently opened sources once there are six or more.
|
||||
three most recently opened sources once there are six or more. The search
|
||||
field is the shared 36px search field (`--app-search-*`). The popover is a
|
||||
height-capped column in which only the list shrinks and scrolls: every
|
||||
other part is `flex: none`. Otherwise a long list (hundreds of sources)
|
||||
squeezes them in proportion to its size, and the type filter collapses.
|
||||
|
||||
### Network source indicators (Electron)
|
||||
|
||||
|
||||
+28
-11
@@ -18,6 +18,19 @@
|
||||
color: var(--app-heading-color);
|
||||
}
|
||||
|
||||
// Only the list shrinks and scrolls. A column flex container shrinks its
|
||||
// items in proportion to their size, so with a long list (hundreds of
|
||||
// sources) the fixed parts lost nearly all their height too: the type
|
||||
// filter, which clips its overflow, collapsed to 2px and the search field
|
||||
// to its text line.
|
||||
.switcher__header,
|
||||
.switcher__search,
|
||||
.switcher__types,
|
||||
.switcher__empty,
|
||||
.switcher__footer {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.switcher__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -46,32 +59,36 @@
|
||||
@include controls.ghost-icon-button(32px, 18px);
|
||||
}
|
||||
|
||||
// The search field is one step darker than the popover. Its focus shows on
|
||||
// the field (token border and halo), not as an outline on the input.
|
||||
// The same search field as the header and the side panels: 36px, the
|
||||
// shared search tokens, focus on the field (token border and halo) rather
|
||||
// than as an outline on the input.
|
||||
.switcher__search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
gap: 8px;
|
||||
height: 36px;
|
||||
margin: 0 14px 10px;
|
||||
padding: 0 10px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--app-line-strong);
|
||||
border-radius: 9px;
|
||||
background: var(--app-content-bg);
|
||||
border: 1px solid var(--app-search-border, var(--app-line-strong));
|
||||
border-radius: 10px;
|
||||
background: var(--app-search-bg, var(--app-content-bg));
|
||||
color: var(--app-muted-color);
|
||||
transition:
|
||||
border-color 0.15s ease,
|
||||
box-shadow 0.15s ease;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--app-focus-ring);
|
||||
box-shadow: 0 0 0 3px
|
||||
color-mix(in srgb, var(--app-focus-ring) 18%, transparent);
|
||||
color-mix(in srgb, var(--app-focus-ring) 12%, transparent);
|
||||
}
|
||||
|
||||
.mat-icon {
|
||||
flex: none;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
font-size: 15px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
input {
|
||||
@@ -82,7 +99,7 @@
|
||||
background: none;
|
||||
color: var(--app-heading-color);
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
font-size: 0.84rem;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--app-muted-color);
|
||||
|
||||
Reference in new issue
Block a user