Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Dom/focus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ export function lockFocus(element: HTMLElement, id: string): VoidFunction {
// Just add event since it will de-duplicate
window.addEventListener('focusin', syncFocus);
window.addEventListener('keydown', onWindowKeyDown, true);
// If the element is not focused, focus it
// https://github.com/ant-design/ant-design/issues/56963
if (!hasFocus(element)) {
element.focus({ preventScroll: true });
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里会有点怪异,因为 syncFocus 就是用来聚焦的。如果这里先手工锁定了一下,那 snycFocus 还需要调用吗?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

另外要补一个 test case

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里一开始本来考虑的是在syncFocus内的else那个地方做一次focus的逻辑,不过考虑到如果写在那个函数内部的话,每次focusin事件触发都有可能执行到else导致事件重复,因此将其focus逻辑放在函数调用之前,让其更独立

syncFocus();
}

Expand Down
72 changes: 71 additions & 1 deletion tests/focus.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
import React, { useRef } from 'react';
import { render } from '@testing-library/react';
import { spyElementPrototype } from '../src/test/domHook';
import { getFocusNodeList, triggerFocus, useLockFocus } from '../src/Dom/focus';
import {
getFocusNodeList,
lockFocus,
triggerFocus,
useLockFocus,
} from '../src/Dom/focus';

describe('focus', () => {
beforeAll(() => {
Expand Down Expand Up @@ -97,6 +102,71 @@ describe('focus', () => {
});
});

// https://github.com/ant-design/ant-design/issues/56963
describe('lockFocus should focus element immediately', () => {
it('should call element.focus when element does not have focus', () => {
const wrapper = document.createElement('div');
wrapper.tabIndex = 0;
document.body.appendChild(wrapper);

const input = document.createElement('input');
wrapper.appendChild(input);

// Focus is on body, not on wrapper
expect(document.activeElement).toBe(document.body);

const focusSpy = jest.spyOn(wrapper, 'focus');
const unlock = lockFocus(wrapper, 'test-focus-immediate');

expect(focusSpy).toHaveBeenCalledWith({ preventScroll: true });

unlock();
focusSpy.mockRestore();
document.body.removeChild(wrapper);
});

it('should not call element.focus when element already has focus', () => {
const wrapper = document.createElement('div');
wrapper.tabIndex = 0;
document.body.appendChild(wrapper);

const input = document.createElement('input');
wrapper.appendChild(input);

// Focus inside wrapper first
input.focus();
expect(wrapper.contains(document.activeElement)).toBe(true);

const focusSpy = jest.spyOn(wrapper, 'focus');
const unlock = lockFocus(wrapper, 'test-focus-already');

expect(focusSpy).not.toHaveBeenCalled();

unlock();
focusSpy.mockRestore();
document.body.removeChild(wrapper);
});

it('should focus element and then sync focus to first focusable child', () => {
const wrapper = document.createElement('div');
document.body.appendChild(wrapper);

const input = document.createElement('input');
wrapper.appendChild(input);

// wrapper itself is not focusable (no tabIndex), focus is on body
expect(document.activeElement).toBe(document.body);

const unlock = lockFocus(wrapper, 'test-focus-child');

// syncFocus should move focus to the first focusable child
expect(document.activeElement).toBe(input);

unlock();
document.body.removeChild(wrapper);
});
});

it('ignoreElement should allow focus on ignored elements', () => {
let capturedIgnoreElement: ((ele: HTMLElement) => void) | null = null;

Expand Down