diff --git a/frontend/src/components/config-page/config-field.ts b/frontend/src/components/config-page/config-field.ts index 032c784..5bbd352 100644 --- a/frontend/src/components/config-page/config-field.ts +++ b/frontend/src/components/config-page/config-field.ts @@ -85,6 +85,21 @@ export class ConfigField extends LitElement { gap: 0.5em; } + /* Every control here meets the app's 44px touch floor (#186). + + This is the shape every row in Settings uses, so it is the + one rule that covers the most controls -- and it is the + *cheapest* place to reach the floor, because there is no + overflow fit on this page. The page header's had one (#69), + which is why that pass had to grow padding and hand the + width back with a negative margin; here the control is a + block in a column and a taller box costs nothing but the + height it takes. + + Measured on the reference device before this: the select + 335x30, the text and number inputs the same, the browse + button 30 tall, the colour swatch 33x33 and the toggle + **34x19**. */ input[type='text'], input[type='number'] { background: var(--yj-bg-elevated, #343a40); @@ -95,6 +110,7 @@ export class ConfigField extends LitElement { font-size: 0.85em; font-family: inherit; min-width: 0; + min-block-size: 44px; flex: 1; } @@ -117,6 +133,7 @@ export class ConfigField extends LitElement { font-size: 0.85em; font-family: inherit; cursor: pointer; + min-block-size: 44px; flex: 1; } @@ -139,6 +156,7 @@ export class ConfigField extends LitElement { font-size: 0.85em; cursor: pointer; white-space: nowrap; + min-block-size: 44px; } button:hover { @@ -158,8 +176,12 @@ export class ConfigField extends LitElement { } input[type='color'] { - width: 2.5em; - height: 2.5em; + /* border-box, or the 2px border makes this 48 and the + assertion below reads as passing by four pixels of + border rather than by the rule. */ + box-sizing: border-box; + width: 44px; + height: 44px; border: 2px solid var(--yj-border, #444); border-radius: 4px; padding: 0; @@ -187,12 +209,32 @@ export class ConfigField extends LitElement { display: flex; align-items: center; justify-content: space-between; + min-block-size: 44px; } + /* The toggle is the one control here whose target and paint + must differ, and it is also the one no sweep can see. + + Its is opacity: 0; width: 0; height: 0, so a + walk of every input on the page skips it as a zero-sized + node -- the thing a finger actually hits is this diff --git a/frontend/src/components/config-page/download-clients.ts b/frontend/src/components/config-page/download-clients.ts index 6aef7a3..d1bceb8 100644 --- a/frontend/src/components/config-page/download-clients.ts +++ b/frontend/src/components/config-page/download-clients.ts @@ -8,6 +8,7 @@ import '@awesome.me/webawesome/dist/components/switch/switch.js'; import '@awesome.me/webawesome/dist/components/spinner/spinner.js'; import '@awesome.me/webawesome/dist/components/callout/callout.js'; import { designTokens } from '../../styles/tokens.css'; +import { waTouchFloor } from '../../styles/wa-touch-floor.css'; import type { DownloadDescriptor, DownloadProvider, @@ -136,6 +137,7 @@ export class DownloadClients extends LitElement { static override styles = [ designTokens, + waTouchFloor, css` :host { display: block; @@ -239,12 +241,17 @@ export class DownloadClients extends LitElement { margin-top: 0.4em; } + /* The checkbox is 16x16 and cannot grow without becoming + a 44px checkbox, but it is already wrapped in the label + that names it -- so the label is the target and only + needs the height (#186). Eight of them. */ .format-option { display: flex; align-items: center; gap: 0.4em; font-size: 0.9em; cursor: pointer; + min-block-size: 44px; } `, ]; diff --git a/frontend/src/components/config-page/shortcut-capture.ts b/frontend/src/components/config-page/shortcut-capture.ts index 96774fe..b072fd9 100644 --- a/frontend/src/components/config-page/shortcut-capture.ts +++ b/frontend/src/components/config-page/shortcut-capture.ts @@ -25,6 +25,11 @@ export class ShortcutCapture extends LitElement { :host { display: inline-block; } + /* 80x25, twenty-six of them -- the most numerous control on + the Settings page after the column lists (#186). The floor + is a height here and nothing else: the width was already + past it, and the type stays where it is so a shortcut still + reads as a key rather than as a button. */ button { font-family: inherit; font-size: var(--yj-text-sm, 13px); @@ -35,6 +40,7 @@ export class ShortcutCapture extends LitElement { color: var(--yj-text-primary, #eee); cursor: pointer; min-width: 80px; + min-height: 44px; text-align: center; transition: border-color 0.15s, @@ -61,6 +67,11 @@ export class ShortcutCapture extends LitElement { opacity: 0.7; } } + /* Reset renders only for a rebound shortcut, so a sweep of a + freshly-installed app never sees it -- it is not in #186's + tables for that reason, and it is a touch target the moment + anybody uses the feature. It also has no background, so the + padding out to 44px is invisible. */ .reset-btn { font-size: var(--yj-text-xs, 11px); padding: 2px 6px; @@ -69,7 +80,8 @@ export class ShortcutCapture extends LitElement { background: transparent; color: var(--yj-text-tertiary, #888); cursor: pointer; - min-width: auto; + min-width: 44px; + min-height: 44px; opacity: 0; transition: opacity 0.15s; } diff --git a/frontend/src/components/library-filter/library-filter.ts b/frontend/src/components/library-filter/library-filter.ts index f62085c..0a5dc13 100644 --- a/frontend/src/components/library-filter/library-filter.ts +++ b/frontend/src/components/library-filter/library-filter.ts @@ -23,8 +23,14 @@ export class LibraryFilter extends LitElement { align-items: center; } + /* 120x32 on the reference device (#186). This control has two + placements since #57 -- the desktop top bar and Settings -> + Libraries -- and it is the only route to setSelectedLibrary + in either, so it is one of the controls #148 argued must not + simply be taken away. It is one component, so it reaches the + floor in one place. */ select { - height: 32px; + min-height: 44px; padding: 0 8px; border-radius: 6px; border: 1px solid diff --git a/frontend/src/styles/wa-touch-floor.css.ts b/frontend/src/styles/wa-touch-floor.css.ts new file mode 100644 index 0000000..385c9fb --- /dev/null +++ b/frontend/src/styles/wa-touch-floor.css.ts @@ -0,0 +1,48 @@ +import { css } from 'lit'; + +/** + * A Web Awesome form control is at least the app's 44px touch floor. + * + * #56 named 44px and #186 found nothing but the transport had reached + * it. Web Awesome's form controls are the part of Settings this app + * does not draw: measured on the reference device (TLP301, 424x439), + * `wa-input`'s control is **204x20** and `wa-button` **185x21** — the + * shortest controls on the page, and the only ones whose height is + * decided inside somebody else's shadow root. + * + * `--wa-form-control-height` is that decision, and it is the library's + * own theming variable rather than a part or an internal — the default + * theme sets it at `:root` and every control that has a height reads + * it (button, input, select, radio). So this is `wa-slider-label`'s + * better half: the API first, and no reach into a shadow root at all. + * + * Three things about it are load-bearing. + * + * **A custom property inherits through a shadow boundary**, which is + * what lets a `:host` declaration reach a `wa-input` the host renders. + * That is also why it is a stylesheet a component adopts rather than a + * `:root` rule in `index.css`: a `:root` rule would cover every wa + * control in the app in one line and be invisible to the component + * tier, which renders a component and no page stylesheet. Here the + * floor is measurable where it is applied. + * + * **It is a flat 44px rather than a floor over the library's own + * expression.** The default is `round(calc(2 * padding-block + 1em * + * line-height), 1px)` — em-based, so `size="small"` is what produced + * the 20px above — and a `max(44px, …)` would have to restate that + * formula here, which is a copy of somebody else's arithmetic that + * goes stale silently. A flat value is safe because this app uses + * exactly two sizes, `small` and the default, and both are under the + * floor; a `size="large"` added later would be pinned down to 44 and + * should take that as the prompt to revisit this. + * + * **Only the height is pinned.** The font size still comes from + * `size="small"`, so a control grows its hit area without growing its + * visual weight — which is what #186's Direction asks for and what the + * page header's second pass had to be corrected to do. + */ +export const waTouchFloor = css` + :host { + --wa-form-control-height: 44px; + } +`; diff --git a/frontend/test/components/__screenshots__/page-header.test.ts/page-header-filtered-by-search-chromium-linux.png b/frontend/test/components/__screenshots__/page-header.test.ts/page-header-filtered-by-search-chromium-linux.png index 16ec379..2a43fb8 100644 Binary files a/frontend/test/components/__screenshots__/page-header.test.ts/page-header-filtered-by-search-chromium-linux.png and b/frontend/test/components/__screenshots__/page-header.test.ts/page-header-filtered-by-search-chromium-linux.png differ diff --git a/frontend/test/components/__screenshots__/page-header.test.ts/page-header-title-count-and-sort-chromium-linux.png b/frontend/test/components/__screenshots__/page-header.test.ts/page-header-title-count-and-sort-chromium-linux.png index f0e6892..fb47408 100644 Binary files a/frontend/test/components/__screenshots__/page-header.test.ts/page-header-title-count-and-sort-chromium-linux.png and b/frontend/test/components/__screenshots__/page-header.test.ts/page-header-title-count-and-sort-chromium-linux.png differ diff --git a/frontend/test/components/settings-touch-targets.test.ts b/frontend/test/components/settings-touch-targets.test.ts new file mode 100644 index 0000000..efa2e55 --- /dev/null +++ b/frontend/test/components/settings-touch-targets.test.ts @@ -0,0 +1,425 @@ +/** + * Every control in Settings is at least 44px (#186, second pass). + * + * The header pass covered the five controls a user meets on every + * screen. Settings is the other half and is much the larger one: swept + * on the reference device (TLP301, 424x439) with all eleven + * `config-section`s expanded, **120 controls** were under the floor, + * not the 93 the issue's first table implies, and `config-field` — the + * row shape the issue names — is eight of them. The bulk is behind the + * disclosures: + * + * | control | size | count | + * |---|---|---| + * | `.column-arrow-btn` | **16x14** | 36 | + * | `.column-toggle` | 16x16 | 29 | + * | `shortcut-capture` button | 80x**25** | 26 | + * | download format checkbox | 16x16 | 8 | + * | `config-field` select | 335x**30** | 7 | + * | `wa-input` / `wa-button` | 204x**20**, 185x**21** | 6 | + * | `library-filter` select | 120x**32** | 1 | + * | `.overflow-btn` | 31x31 | 1 | + * + * **This tier can measure it, unlike #187's seek bar**, for the reason + * the header pass gives: the controls are real elements and the rules + * are min-sizes, so a real Chromium rendering a real component gives + * the actual answer at any width. And unlike the header there is no + * overflow fit on this page, so nothing here needs the negative-margin + * treatment — height is free and the two square controls can simply be + * square. + * + * **What the sweep cannot see is written down here as a test rather + * than as a comment**, because it is the trap this whole issue keeps + * setting. Two controls are invisible to a walk of `button, select, + * input`: `config-field`'s toggle, whose `` is + * `opacity: 0; width: 0; height: 0` so the thing a finger hits is the + * `