From 2d904db4bf7779dadbb723bd21868c70443bf948 Mon Sep 17 00:00:00 2001 From: Zacky Ma Date: Fri, 22 May 2026 15:44:16 -0700 Subject: [PATCH 1/3] =?UTF-8?q?remove=20tooltip=20id=20from=20target?= =?UTF-8?q?=E2=80=99s=20aria-describedby=20attribute=20when=20the=20toolti?= =?UTF-8?q?p=20is=20removed=20from=20DOM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-a2f01802-9f4b-483a-b2f1-b32511fd368c.json | 7 ++++++ .../src/tooltip/tooltip.spec.ts | 23 +++++++++++++++++++ .../web-components/src/tooltip/tooltip.ts | 8 ++++++- 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 change/@fluentui-web-components-a2f01802-9f4b-483a-b2f1-b32511fd368c.json diff --git a/change/@fluentui-web-components-a2f01802-9f4b-483a-b2f1-b32511fd368c.json b/change/@fluentui-web-components-a2f01802-9f4b-483a-b2f1-b32511fd368c.json new file mode 100644 index 00000000000000..4d522e7fb7dbca --- /dev/null +++ b/change/@fluentui-web-components-a2f01802-9f4b-483a-b2f1-b32511fd368c.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "remove tooltip id from target’s aria-describedby attribute when the tooltip is removed from DOM", + "packageName": "@fluentui/web-components", + "email": "machi@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/web-components/src/tooltip/tooltip.spec.ts b/packages/web-components/src/tooltip/tooltip.spec.ts index 48161f4e710344..8f6ca63bb49075 100644 --- a/packages/web-components/src/tooltip/tooltip.spec.ts +++ b/packages/web-components/src/tooltip/tooltip.spec.ts @@ -71,6 +71,29 @@ test.describe('Tooltip', () => { await expect(button).toHaveAttribute('aria-describedby', id); }); + test('should remove the tooltip id from `aria-describedby` attribute when tooltip is removed', async ({ + fastPage, + }) => { + const { element, page } = fastPage; + const button = page.locator('button'); + + await fastPage.setTemplate(/* html */ ` +
+ + <${tagName} anchor="target">This is a tooltip + <${tagName} anchor="target">This is another tooltip +
+ `); + + const id2 = await element.nth(1).evaluate((node: Tooltip) => node.id); + + await element.nth(0).evaluate(node => { + node.remove(); + }); + + await expect(button).toHaveAttribute('aria-describedby', id2); + }); + test('should not be visible by default', async ({ fastPage }) => { const { element } = fastPage; diff --git a/packages/web-components/src/tooltip/tooltip.ts b/packages/web-components/src/tooltip/tooltip.ts index 9ff69650d7f0d9..9fbce548176092 100644 --- a/packages/web-components/src/tooltip/tooltip.ts +++ b/packages/web-components/src/tooltip/tooltip.ts @@ -115,11 +115,17 @@ export class Tooltip extends FASTElement { } public disconnectedCallback(): void { - super.disconnectedCallback(); this.anchorElement?.removeEventListener('focus', this.focusAnchorHandler); this.anchorElement?.removeEventListener('blur', this.blurAnchorHandler); this.anchorElement?.removeEventListener('mouseenter', this.mouseenterAnchorHandler); this.anchorElement?.removeEventListener('mouseleave', this.mouseleaveAnchorHandler); + + if (this.anchorElement) { + const describedBy = this.anchorElement.getAttribute('aria-describedby')?.trim().split(' ') ?? []; + this.anchorElement.setAttribute('aria-describedby', describedBy.filter(id => id !== this.id).join(' ')); + } + + super.disconnectedCallback(); } /** From ad38082d1f30d78c4965329e6f87ffc3de0352bd Mon Sep 17 00:00:00 2001 From: Zacky Ma Date: Fri, 22 May 2026 15:50:41 -0700 Subject: [PATCH 2/3] refactor aria-describedby reset code --- packages/web-components/src/tooltip/tooltip.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/web-components/src/tooltip/tooltip.ts b/packages/web-components/src/tooltip/tooltip.ts index 9fbce548176092..6821483d3d1957 100644 --- a/packages/web-components/src/tooltip/tooltip.ts +++ b/packages/web-components/src/tooltip/tooltip.ts @@ -121,8 +121,16 @@ export class Tooltip extends FASTElement { this.anchorElement?.removeEventListener('mouseleave', this.mouseleaveAnchorHandler); if (this.anchorElement) { - const describedBy = this.anchorElement.getAttribute('aria-describedby')?.trim().split(' ') ?? []; - this.anchorElement.setAttribute('aria-describedby', describedBy.filter(id => id !== this.id).join(' ')); + const describedBy = this.anchorElement.getAttribute('aria-describedby') ?? ''; + const ids = describedBy + .trim() + .split(/\s+/) + .filter(id => id !== this.id); + if (ids.length) { + this.anchorElement.setAttribute('aria-describedby', ids.join(' ')); + } else { + this.anchorElement.removeAttribute('aria-describedby'); + } } super.disconnectedCallback(); From c147a7390e6c81b5de236db74ad0e3e28ba62093 Mon Sep 17 00:00:00 2001 From: Zacky Ma Date: Tue, 26 May 2026 14:17:45 -0700 Subject: [PATCH 3/3] more thorough test --- .../web-components/src/tooltip/tooltip.spec.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/web-components/src/tooltip/tooltip.spec.ts b/packages/web-components/src/tooltip/tooltip.spec.ts index 8f6ca63bb49075..f95b4b51ed0f8d 100644 --- a/packages/web-components/src/tooltip/tooltip.spec.ts +++ b/packages/web-components/src/tooltip/tooltip.spec.ts @@ -85,13 +85,29 @@ test.describe('Tooltip', () => { `); + const id1 = await element.nth(0).evaluate((node: Tooltip) => node.id); const id2 = await element.nth(1).evaluate((node: Tooltip) => node.id); + await expect(button).toHaveAttribute('aria-describedby', [id1, id2].join(' ')); + await element.nth(0).evaluate(node => { node.remove(); }); await expect(button).toHaveAttribute('aria-describedby', id2); + + await button.evaluate( + (node, [tagName, id]) => { + const tooltip = document.createElement(tagName); + tooltip.id = id; + tooltip.setAttribute('anchor', 'target'); + tooltip.textContent = 'New tooltip'; + node.after(tooltip); + }, + [tagName, id1], + ); + + await expect(button).toHaveAttribute('aria-describedby', [id2, id1].join(' ')); }); test('should not be visible by default', async ({ fastPage }) => {