From 5c955d705952cd62cf49947d662e2f52e7af11e8 Mon Sep 17 00:00:00 2001 From: Ridwan Sanusi Date: Mon, 29 Jun 2026 10:06:14 -0400 Subject: [PATCH 1/6] a11y(1.4.13): migrate saved-query nav tooltips to Tooltip primitive Replaces the hand-rolled JS-positioned tooltip in saved-query-views.svelte and standalone-activities/saved-views.svelte with the Holocene Tooltip primitive (PR #3429). Each file previously maintained ~40 lines of custom state and DOM positioning logic (showTooltip, tooltipText, tooltipX/Y, positionTooltipFrom, onQueryBtnEnter/Move/Leave, use:portal floating
) that failed three SC 1.4.13 sub-criteria: - Dismissible: no Escape handler - Hoverable: pointer crossing the wrapper boundary fired onmouseleave before reaching the fixed
, dismissing the tooltip - ARIA: plain
with no aria-describedby on the trigger The Tooltip primitive supplies all three: - Escape-to-dismiss via svelte:window keydown (dismissed flag auto-resets) - Hover bridge: 120 ms HOVER_HIDE_DELAY_MS lets the pointer transit onto the Portal-rendered popover; mouseenter on the popover cancels the hide timer - aria-describedby on the wrapper
; role="tooltip" + id on the popover hide={$savedQueryNavOpen} preserves the existing gate: tooltip is suppressed while the nav rail is expanded (full name already visible). usePortal delegates positioning to the Portal/Floating-UI integration, replacing the manual getBoundingClientRect arithmetic and also closing the 1.4.4 fixed-pixel-width gap on that surface. Net: ~80 lines removed across two files; no new lines of tooltip logic added. A11y-Audit-Ref: 1.4.13-saved-query-hand-rolled-tooltip Co-Authored-By: Claude Sonnet 4.6 --- .../standalone-activities/saved-views.svelte | 71 ++-------------- src/lib/pages/saved-query-views.svelte | 85 +++---------------- 2 files changed, 18 insertions(+), 138 deletions(-) diff --git a/src/lib/components/standalone-activities/saved-views.svelte b/src/lib/components/standalone-activities/saved-views.svelte index 05dab00dc2..7e074256c9 100644 --- a/src/lib/components/standalone-activities/saved-views.svelte +++ b/src/lib/components/standalone-activities/saved-views.svelte @@ -12,6 +12,7 @@ import Button from '$lib/holocene/button.svelte'; import type { IconName } from '$lib/holocene/icon'; import Icon from '$lib/holocene/icon/icon.svelte'; + import Tooltip from '$lib/holocene/tooltip.svelte'; import { translate } from '$lib/i18n/translate'; import { activityRefresh } from '$lib/stores/activities'; import { activityFilters } from '$lib/stores/filters'; @@ -200,50 +201,6 @@ }); }; - function portal( - node: HTMLElement, - target: HTMLElement | string = document.body, - ) { - const targetEl = - typeof target === 'string' - ? (document.querySelector(target) as HTMLElement | null) - : (target as HTMLElement | null); - if (targetEl) targetEl.appendChild(node); - return { - destroy() { - if (node.parentNode) node.parentNode.removeChild(node); - }, - }; - } - - let showTooltip = $state(false); - let tooltipText: string | undefined = $state(); - let tooltipX = $state(0); - let tooltipY = $state(0); - - const positionTooltipFrom = (el: HTMLElement) => { - const rect = el.getBoundingClientRect(); - tooltipX = Math.round(rect.right + 8); - tooltipY = Math.round(rect.top + 16); - }; - - const onQueryBtnEnter = (e: MouseEvent | FocusEvent, name: string) => { - if ($savedQueryNavOpen) return; - const el = e.currentTarget as HTMLElement; - tooltipText = name; - positionTooltipFrom(el); - showTooltip = true; - }; - - const onQueryBtnMove = (e: MouseEvent | FocusEvent) => { - if (!showTooltip) return; - const el = e.currentTarget as HTMLElement; - positionTooltipFrom(el); - }; - - const onQueryBtnLeave = () => { - showTooltip = false; - };
{#snippet queryButton(view: SavedQuery)} -