Skip to content

Commit

Permalink
[fix] Modal: refocus trigger-element after closing
Browse files Browse the repository at this point in the history
Close #1821
Fix #1822
  • Loading branch information
RafikiTiki authored and necolas committed Jan 29, 2021
1 parent e0cebea commit 58a8bbe
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 0 deletions.
13 changes: 13 additions & 0 deletions packages/react-native-web/src/exports/Modal/ModalFocusTrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<>
<FocusBracket />
Expand Down
115 changes: 115 additions & 0 deletions packages/react-native-web/src/exports/Modal/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<>
<a data-testid={'outside'} href={'#outside'}>
Outside
</a>
<Modal visible={false}>
<a data-testid={'inside'} href={'#hello'}>
Hello
</a>
</Modal>
<a data-testid={'modal-trigger'} href={'#modal-trigger'}>
Outside
</a>
</>
);

const modalTrigger = document.querySelector('[data-testid="modal-trigger"]');
modalTrigger.focus();
expect(document.activeElement).toBe(modalTrigger);

rerender(
<>
<a data-testid={'outside'} href={'#outside'}>
Outside
</a>
<Modal visible={true}>
<a data-testid={'inside'} href={'#hello'}>
Hello
</a>
</Modal>
<a data-testid={'modal-trigger'} href={'#modal-trigger'}>
Outside
</a>
</>
);

const insideElement = document.querySelector('[data-testid="inside"]');
expect(document.activeElement).toBe(insideElement);

rerender(
<>
<a data-testid={'outside'} href={'#outside'}>
Outside
</a>
<Modal visible={false}>
<a data-testid={'inside'} href={'#hello'}>
Hello
</a>
</Modal>
<a data-testid={'modal-trigger'} href={'#modal-trigger'}>
Outside
</a>
</>
);

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(
<>
<a data-testid={'outside'} href={'#outside'}>
Outside
</a>
<Modal visible={false}>
<a data-testid={'inside'} href={'#hello'}>
Hello
</a>
</Modal>
<a data-testid={'modal-trigger'} href={'#modal-trigger'}>
Outside
</a>
</>
);

const modalTrigger = document.querySelector('[data-testid="modal-trigger"]');
modalTrigger.focus();
expect(document.activeElement).toBe(modalTrigger);

rerender(
<>
<a data-testid={'outside'} href={'#outside'}>
Outside
</a>
<Modal visible={true}>
<a data-testid={'inside'} href={'#hello'}>
Hello
</a>
</Modal>
<a data-testid={'modal-trigger'} href={'#modal-trigger'}>
Outside
</a>
</>
);

const insideElement = document.querySelector('[data-testid="inside"]');
expect(document.activeElement).toBe(insideElement);

rerender(
<>
<a data-testid={'outside'} href={'#outside'}>
Outside
</a>
<Modal visible={false}>
<a data-testid={'inside'} href={'#hello'}>
Hello
</a>
</Modal>
</>
);

expect(document.activeElement).toBe(document.body);
});

test('focus is trapped when active', () => {
render(
<>
Expand Down

0 comments on commit 58a8bbe

Please sign in to comment.