Skip to content

Commit 5bdc362

Browse files
feat: add options to set initial focus within modal (#476)
1 parent 23cccc6 commit 5bdc362

File tree

10 files changed

+328
-206
lines changed

10 files changed

+328
-206
lines changed

react-responsive-modal/__tests__/index.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { render, fireEvent, waitFor } from '@testing-library/react';
2+
import { fireEvent, render, waitFor } from '@testing-library/react';
33
import { Modal } from '../src';
44

55
describe('modal', () => {

react-responsive-modal/cypress/integration/modal.spec.ts

+10
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,14 @@ describe('simple modal', () => {
6565
cy.get('[data-testid=modal]').should('not.exist');
6666
cy.get('body').should('not.have.css', 'overflow', 'hidden');
6767
});
68+
69+
it('should focus first element within modal', () => {
70+
cy.get('button').eq(3).click();
71+
cy.get('[data-testid=modal] input').first().should('have.focus');
72+
});
73+
74+
it('should focus on modal root', () => {
75+
cy.get('button').eq(4).click();
76+
cy.get('[data-testid=modal]').should('have.focus');
77+
});
6878
});

react-responsive-modal/package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,15 @@
4747
"size-limit": [
4848
{
4949
"path": "dist/react-responsive-modal.cjs.production.min.js",
50-
"limit": "3.8 KB"
50+
"limit": "4.0 KB"
5151
},
5252
{
5353
"path": "dist/react-responsive-modal.esm.js",
54-
"limit": "3.8 KB"
54+
"limit": "4.0 KB"
5555
}
5656
],
5757
"dependencies": {
58+
"@bedrock-layout/use-forwarded-ref": "^1.1.4",
5859
"body-scroll-lock": "^3.1.5",
5960
"classnames": "^2.2.6"
6061
},

react-responsive-modal/src/FocusTrap.tsx

+18-5
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import {
88

99
interface FocusTrapProps {
1010
container?: React.RefObject<HTMLElement> | null;
11+
initialFocusRef?: React.RefObject<HTMLElement>;
1112
}
1213

13-
export const FocusTrap = ({ container }: FocusTrapProps) => {
14+
export const FocusTrap = ({ container, initialFocusRef }: FocusTrapProps) => {
1415
const refLastFocus = useRef<HTMLElement | null>();
1516
/**
1617
* Handle focus lock on the modal
@@ -27,8 +28,7 @@ export const FocusTrap = ({ container }: FocusTrapProps) => {
2728
}
2829
// On mount we focus on the first focusable element in the modal if there is one
2930
if (isBrowser && container?.current) {
30-
const allTabbingElements = getAllTabbingElements(container.current);
31-
if (allTabbingElements[0]) {
31+
const savePreviousFocus = () => {
3232
// First we save the last focused element
3333
// only if it's a focusable element
3434
if (
@@ -38,7 +38,20 @@ export const FocusTrap = ({ container }: FocusTrapProps) => {
3838
) {
3939
refLastFocus.current = document.activeElement as HTMLElement;
4040
}
41-
allTabbingElements[0].focus();
41+
};
42+
43+
if (initialFocusRef) {
44+
savePreviousFocus();
45+
// We need to schedule focusing on a next frame - this allows to focus on the modal root
46+
requestAnimationFrame(() => {
47+
initialFocusRef.current?.focus();
48+
});
49+
} else {
50+
const allTabbingElements = getAllTabbingElements(container.current);
51+
if (allTabbingElements[0]) {
52+
savePreviousFocus();
53+
allTabbingElements[0].focus();
54+
}
4255
}
4356
}
4457
return () => {
@@ -48,7 +61,7 @@ export const FocusTrap = ({ container }: FocusTrapProps) => {
4861
refLastFocus.current?.focus();
4962
}
5063
};
51-
}, [container]);
64+
}, [container, initialFocusRef]);
5265

5366
return null;
5467
};

0 commit comments

Comments
 (0)