Skip to content

Commit 5e23b31

Browse files
afc163zombieJ
andauthored
fix([email protected]): prevent scroll when modal container and sentinel get focused (#448)
* fix([email protected]): prevent scroll when modal container and sentinel get focused * chore: allow publish in branch 9.0.x * test: fix test case --------- Co-authored-by: 二货爱吃白萝卜 <[email protected]>
1 parent d54ec5e commit 5e23b31

File tree

5 files changed

+40
-18
lines changed

5 files changed

+40
-18
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"docs:deploy": "gh-pages -d .doc",
3434
"compile": "father build && lessc assets/index.less assets/index.css && lessc assets/bootstrap.less assets/bootstrap.css",
3535
"deploy": "npm run docs:build && npm run docs:deploy",
36-
"prepublishOnly": "npm run compile && np --yolo --no-publish",
36+
"prepublishOnly": "npm run compile && np --yolo --no-publish --branch=9.0.x",
3737
"lint": "eslint src/ --ext .ts,.tsx,.jsx,.js,.md",
3838
"lint:tsc": "tsc -p tsconfig.json --noEmit",
3939
"prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",

src/Dialog/Content/Panel.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@ const Panel = React.forwardRef<ContentRef, PanelProps>((props, ref) => {
4848

4949
React.useImperativeHandle(ref, () => ({
5050
focus: () => {
51-
sentinelStartRef.current?.focus();
51+
sentinelStartRef.current?.focus({ preventScroll: true });
5252
},
5353
changeActive: (next) => {
5454
const { activeElement } = document;
5555
if (next && activeElement === sentinelEndRef.current) {
56-
sentinelStartRef.current.focus();
56+
sentinelStartRef.current.focus({ preventScroll: true });
5757
} else if (!next && activeElement === sentinelStartRef.current) {
58-
sentinelEndRef.current.focus();
58+
sentinelEndRef.current.focus({ preventScroll: true });
5959
}
6060
},
6161
}));

src/Dialog/index.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,8 @@ export default function Dialog(props: IDialogPropTypes) {
129129
}
130130

131131
// keep focus inside dialog
132-
if (visible) {
133-
if (e.keyCode === KeyCode.TAB) {
134-
contentRef.current.changeActive(!e.shiftKey);
135-
}
132+
if (visible && e.keyCode === KeyCode.TAB) {
133+
contentRef.current.changeActive(!e.shiftKey);
136134
}
137135
}
138136

tests/index.spec.tsx

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
11
/* eslint-disable react/no-render-return-value, max-classes-per-file, func-names, no-console */
2-
import React, { cloneElement, useEffect } from 'react';
3-
import { act } from 'react-dom/test-utils';
4-
import { render } from '@testing-library/react';
2+
import { fireEvent, render } from '@testing-library/react';
53
import type { ReactWrapper } from 'enzyme';
64
import { mount } from 'enzyme';
5+
import { Provider } from 'rc-motion';
6+
import React, { cloneElement, useEffect } from 'react';
7+
import { act } from 'react-dom/test-utils';
78
import KeyCode from 'rc-util/lib/KeyCode';
89
import type { DialogProps } from '../src';
910
import Dialog from '../src';
1011

1112
describe('dialog', () => {
13+
async function runFakeTimer() {
14+
for (let i = 0; i < 100; i += 1) {
15+
await act(async () => {
16+
jest.advanceTimersByTime(100);
17+
await Promise.resolve();
18+
});
19+
}
20+
}
21+
1222
beforeEach(() => {
1323
jest.useFakeTimers();
1424
});
1525

1626
afterEach(() => {
27+
jest.clearAllTimers();
1728
jest.useRealTimers();
1829
});
1930

@@ -252,15 +263,17 @@ describe('dialog', () => {
252263
});
253264

254265
it('trap focus after shift-tabbing', () => {
255-
const wrapper = mount(<Dialog visible />, { attachTo: document.body });
256-
wrapper.find('.rc-dialog-wrap').simulate('keyDown', {
266+
render(<Dialog visible />);
267+
268+
document.querySelector<HTMLDivElement>('.rc-dialog > div').focus();
269+
270+
fireEvent.keyDown(document.querySelector('.rc-dialog-wrap'), {
257271
keyCode: KeyCode.TAB,
272+
key: 'Tab',
258273
shiftKey: true,
259274
});
260-
const sentinelEnd = document.querySelectorAll('.rc-dialog-content + div')[0];
275+
const sentinelEnd = document.querySelector('.rc-dialog-content + div');
261276
expect(document.activeElement).toBe(sentinelEnd);
262-
263-
wrapper.unmount();
264277
});
265278
});
266279

tests/setup.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
/* eslint-disable no-console */
2-
global.requestAnimationFrame = cb => setTimeout(cb, 0);
2+
global.requestAnimationFrame = (cb) => {
3+
return global.setTimeout(cb, 0);
4+
};
5+
global.cancelAnimationFrame = (cb) => {
6+
return global.clearTimeout(cb, 0);
7+
};
8+
window.requestAnimationFrame = (cb) => {
9+
return window.setTimeout(cb, 0);
10+
};
11+
window.cancelAnimationFrame = (cb) => {
12+
return window.clearTimeout(cb, 0);
13+
};
314

415
const originError = console.error;
516
const ignoreList = [
617
'Rendering components directly into document.body',
718
'Warning: unmountComponentAtNode():',
819
];
920
console.error = (...args) => {
10-
if (ignoreList.some(str => args[0].includes(str))) {
21+
if (ignoreList.some((str) => args[0].includes(str))) {
1122
return;
1223
}
1324

0 commit comments

Comments
 (0)