diff --git a/.changeset/every-balloons-open.md b/.changeset/every-balloons-open.md
new file mode 100644
index 0000000000..064ed11338
--- /dev/null
+++ b/.changeset/every-balloons-open.md
@@ -0,0 +1,5 @@
+---
+"@digdir/designsystemet-web": patch
+---
+
+**tooltip**: Fixes a bug where tooltip would reappear on mousedown outside trigger
diff --git a/packages/web/src/tooltip/tooltip.test.ts b/packages/web/src/tooltip/tooltip.test.ts
index 2350c90ac2..9f1c77c154 100644
--- a/packages/web/src/tooltip/tooltip.test.ts
+++ b/packages/web/src/tooltip/tooltip.test.ts
@@ -96,6 +96,62 @@ describe('tooltip behavior', () => {
expect(tip.hidePopover).toHaveBeenCalledTimes(1);
});
+ it('hides tooltip on blur when focus leaves to a non-tooltip element', async () => {
+ const tip = document.createElement('div');
+ tip.showPopover = vi.fn();
+ tip.hidePopover = vi.fn();
+ setTooltipElement(tip);
+
+ document.body.innerHTML = ``;
+
+ const button = document.querySelector('button') as HTMLButtonElement;
+ button.dispatchEvent(new FocusEvent('focus'));
+ expect(tip.showPopover).toHaveBeenCalledTimes(1);
+
+ button.dispatchEvent(new FocusEvent('blur'));
+ expect(tip.hidePopover).toHaveBeenCalledTimes(1);
+ expect(tip.showPopover).toHaveBeenCalledTimes(1); // Must not reshow on blur
+ });
+
+ it('does not reshow tooltip on blur (regression #4801)', async () => {
+ const tip = document.createElement('div');
+ tip.showPopover = vi.fn();
+ tip.hidePopover = vi.fn();
+ setTooltipElement(tip);
+
+ document.body.innerHTML = ``;
+
+ const button = document.querySelector('button') as HTMLButtonElement;
+ button.dispatchEvent(new FocusEvent('focus'));
+ expect(tip.showPopover).toHaveBeenCalledTimes(1);
+
+ // Reset internal source by hiding (mimics moving mouse away/closing)
+ setTooltipElement(tip);
+
+ // Blurring the still-focused button (e.g. clicking elsewhere) must not reshow
+ button.dispatchEvent(new FocusEvent('blur'));
+ expect(tip.showPopover).toHaveBeenCalledTimes(1);
+ });
+
+ it('does not reshow the previous tooltip when blurring to another tooltip trigger', async () => {
+ const tip = document.createElement('div');
+ tip.showPopover = vi.fn();
+ tip.hidePopover = vi.fn();
+ setTooltipElement(tip);
+
+ document.body.innerHTML = ``;
+
+ const a = document.querySelector('#a') as HTMLButtonElement;
+ const b = document.querySelector('#b') as HTMLButtonElement;
+ a.dispatchEvent(new FocusEvent('focus'));
+ expect(tip.showPopover).toHaveBeenCalledTimes(1);
+
+ // Focus moves to another tooltip trigger: blur must not hide nor reshow
+ a.dispatchEvent(new FocusEvent('blur', { relatedTarget: b }));
+ expect(tip.hidePopover).not.toHaveBeenCalled();
+ expect(tip.showPopover).toHaveBeenCalledTimes(1);
+ });
+
it('updates tooltip text and announces when data-tooltip changes programmatically', async () => {
const tip = document.createElement('div');
setTooltipElement(tip);
diff --git a/packages/web/src/tooltip/tooltip.ts b/packages/web/src/tooltip/tooltip.ts
index 3e03b5eaf3..df7d6184b9 100644
--- a/packages/web/src/tooltip/tooltip.ts
+++ b/packages/web/src/tooltip/tooltip.ts
@@ -69,16 +69,26 @@ const handleAriaAttributes = () => {
}
};
-const handleInterest = ({ type, target }: Event) => {
+const handleInterest = (event: Event) => {
+ const { type, target } = event;
clearTimeout(HOVER_TIMER);
if (target === TIP) return; // Allow tooltip to be hovered, following https://www.w3.org/TR/WCAG21/#content-on-hover-or-focus
+
+ const source = (target as Element)?.closest?.(SELECTOR_TOOLTIP);
+
+ // This prevents the tooltip from reappearing on mousedown/click
+ if (type === 'blur') {
+ const next = (event as FocusEvent).relatedTarget as Element | null;
+ if (source === SOURCE && !next?.closest?.(SELECTOR_TOOLTIP)) hideTooltip();
+ return;
+ }
+
if (type === 'mouseover' && !IS_HOVERING && !IS_IOS) {
HOVER_TIMER = setTimeout(handleInterest, DELAY_HOVER, { target }); // Delay mouse showing tooltip if not already shown
return;
}
- const source = (target as Element)?.closest?.(`[${ATTR_TOOLTIP}]`);
if (source === SOURCE) return; // No need to update
if (!source) return hideTooltip(); // If no new anchor, cleanup previous autoUpdate
if (!TIP) TIP = tag('div', { class: 'ds-tooltip' });