diff --git a/frontend/src/components/page-header/page-header.ts b/frontend/src/components/page-header/page-header.ts index 723ad46..29d064a 100644 --- a/frontend/src/components/page-header/page-header.ts +++ b/frontend/src/components/page-header/page-header.ts @@ -320,10 +320,25 @@ export class PageHeader extends LitElement { query that no tier here renders, which is exactly how the seek bar's phone rule came to be dead for months. - Only the *width* of this reaches the overflow fit below: - that pass measures inline size, so the height costs it - nothing, and the two square controls grow the header's - content by 22px in total. */ + **The height is the box and the width is not**, and that + asymmetry is the whole of what the overflow fit below + cares about. That pass measures inline size, so a taller + control costs it nothing and a wider one costs it + directly. Growing the two square controls to 44px wide + added 22px, which fits at every width Chromium was + checked at and clipped the overflow trigger at 320px in + **WebKit** -- the engine closest to what actually ships, + and the one no machine here can run. So the horizontal + half is padding with the margin cancelling it, which is + what the issue asked for in the first place: the target + grows and the layout does not. + + The cost is that a horizontal target can now overlap a + neighbour, which the box version could not. The arrow's + is deliberately lopsided for the seek bar's reason + (#187): the select is 6px to its left and there is open + space to its right, so it takes the side with nothing to + steal from. */ .sort select { font: inherit; color: inherit; @@ -347,9 +362,16 @@ export class PageHeader extends LitElement { padding: 3px 5px; /* 28x21 before this, the smallest control in the header and the only one that failed the floor in - both directions. */ - min-inline-size: 44px; + both directions. + + Vertically the box grows, because the header has the + room and nothing measures it. Horizontally the box + must not: 28 + 2 + 14 is a 44px target over a 28px + layout box, weighted right because the select is 6px + to the left. */ min-block-size: 44px; + padding-inline: 5px 21px; + margin-inline: 0 -16px; } .sort-dir:hover { @@ -417,8 +439,12 @@ export class PageHeader extends LitElement { padding: 6px 10px; /* 38x27, and it is the route to every collapsed action, so it is the last control that should be - hard to hit. */ - min-inline-size: 44px; + hard to hit -- and the one WebKit clipped at 320px + when this was 6px wider as a box. 38 + 3 + 3 is a + 44px target over a 38px layout box; the actions row + has an 8px gap, so this one can be symmetric. */ + padding-inline: 13px; + margin-inline: -3px; } /* The display: flex above outranks the UA stylesheet's diff --git a/frontend/src/components/search-dialog/search-trigger.ts b/frontend/src/components/search-dialog/search-trigger.ts index d3e8efe..45e9765 100644 --- a/frontend/src/components/search-dialog/search-trigger.ts +++ b/frontend/src/components/search-dialog/search-trigger.ts @@ -73,9 +73,20 @@ export class SearchTrigger extends LitElement { second opinion about it (#186). The rest of that comment said the header's own action buttons are smaller because they carry a label; they are 44px - now too, so that no longer distinguishes anything. */ + now too, so that no longer distinguishes anything. + + The extra width is a target rather than a box, for + page-header's reason: this button sits in that + header, whose overflow fit (#69) measures inline + size, and four pixels there is four pixels the + trigger for every collapsed action does not get at + 320px. Height is free -- nothing measures it. */ min-width: 44px; min-height: 44px; + /* Border-box, so the 44 above is the whole target and + the margin is what hands the four extra pixels back + to the row. */ + margin-inline: -2px; padding: 0; background: none; border: 1px solid var(--yj-border-subtle, #555); diff --git a/frontend/test/components/touch-targets.test.ts b/frontend/test/components/touch-targets.test.ts index f5c459f..e6c0912 100644 --- a/frontend/test/components/touch-targets.test.ts +++ b/frontend/test/components/touch-targets.test.ts @@ -90,6 +90,46 @@ describe("the page header's controls", () => { expect(tooSmall(controls)).toEqual([]); }); + it('grows the target without growing the box, so the overflow fit is untouched', async () => { + // The regression this exists for, and it was a real one: growing + // the two square controls to 44px *wide* added 22px to the header, + // which fit at every width Chromium was checked at and clipped the + // overflow trigger at 320x600 in WebKit -- the engine closest to + // what ships, and the one no machine here can run. #69's fit pass + // measures inline size, so a taller control is free and a wider one + // is not. + // + // Negative inline margins are what keep the box out of it: the + // padding makes the target, and the margin gives the space back. + const el = await fixture('page-header', { + heading: 'Playlists', + sortOptions: SORTS, + sortField: 'name', + actions: actions(), + }); + + el.style.width = '320px'; + + for (let frame = 0; frame < 3; frame += 1) { + await new Promise((r) => requestAnimationFrame(r)); + await el.updateComplete; + } + + for (const selector of ['.sort-dir', '.more-button']) { + const control = shadowAll(el, selector).filter( + (c) => !(c as HTMLButtonElement).hidden, + )[0]; + + expect(control, selector).toBeTruthy(); + + const style = getComputedStyle(control!); + const added = + parseFloat(style.marginInlineStart) + parseFloat(style.marginInlineEnd); + + expect(added, `${selector} gives its extra width back`).toBeLessThan(0); + } + }); + it('includes the overflow trigger, which is the route to the rest', async () => { // At 320px the fit pass collapses actions into the menu, so the // trigger is rendered — and it is then the only way to reach them,