diff --git a/src/capture-announcements.ts b/src/capture-announcements.ts index f6b2c0e..46095fb 100644 --- a/src/capture-announcements.ts +++ b/src/capture-announcements.ts @@ -179,7 +179,7 @@ export default function CaptureAnnouncements(options: Options): Restore { // prettier-ignore const cleanups: Restore[] = [ interceptMethod(Element.prototype, 'setAttribute', onSetAttribute), - interceptMethod(Element.prototype, 'removeAttribute', onRemoveAttribute), + interceptMethod(Element.prototype, 'removeChild', onRemoveChild), interceptMethod(Element.prototype, 'insertAdjacentElement', onInsertAdjacent), interceptMethod(Element.prototype, 'insertAdjacentHTML', onInsertAdjacent), interceptMethod(Element.prototype, 'insertAdjacentText', onInsertAdjacent), @@ -212,6 +212,25 @@ function onRemoveAttribute( } } +function onRemoveChild( + this: Element, + ...args: Parameters +) { + if (args[0] == null || !isElement(args[0])) return; + + const elementAndItsLiveRegionChildren = [ + args[0], + ...args[0].querySelectorAll(LIVE_REGION_QUERY), + ]; + + // Check whether removed element or any of its children were tracked + for (const element of elementAndItsLiveRegionChildren) { + if (liveRegions.has(element)) { + liveRegions.delete(element); + } + } +} + function trimWhiteSpace(text: string): string | null { const trimmed = text.trim().replace(WHITE_SPACE_REGEXP, ' '); return trimmed.length > 0 ? trimmed : null; diff --git a/test/capture-announcements.test.ts b/test/capture-announcements.test.ts index e181f3e..d1e8554 100644 --- a/test/capture-announcements.test.ts +++ b/test/capture-announcements.test.ts @@ -416,4 +416,35 @@ describe('element tracking', () => { expect(liveRegions.size).toBe(0); expect(liveRegions.has(element)).toBe(false); }); + + test('element is removed from tracked elements when unmounted', () => { + element.setAttribute('role', 'status'); + appendToRoot(element); + element.textContent = 'Hello world'; + + expect(liveRegions.size).toBe(1); + expect(liveRegions.has(element)).toBe(true); + + element.parentElement!.removeChild(element); + + expect(liveRegions.size).toBe(0); + expect(liveRegions.has(element)).toBe(false); + }); + + test('element is removed from tracked elements when ancestor is unmounted', () => { + const child = document.createElement('div'); + child.setAttribute('role', 'status'); + element.appendChild(child); + + appendToRoot(element); + child.textContent = 'Hello world'; + + expect(liveRegions.size).toBe(1); + expect(liveRegions.has(child)).toBe(true); + + element.parentElement!.removeChild(element); + + expect(liveRegions.size).toBe(0); + expect(liveRegions.has(element)).toBe(false); + }); });