From 2d904db4bf7779dadbb723bd21868c70443bf948 Mon Sep 17 00:00:00 2001 From: Zacky Ma Date: Fri, 22 May 2026 15:44:16 -0700 Subject: [PATCH 1/2] =?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 0000000000000..4d522e7fb7dbc --- /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 48161f4e71034..8f6ca63bb4907 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 9ff69650d7f0d..9fbce54817609 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/2] 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 9fbce54817609..6821483d3d195 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();