mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-12 23:31:30 +00:00
fix(frontend): dismiss tooltips during drawer activation
This commit is contained in:
@@ -1,30 +1,23 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "f5eb87437a396048959a851584d3104326e0d671",
|
||||
"verified_at": "2026-08-25T16:41:20Z",
|
||||
"base_sha": "389ff2825e5228a9113921cd2e3ca6c1d2118962",
|
||||
"verified_at": "2026-08-25T16:54:23Z",
|
||||
"result": "passed",
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/components/Infrastructure/ResourceOperatorStateSection.tsx",
|
||||
"frontend-modern/src/components/Workloads/StackedMemoryBar.tsx",
|
||||
"frontend-modern/src/components/shared/InlineDetailTableRow.tsx",
|
||||
"frontend-modern/src/components/shared/Subtabs.tsx",
|
||||
"frontend-modern/src/features/platformPage/PlatformWindowedRows.tsx"
|
||||
"frontend-modern/src/hooks/useTooltip.ts"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/components/Infrastructure/ResourceOperatorStateSection.tsx": "1a01f6e6c8db585c32b37afae33ed7b6671dc76e40e8f49c364ee78659e8a961",
|
||||
"frontend-modern/src/components/Workloads/StackedMemoryBar.tsx": "3266fa75221643199d41b7a87f8e07181e643171c744ac0e32e2ac9c6773e3bf",
|
||||
"frontend-modern/src/components/shared/InlineDetailTableRow.tsx": "0e169eb088c0bb0754dda5936b52baecc102f1a50853263517d76931729a1673",
|
||||
"frontend-modern/src/components/shared/Subtabs.tsx": "5106f14ec387c72bd2d3014200678f342e28b04d5ff372aa95410695fe0dc544",
|
||||
"frontend-modern/src/features/platformPage/PlatformWindowedRows.tsx": "3f5d02c126379a44803bea3efcc803965d872eb188ec7e0305c973a82fa80924"
|
||||
"frontend-modern/src/hooks/useTooltip.ts": "5cd07282857599694979567df14cbfdb54f853b73f6d5d17b29ab1fffcc51c17"
|
||||
},
|
||||
"routes": [
|
||||
"/docker/overview",
|
||||
"/truenas/overview",
|
||||
"/kubernetes/overview"
|
||||
"/vmware/overview"
|
||||
],
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1280,
|
||||
"height": 720
|
||||
"height": 800
|
||||
},
|
||||
{
|
||||
"width": 390,
|
||||
@@ -32,16 +25,15 @@
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
"TrueNAS system summary with memory-capacity label scrim",
|
||||
"TrueNAS system drawer Manage tab at desktop width",
|
||||
"TrueNAS system drawer Manage tab at phone width",
|
||||
"Kubernetes cluster drawer with five-tab overflow rail before and after scrolling"
|
||||
"Docker host table before expansion with metric hover targets",
|
||||
"Docker auth-service-01 host drawer open without a lingering metric tooltip",
|
||||
"TrueNAS truenas-main system drawer open at phone width",
|
||||
"vSphere etl-batch-01 workload drawer open at phone width"
|
||||
],
|
||||
"interactions": [
|
||||
"opened the TrueNAS system inline drawer and selected Manage",
|
||||
"verified Manage remained selected through repeated realtime refresh samples and after the final main rebase",
|
||||
"inspected wrapped operator-policy help text and the stacked phone maintenance action",
|
||||
"opened a Kubernetes cluster inline drawer at 390px and used its right-edge tab scroll control",
|
||||
"verified the left-edge control appeared after scrolling and restored the default viewport"
|
||||
"activated the auth-service-01 metric-heavy row at 390px and verified the memory and CPU tooltip portals dismissed before the drawer reflow",
|
||||
"repeated the auth-service-01 row activation at desktop width and verified the drawer remained unobscured with no lingering metric tooltip",
|
||||
"opened TrueNAS and vSphere specialized drawers at 390px and inspected placement, clipping, stacking, scrolling, and tab usability",
|
||||
"restored the browser viewport after responsive verification"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -41,6 +41,49 @@ describe('useTooltip', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('dismisses an active hover tooltip when the pointer activates the page', () => {
|
||||
createRoot((dispose) => {
|
||||
const tip = useTooltip();
|
||||
tip.onMouseEnter({
|
||||
clientX: 120,
|
||||
clientY: 80,
|
||||
currentTarget: {
|
||||
ownerDocument: document,
|
||||
getBoundingClientRect: () => ({ left: 10, top: 20, width: 40, height: 16 }),
|
||||
},
|
||||
} as unknown as MouseEvent);
|
||||
|
||||
expect(tip.show()).toBe(true);
|
||||
document.dispatchEvent(new Event('pointerdown', { bubbles: true }));
|
||||
expect(tip.show()).toBe(false);
|
||||
dispose();
|
||||
});
|
||||
});
|
||||
|
||||
it('re-arms pointer dismissal after a subsequent hover', () => {
|
||||
createRoot((dispose) => {
|
||||
const tip = useTooltip();
|
||||
const enter = () =>
|
||||
tip.onMouseEnter({
|
||||
clientX: 120,
|
||||
clientY: 80,
|
||||
currentTarget: {
|
||||
ownerDocument: document,
|
||||
getBoundingClientRect: () => ({ left: 10, top: 20, width: 40, height: 16 }),
|
||||
},
|
||||
} as unknown as MouseEvent);
|
||||
|
||||
enter();
|
||||
document.dispatchEvent(new Event('pointerdown', { bubbles: true }));
|
||||
enter();
|
||||
expect(tip.show()).toBe(true);
|
||||
|
||||
document.dispatchEvent(new Event('pointerdown', { bubbles: true }));
|
||||
expect(tip.show()).toBe(false);
|
||||
dispose();
|
||||
});
|
||||
});
|
||||
|
||||
it('ignores synthesized mouse hover on a coarse touch pointer', () => {
|
||||
vi.stubGlobal(
|
||||
'matchMedia',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createSignal } from 'solid-js';
|
||||
import { createSignal, onCleanup } from 'solid-js';
|
||||
|
||||
import { supportsHoverTooltips } from '@/components/shared/hoverCapability';
|
||||
|
||||
@@ -24,22 +24,47 @@ export interface TooltipPos {
|
||||
export function useTooltip() {
|
||||
const [show, setShow] = createSignal(false);
|
||||
const [pos, setPos] = createSignal<TooltipPos>({ x: 0, y: 0 });
|
||||
let pointerDismissDocument: Document | undefined;
|
||||
|
||||
const disarmPointerDismissal = () => {
|
||||
pointerDismissDocument?.removeEventListener('pointerdown', dismissTooltip, true);
|
||||
pointerDismissDocument = undefined;
|
||||
};
|
||||
|
||||
const dismissTooltip = () => {
|
||||
setShow(false);
|
||||
disarmPointerDismissal();
|
||||
};
|
||||
|
||||
const armPointerDismissal = (ownerDocument?: Document) => {
|
||||
disarmPointerDismissal();
|
||||
pointerDismissDocument =
|
||||
ownerDocument ?? (typeof document === 'undefined' ? undefined : document);
|
||||
pointerDismissDocument?.addEventListener('pointerdown', dismissTooltip, {
|
||||
capture: true,
|
||||
once: true,
|
||||
});
|
||||
};
|
||||
|
||||
const onMouseEnter = (e: MouseEvent) => {
|
||||
if (!supportsHoverTooltips()) {
|
||||
setShow(false);
|
||||
dismissTooltip();
|
||||
return;
|
||||
}
|
||||
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
||||
const currentTarget = e.currentTarget as HTMLElement;
|
||||
const rect = currentTarget.getBoundingClientRect();
|
||||
const hasPointerPosition = Number.isFinite(e.clientX) && Number.isFinite(e.clientY);
|
||||
setPos({
|
||||
x: hasPointerPosition ? e.clientX : rect.left + rect.width / 2,
|
||||
y: hasPointerPosition ? e.clientY : rect.top,
|
||||
});
|
||||
setShow(true);
|
||||
armPointerDismissal(currentTarget.ownerDocument);
|
||||
};
|
||||
|
||||
const onMouseLeave = () => setShow(false);
|
||||
const onMouseLeave = dismissTooltip;
|
||||
|
||||
onCleanup(disarmPointerDismissal);
|
||||
|
||||
return { show, setShow, pos, onMouseEnter, onMouseLeave } as const;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user