diff --git a/packages/react-native-web/src/exports/Modal/ModalFocusTrap.js b/packages/react-native-web/src/exports/Modal/ModalFocusTrap.js index b58227e06..9c3df9342 100644 --- a/packages/react-native-web/src/exports/Modal/ModalFocusTrap.js +++ b/packages/react-native-web/src/exports/Modal/ModalFocusTrap.js @@ -123,6 +123,19 @@ const ModalFocusTrap = ({ active, children }: ModalFocusTrapProps) => { } }, [active]); + // To be fully compliant with WCAG we need to refocus element that triggered opening modal + // after closing it + useEffect(function() { + if (canUseDOM) { + const lastFocusedElementOutsideTrap = document.activeElement; + return function() { + if (lastFocusedElementOutsideTrap && document.contains(lastFocusedElementOutsideTrap)) { + UIManager.focus(lastFocusedElementOutsideTrap); + } + }; + } + }, []); + return ( <> diff --git a/packages/react-native-web/src/exports/Modal/__tests__/index.js b/packages/react-native-web/src/exports/Modal/__tests__/index.js index 605bd23d6..8aeceb065 100644 --- a/packages/react-native-web/src/exports/Modal/__tests__/index.js +++ b/packages/react-native-web/src/exports/Modal/__tests__/index.js @@ -280,6 +280,121 @@ describe('components/Modal', () => { expect(document.activeElement).toBe(insideElement); }); + test('focus is brought back to the element that triggered modal after closing', () => { + const { rerender } = render( + <> + + Outside + + + + Hello + + + + Outside + + + ); + + const modalTrigger = document.querySelector('[data-testid="modal-trigger"]'); + modalTrigger.focus(); + expect(document.activeElement).toBe(modalTrigger); + + rerender( + <> + + Outside + + + + Hello + + + + Outside + + + ); + + const insideElement = document.querySelector('[data-testid="inside"]'); + expect(document.activeElement).toBe(insideElement); + + rerender( + <> + + Outside + + + + Hello + + + + Outside + + + ); + + expect(document.activeElement).toBe(modalTrigger); + }); + + test('focus is brought back to the body when element that triggered modal is removed from the DOM after closing modal', () => { + const { rerender } = render( + <> + + Outside + + + + Hello + + + + Outside + + + ); + + const modalTrigger = document.querySelector('[data-testid="modal-trigger"]'); + modalTrigger.focus(); + expect(document.activeElement).toBe(modalTrigger); + + rerender( + <> + + Outside + + + + Hello + + + + Outside + + + ); + + const insideElement = document.querySelector('[data-testid="inside"]'); + expect(document.activeElement).toBe(insideElement); + + rerender( + <> + + Outside + + + + Hello + + + + ); + + expect(document.activeElement).toBe(document.body); + }); + test('focus is trapped when active', () => { render( <>