Skip to content

Commit c441ad1

Browse files
committed
feat: support useFocusLock
1 parent 7b64207 commit c441ad1

File tree

3 files changed

+51
-12
lines changed

3 files changed

+51
-12
lines changed

docs/examples/focus.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
import React, { useRef } from 'react';
22
import {} from '../../src';
3-
import { lockFocus } from '../../src/Dom/focus';
3+
import { useLockFocus } from '../../src/Dom/focus';
44
import './focus.css';
55

66
export default function FocusDemo() {
77
const containerRef = useRef<HTMLDivElement>(null);
8-
const [locking, setLocking] = React.useState(false);
8+
const [locking, setLocking] = React.useState(true);
99

10-
React.useEffect(() => {
11-
if (locking) {
12-
return lockFocus(containerRef.current!);
13-
}
14-
}, [locking]);
10+
useLockFocus(locking, () => containerRef.current);
1511

1612
return (
1713
<div style={{ padding: 32 }} className="focus-demo">

src/Dom/focus.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,13 @@ export function lockFocus(element: HTMLElement): VoidFunction {
170170
};
171171
}
172172

173-
export function useFocusLock(element: HTMLElement | null) {
173+
export function useLockFocus(
174+
lock: boolean,
175+
getElement: () => HTMLElement | null,
176+
) {
174177
useEffect(() => {
175-
if (element) {
176-
return lockFocus(element);
178+
if (lock) {
179+
return lockFocus(getElement());
177180
}
178-
}, [element]);
181+
}, [lock]);
179182
}
Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/* eslint-disable class-methods-use-this */
2+
import React, { JSX, useRef } from 'react';
3+
import { render, cleanup } from '@testing-library/react';
24
import { spyElementPrototype } from '../src/test/domHook';
3-
import { getFocusNodeList, triggerFocus } from '../src/Dom/focus';
5+
import { getFocusNodeList, triggerFocus, useLockFocus } from '../src/Dom/focus';
46

57
describe('focus', () => {
68
beforeAll(() => {
@@ -56,4 +58,42 @@ describe('focus', () => {
5658
focusSpy.mockRestore();
5759
setSelectionRangeSpy.mockRestore();
5860
});
61+
62+
describe('useLockFocus', () => {
63+
const TestComponent: React.FC<{ lock: boolean }> = ({ lock }) => {
64+
const elementRef = useRef<HTMLDivElement>(null);
65+
useLockFocus(lock, () => elementRef.current);
66+
67+
return (
68+
<>
69+
<button data-testid="outer-button">Outer</button>
70+
<div ref={elementRef} data-testid="focus-container" tabIndex={0}>
71+
<input key="input1" data-testid="input1" />
72+
<button key="button1" data-testid="button1">
73+
Button
74+
</button>
75+
</div>
76+
</>
77+
);
78+
};
79+
80+
it('should restore focus to range when focusing other elements', () => {
81+
const { getByTestId } = render(<TestComponent lock={true} />);
82+
83+
const focusContainer = getByTestId('focus-container');
84+
const input1 = getByTestId('input1') as HTMLInputElement;
85+
86+
// Should focus to first focusable element after lock
87+
expect(document.activeElement).toBe(focusContainer);
88+
89+
// Focus inside container first
90+
input1.focus();
91+
expect(document.activeElement).toBe(input1);
92+
93+
// Focus outer button
94+
const outerButton = getByTestId('outer-button') as HTMLButtonElement;
95+
outerButton.focus();
96+
expect(document.activeElement).toBe(input1);
97+
});
98+
});
5999
});

0 commit comments

Comments
 (0)