diff --git a/.changeset/smooth-terms-wait.md b/.changeset/smooth-terms-wait.md new file mode 100644 index 0000000..219af8f --- /dev/null +++ b/.changeset/smooth-terms-wait.md @@ -0,0 +1,5 @@ +--- +'@primer/behaviors': patch +--- + +Fix bug found when removing nodes in a focus zone with strict mode enabled diff --git a/src/__tests__/focus-zone.test.tsx b/src/__tests__/focus-zone.test.tsx index aa51c3a..5ed4363 100644 --- a/src/__tests__/focus-zone.test.tsx +++ b/src/__tests__/focus-zone.test.tsx @@ -751,3 +751,42 @@ it('Should ignore disabled elements after focus zone is enabled', async () => { controller.abort() }) + +it('Should handle elements being removed if strict', async () => { + const user = userEvent.setup() + const {container} = render( + <div> + <button tabIndex={0} id="outside"> + Outside Apple + </button> + <div id="focusZone"> + <button tabIndex={0}>Apple</button> + <button tabIndex={0}>Banana</button> + <button tabIndex={0}>Cantaloupe</button> + </div> + </div>, + ) + + const focusZoneContainer = container.querySelector<HTMLElement>('#focusZone')! + const [firstButton, secondButton, thirdButton] = focusZoneContainer.querySelectorAll('button') + const outsideButton = container.querySelector<HTMLElement>('#outside')! + const controller = focusZone(focusZoneContainer, {strict: true}) + + firstButton.focus() + await user.keyboard('{arrowdown}') + expect(document.activeElement).toEqual(secondButton) + + outsideButton.focus() + focusZoneContainer.removeChild(secondButton) + + // The mutation observer fires asynchronously + await nextTick() + + await user.tab() + expect(document.activeElement).toEqual(firstButton) + + await user.keyboard('{arrowdown}') + expect(document.activeElement).toEqual(thirdButton) + + controller.abort() +}) diff --git a/src/focus-zone.ts b/src/focus-zone.ts index 1ee75c0..ee6ad0a 100644 --- a/src/focus-zone.ts +++ b/src/focus-zone.ts @@ -524,7 +524,7 @@ export function focusZone(container: HTMLElement, settings?: FocusZoneSettings): for (const mutation of mutations) { for (const removedNode of mutation.removedNodes) { if (removedNode instanceof HTMLElement) { - endFocusManagement(...iterateFocusableElements(removedNode, iterateFocusableElementsOptions)) + endFocusManagement(...iterateFocusableElements(removedNode)) } } // If an element is hidden or disabled, remove it from the list of focusable elements