Skip to content

Commit

Permalink
fix(antd@4.x): prevent scroll when modal container and sentinel get f…
Browse files Browse the repository at this point in the history
…ocused (#448)

* fix(antd@4.x): prevent scroll when modal container and sentinel get focused

* chore: allow publish in branch 9.0.x

* test: fix test case

---------

Co-authored-by: 二货爱吃白萝卜 <smith3816@gmail.com>
  • Loading branch information
afc163 and zombieJ authored Aug 1, 2024
1 parent d54ec5e commit 5e23b31
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 18 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"docs:deploy": "gh-pages -d .doc",
"compile": "father build && lessc assets/index.less assets/index.css && lessc assets/bootstrap.less assets/bootstrap.css",
"deploy": "npm run docs:build && npm run docs:deploy",
"prepublishOnly": "npm run compile && np --yolo --no-publish",
"prepublishOnly": "npm run compile && np --yolo --no-publish --branch=9.0.x",
"lint": "eslint src/ --ext .ts,.tsx,.jsx,.js,.md",
"lint:tsc": "tsc -p tsconfig.json --noEmit",
"prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
Expand Down
6 changes: 3 additions & 3 deletions src/Dialog/Content/Panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ const Panel = React.forwardRef<ContentRef, PanelProps>((props, ref) => {

React.useImperativeHandle(ref, () => ({
focus: () => {
sentinelStartRef.current?.focus();
sentinelStartRef.current?.focus({ preventScroll: true });
},
changeActive: (next) => {
const { activeElement } = document;
if (next && activeElement === sentinelEndRef.current) {
sentinelStartRef.current.focus();
sentinelStartRef.current.focus({ preventScroll: true });
} else if (!next && activeElement === sentinelStartRef.current) {
sentinelEndRef.current.focus();
sentinelEndRef.current.focus({ preventScroll: true });
}
},
}));
Expand Down
6 changes: 2 additions & 4 deletions src/Dialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,8 @@ export default function Dialog(props: IDialogPropTypes) {
}

// keep focus inside dialog
if (visible) {
if (e.keyCode === KeyCode.TAB) {
contentRef.current.changeActive(!e.shiftKey);
}
if (visible && e.keyCode === KeyCode.TAB) {
contentRef.current.changeActive(!e.shiftKey);
}
}

Expand Down
29 changes: 21 additions & 8 deletions tests/index.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
/* eslint-disable react/no-render-return-value, max-classes-per-file, func-names, no-console */
import React, { cloneElement, useEffect } from 'react';
import { act } from 'react-dom/test-utils';
import { render } from '@testing-library/react';
import { fireEvent, render } from '@testing-library/react';
import type { ReactWrapper } from 'enzyme';
import { mount } from 'enzyme';
import { Provider } from 'rc-motion';
import React, { cloneElement, useEffect } from 'react';
import { act } from 'react-dom/test-utils';
import KeyCode from 'rc-util/lib/KeyCode';
import type { DialogProps } from '../src';
import Dialog from '../src';

describe('dialog', () => {
async function runFakeTimer() {
for (let i = 0; i < 100; i += 1) {
await act(async () => {
jest.advanceTimersByTime(100);
await Promise.resolve();
});
}
}

beforeEach(() => {
jest.useFakeTimers();
});

afterEach(() => {
jest.clearAllTimers();
jest.useRealTimers();
});

Expand Down Expand Up @@ -252,15 +263,17 @@ describe('dialog', () => {
});

it('trap focus after shift-tabbing', () => {
const wrapper = mount(<Dialog visible />, { attachTo: document.body });
wrapper.find('.rc-dialog-wrap').simulate('keyDown', {
render(<Dialog visible />);

document.querySelector<HTMLDivElement>('.rc-dialog > div').focus();

fireEvent.keyDown(document.querySelector('.rc-dialog-wrap'), {
keyCode: KeyCode.TAB,
key: 'Tab',
shiftKey: true,
});
const sentinelEnd = document.querySelectorAll('.rc-dialog-content + div')[0];
const sentinelEnd = document.querySelector('.rc-dialog-content + div');
expect(document.activeElement).toBe(sentinelEnd);

wrapper.unmount();
});
});

Expand Down
15 changes: 13 additions & 2 deletions tests/setup.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
/* eslint-disable no-console */
global.requestAnimationFrame = cb => setTimeout(cb, 0);
global.requestAnimationFrame = (cb) => {
return global.setTimeout(cb, 0);
};
global.cancelAnimationFrame = (cb) => {
return global.clearTimeout(cb, 0);
};
window.requestAnimationFrame = (cb) => {
return window.setTimeout(cb, 0);
};
window.cancelAnimationFrame = (cb) => {
return window.clearTimeout(cb, 0);
};

const originError = console.error;
const ignoreList = [
'Rendering components directly into document.body',
'Warning: unmountComponentAtNode():',
];
console.error = (...args) => {
if (ignoreList.some(str => args[0].includes(str))) {
if (ignoreList.some((str) => args[0].includes(str))) {
return;
}

Expand Down

0 comments on commit 5e23b31

Please sign in to comment.