From 50821aae44b373436960dca46487f1b3c91a983a Mon Sep 17 00:00:00 2001 From: dev Date: Tue, 21 Apr 2026 01:49:02 +0200 Subject: [PATCH] =?UTF-8?q?soma:=20P3=20cleanup=20=E2=80=94=20LinkPreview?= =?UTF-8?q?=20a11y=20section=20+=20TagGroup=20key=20API=20consistency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LinkPreview README: elevate the "intentionally hidden from AT" note to its own `## Accessibility` section with the three reasons (duplicate content, hover-only activation, focus stays on trigger). Consumers reaching for an AT-visible preview are pointed at Popover / Tooltip. - TagGroup `handleItemKeydown`: vertical nav now calls `getDirectionalKeys(dir, 'vertical')` (returns UP/DOWN absolute) instead of hardcoded `KEYS.ARROW_DOWN/UP`. Functionally identical — vertical keys don't flip under RTL — but keeps the shape symmetric with the horizontal branch and matches the convention used across the library. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../soma/components/link-preview/README.md | 23 ++++++++++++++++++- .../tag-group/tag-group-provider.svelte.ts | 8 +++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/uix/soma/components/link-preview/README.md b/src/uix/soma/components/link-preview/README.md index 9a59bfc2e..fdf46a8df 100644 --- a/src/uix/soma/components/link-preview/README.md +++ b/src/uix/soma/components/link-preview/README.md @@ -30,7 +30,28 @@ A floating preview card that appears on hover over a link. Supports configurable | ------- | ------------- | ------ | | Content | `aria-hidden` | `true` | -The preview content is intentionally hidden from assistive technology — it is a visual convenience only. The underlying link remains fully accessible. +## Accessibility + +**LinkPreview is invisible to assistive technology by design.** The floating +preview card exists for sighted-hover discovery only — it adds no +information a screen-reader user could not already reach through the +underlying `` element. Emitting `aria-hidden="true"` on `Content` is a +deliberate choice: + +- **Duplicate content:** the preview typically mirrors the destination page + summary that a screen reader would announce after following the link. + Re-reading it as a tooltip is noise, not help. +- **Hover-only activation:** APG explicitly recommends against exposing + pure-hover widgets to assistive tech because keyboard / touch users never + see them anyway — making them invisible everyone except mouse hover is + the consistent experience. +- **Focus stays on the trigger:** the preview never steals focus, never + traps Tab, and closes automatically on blur. There is nothing for a + keyboard / AT user to miss. + +If your LinkPreview contains content that is NOT in the destination page +(a custom CTA, a badge, a pricing hint), consider using `Popover` or +`Tooltip` instead — both are AT-visible. ## Data Attributes diff --git a/src/uix/soma/components/tag-group/tag-group-provider.svelte.ts b/src/uix/soma/components/tag-group/tag-group-provider.svelte.ts index ead119ba6..fd721abc0 100644 --- a/src/uix/soma/components/tag-group/tag-group-provider.svelte.ts +++ b/src/uix/soma/components/tag-group/tag-group-provider.svelte.ts @@ -156,7 +156,11 @@ export class TagGroupProvider extends Provider { handleItemKeydown(e: SomaKeyboardEvent, value: string) { if (this.opts.disabled.current) return; const dir = this.resolvedDir.current; + // Horizontal flips under RTL; vertical doesn't (UP / DOWN are absolute + // per APG). Using `getDirectionalKeys` for both axes keeps the shape + // symmetric — consistent with other components that navigate grids. const { nextKey, prevKey } = getDirectionalKeys(dir, 'horizontal'); + const { nextKey: downKey, prevKey: upKey } = getDirectionalKeys(dir, 'vertical'); const items = this.getItems(); if (items.length === 0) return; const currentIndex = items.indexOf(e.currentTarget); @@ -171,9 +175,9 @@ export class TagGroupProvider extends Provider { this.focusAt(currentIndex - 1); return; } - if (e.key === KEYS.ARROW_DOWN || e.key === KEYS.ARROW_UP) { + if (e.key === downKey || e.key === upKey) { e.preventDefault(); - const delta = e.key === KEYS.ARROW_DOWN ? 1 : -1; + const delta = e.key === downKey ? 1 : -1; this.focusAt(currentIndex + delta); return; }