From 1e91d148dce3ba7c6dd20104544348043ccbc212 Mon Sep 17 00:00:00 2001 From: eleith Date: Wed, 28 Oct 2026 10:26:47 -0700 Subject: [PATCH] Improve control feedback and URL copying --- .changeset/clear-control-feedback.md | 5 ++++ apps/web/e2e/details-dialog.e2e.ts | 10 +++++++- .../bookmarks/BookmarkDetails.svelte | 3 +++ apps/web/src/lib/components/ui/controls.css | 23 ++++++++----------- docs/directory.example.yaml | 2 +- 5 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 .changeset/clear-control-feedback.md diff --git a/.changeset/clear-control-feedback.md b/.changeset/clear-control-feedback.md new file mode 100644 index 0000000..6fbbcce --- /dev/null +++ b/.changeset/clear-control-feedback.md @@ -0,0 +1,5 @@ +--- +'@directory/web': patch +--- + +Select the full destination URL on focus or click and make button hover and focus states more distinct. diff --git a/apps/web/e2e/details-dialog.e2e.ts b/apps/web/e2e/details-dialog.e2e.ts index 89d6d7e..77f188b 100644 --- a/apps/web/e2e/details-dialog.e2e.ts +++ b/apps/web/e2e/details-dialog.e2e.ts @@ -75,7 +75,9 @@ test('direct SSR enhances details once after hydration, without flashing plain d } }); -test('focus trap, dismissal, history and return focus work together', async ({ page }) => { +test('focus trap, URL selection, dismissal, history and return focus work together', async ({ + page +}) => { await page.goto(server.url); await hydrated(page); const tile = page.locator('a[href="/?link=beta"]'); @@ -88,6 +90,12 @@ test('focus trap, dismissal, history and return focus work together', async ({ p await expect(close).toBeFocused(); await page.keyboard.press('Tab'); await expect(destination).toBeFocused(); + const destinationLength = (await destination.inputValue()).length; + await expect(destination).toHaveJSProperty('selectionStart', 0); + await expect(destination).toHaveJSProperty('selectionEnd', destinationLength); + await destination.click(); + await expect(destination).toHaveJSProperty('selectionStart', 0); + await expect(destination).toHaveJSProperty('selectionEnd', destinationLength); await page.keyboard.press('Tab'); await expect(external).toBeFocused(); await page.keyboard.press('Shift+Tab'); diff --git a/apps/web/src/lib/components/bookmarks/BookmarkDetails.svelte b/apps/web/src/lib/components/bookmarks/BookmarkDetails.svelte index 41cb273..ed8d63e 100644 --- a/apps/web/src/lib/components/bookmarks/BookmarkDetails.svelte +++ b/apps/web/src/lib/components/bookmarks/BookmarkDetails.svelte @@ -79,6 +79,9 @@ id={`destination-${link.id}`} type="url" value={link.url} + onfocus={(event) => event.currentTarget.select()} + onmouseup={(event) => event.preventDefault()} + onclick={(event) => event.currentTarget.select()} readonly spellcheck="false" /> diff --git a/apps/web/src/lib/components/ui/controls.css b/apps/web/src/lib/components/ui/controls.css index a1e337c..8f61a61 100644 --- a/apps/web/src/lib/components/ui/controls.css +++ b/apps/web/src/lib/components/ui/controls.css @@ -22,9 +22,6 @@ } .ui-button--primary { - --ui-control-focus-color: var(--color-on-primary); - --ui-control-focus-background: var(--color-primary-fill); - min-width: 80px; font-weight: 600; color: var(--color-on-primary); @@ -149,7 +146,7 @@ .ui-button:not(:disabled):hover { background: color-mix( in srgb, - var(--ui-control-hover-color, var(--color-text)) var(--ui-control-hover-amount, 8%), + var(--ui-control-hover-color, var(--color-text)) var(--ui-control-hover-amount, 16%), var(--color-surface-card) ); box-shadow: inset 0 0 0 var(--border-width) var(--color-border-strong); @@ -158,7 +155,7 @@ .ui-button--primary:not(:disabled):hover { background: var(--color-primary-fill); box-shadow: - inset 0 0 0 var(--border-width) var(--color-on-primary), + inset 0 0 0 2px var(--color-on-primary), var(--shadow-card); } @@ -167,15 +164,16 @@ border-color: var(--color-border-strong); } -.ui-button:not(:disabled):focus, +.ui-button:not(:disabled):focus { + outline: 3px solid var(--color-text); + outline-offset: 2px; +} + .ui-input:has(> .ui-input__control:enabled:focus) { - background: var( - --ui-control-focus-background, - color-mix(in srgb, var(--color-primary) 16%, var(--color-surface-card)) - ); - border-color: var(--ui-control-focus-color, var(--color-primary)); + background: color-mix(in srgb, var(--color-primary) 16%, var(--color-surface-card)); + border-color: var(--color-primary); box-shadow: none; - outline: 2px solid var(--ui-control-focus-color, var(--color-primary)); + outline: 2px solid var(--color-primary); outline-offset: -2px; } @@ -186,7 +184,6 @@ .ui-button--primary:not(:disabled):active { background: var(--color-primary-fill); box-shadow: inset 0 0 0 3px var(--color-on-primary); - outline: none; } .ui-button:disabled, diff --git a/docs/directory.example.yaml b/docs/directory.example.yaml index 8f4a8b0..f5c4da7 100644 --- a/docs/directory.example.yaml +++ b/docs/directory.example.yaml @@ -42,7 +42,7 @@ links: photos: title: Photos url: https://photos.example.com - description: | + description: >- Family photos and videos, all in one place. Install your photo service's mobile app for automatic uploads. tags: [photos, videos, memories] -- 2.54.0