diff --git a/src/Preview/index.tsx b/src/Preview/index.tsx index 94b76ad..d99a48f 100644 --- a/src/Preview/index.tsx +++ b/src/Preview/index.tsx @@ -196,7 +196,9 @@ const Preview: React.FC = props => { } = props; const imgRef = useRef(); - const wrapperRef = useRef(null); + const triggerRef = useRef(null); + const [wrapperEl, setWrapperEl] = useState(null); + const groupContext = useContext(PreviewGroupContext); const showLeftOrRightSwitches = groupContext && count > 1; const showOperationsProgress = groupContext && count >= 1; @@ -366,6 +368,10 @@ const Preview: React.FC = props => { const onVisibleChanged = (nextVisible: boolean) => { if (!nextVisible) { setLockScroll(false); + + // Restore focus to the trigger element after leave animation + triggerRef.current?.focus?.(); + triggerRef.current = null; } afterOpenChange?.(nextVisible); }; @@ -385,7 +391,13 @@ const Preview: React.FC = props => { }; // =========================== Focus ============================ - useLockFocus(open && portalRender, () => wrapperRef.current); + useEffect(() => { + if (open) { + triggerRef.current = document.activeElement as HTMLElement; + } + }, [open]); + + useLockFocus(open && !!wrapperEl, () => wrapperEl); // ========================== Render ========================== const bodyStyle: React.CSSProperties = { @@ -423,7 +435,7 @@ const Preview: React.FC = props => { return (
{ expect(document.querySelector('.rc-image-preview')).toBeFalsy(); }); + + it('Focus should be trapped inside preview after keyboard open and restored on close', () => { + const rectSpy = jest.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ + x: 0, y: 0, width: 100, height: 100, + top: 0, right: 100, bottom: 100, left: 0, + toJSON: () => undefined, + } as DOMRect); + + const { container } = render(focus trap); + const wrapper = container.querySelector('.rc-image') as HTMLElement; + + // Open preview via keyboard + wrapper.focus(); + expect(document.activeElement).toBe(wrapper); + + fireEvent.keyDown(wrapper, { key: 'Enter' }); + act(() => { + jest.runAllTimers(); + }); + + // Focus should be inside the preview + const preview = document.querySelector('.rc-image-preview') as HTMLElement; + expect(preview).toBeTruthy(); + expect(preview.contains(document.activeElement)).toBeTruthy(); + + // Focus should not escape when trying to focus outside + wrapper.focus(); + expect(preview.contains(document.activeElement)).toBeTruthy(); + + // Close preview via Escape + fireEvent.keyDown(window, { key: 'Escape' }); + act(() => { + jest.runAllTimers(); + }); + + // Focus should return to the trigger element + expect(document.activeElement).toBe(wrapper); + + rectSpy.mockRestore(); + }); });