Skip to content

Commit 17d963c

Browse files
committed
feat: add focusTrap prop to preview config
Add `focusTrap` option to `InternalPreviewConfig` (default `true`). When set to `false`, the preview will not trap focus via `useLockFocus`. This aligns with rc-dialog and rc-drawer which both support controlling focus trapping behavior. The main use case is rendering an always-open preview inline (e.g. for documentation semantic demos), where the global focus lock incorrectly prevents keyboard interaction with the rest of the page.
1 parent 8514cd1 commit 17d963c

2 files changed

Lines changed: 30 additions & 1 deletion

File tree

src/Preview/index.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ export interface InternalPreviewConfig {
8989
zIndex?: number;
9090
afterOpenChange?: (open: boolean) => void;
9191

92+
// Focus
93+
/** Whether to trap focus within the preview when open. Default is true. */
94+
focusTrap?: boolean;
95+
9296
// Operation
9397
movable?: boolean;
9498
icons?: OperationIcons;
@@ -193,6 +197,7 @@ const Preview: React.FC<PreviewProps> = props => {
193197
styles = {},
194198
mousePosition,
195199
zIndex,
200+
focusTrap = true,
196201
} = props;
197202

198203
const imgRef = useRef<HTMLImageElement>();
@@ -397,7 +402,7 @@ const Preview: React.FC<PreviewProps> = props => {
397402
}
398403
}, [open]);
399404

400-
useLockFocus(open && portalRender, () => wrapperRef.current);
405+
useLockFocus(focusTrap && open && portalRender, () => wrapperRef.current);
401406

402407
// ========================== Render ==========================
403408
const bodyStyle: React.CSSProperties = {

tests/preview.test.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,4 +1296,28 @@ describe('Preview', () => {
12961296

12971297
rectSpy.mockRestore();
12981298
});
1299+
1300+
it('Focus should not be trapped when focusTrap is false', () => {
1301+
const rectSpy = jest.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({
1302+
x: 0, y: 0, width: 100, height: 100,
1303+
top: 0, right: 100, bottom: 100, left: 0,
1304+
toJSON: () => undefined,
1305+
} as DOMRect);
1306+
1307+
const { container } = render(<Image src="src" alt="no trap" preview={{ open: true, focusTrap: false }} />);
1308+
1309+
act(() => {
1310+
jest.runAllTimers();
1311+
});
1312+
1313+
const preview = document.querySelector('.rc-image-preview') as HTMLElement;
1314+
expect(preview).toBeTruthy();
1315+
1316+
// Focus outside the preview should not be redirected back
1317+
const wrapper = container.querySelector('.rc-image') as HTMLElement;
1318+
wrapper.focus();
1319+
expect(document.activeElement).toBe(wrapper);
1320+
1321+
rectSpy.mockRestore();
1322+
});
12991323
});

0 commit comments

Comments
 (0)