From 40c9dc14744f5d7c09dd0d69f6995bdaa737e423 Mon Sep 17 00:00:00 2001 From: Clayton Date: Fri, 13 Mar 2026 02:01:58 -0500 Subject: [PATCH 01/12] feat: improve Image preview accessibility --- src/Image.tsx | 16 ++++++++++++++++ src/Preview/Footer.tsx | 7 +++++-- src/Preview/PrevNext.tsx | 14 ++++++++++---- src/Preview/index.tsx | 21 +++++++++++++++++++++ tests/preview.test.tsx | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 6 deletions(-) diff --git a/src/Image.tsx b/src/Image.tsx index 4e37d86..fcadb5f 100644 --- a/src/Image.tsx +++ b/src/Image.tsx @@ -203,6 +203,18 @@ const ImageInternal: CompoundedComponent = props => { onClick?.(e); }; + // ======================= Keyboard Preview ===================== + const onPreviewKeyDown: React.KeyboardEventHandler = event => { + if (!canPreview) { + return; + } + + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + onPreview(event as any); + } + }; + // =========================== Render =========================== return ( <> @@ -212,6 +224,10 @@ const ImageInternal: CompoundedComponent = props => { [`${prefixCls}-error`]: status === 'error', })} onClick={canPreview ? onPreview : onClick} + role={canPreview ? 'button' : otherProps.role} + tabIndex={canPreview && otherProps.tabIndex == null ? 0 : otherProps.tabIndex} + aria-label={canPreview ? (alt || 'Preview image') : otherProps['aria-label']} + onKeyDown={onPreviewKeyDown} style={{ width, height, diff --git a/src/Preview/Footer.tsx b/src/Preview/Footer.tsx index d03cfe3..0623db8 100644 --- a/src/Preview/Footer.tsx +++ b/src/Preview/Footer.tsx @@ -95,15 +95,18 @@ export default function Footer(props: FooterProps) { const renderOperation = ({ type, disabled, onClick, icon }: RenderOperationParams) => { return ( -
{icon} -
+ ); }; diff --git a/src/Preview/PrevNext.tsx b/src/Preview/PrevNext.tsx index 049e721..9b82a51 100644 --- a/src/Preview/PrevNext.tsx +++ b/src/Preview/PrevNext.tsx @@ -23,22 +23,28 @@ export default function PrevNext(props: PrevNextProps) { return ( <> -
onActive(-1)} + disabled={current === 0} + aria-label="Previous image" > {prev ?? left} -
-
+
+ ); } diff --git a/src/Preview/index.tsx b/src/Preview/index.tsx index 05594fc..a4d48fd 100644 --- a/src/Preview/index.tsx +++ b/src/Preview/index.tsx @@ -195,6 +195,8 @@ const Preview: React.FC = props => { } = props; const imgRef = useRef(); + const wrapperRef = useRef(null); + const lastActiveRef = useRef(null); const groupContext = useContext(PreviewGroupContext); const showLeftOrRightSwitches = groupContext && count > 1; const showOperationsProgress = groupContext && count >= 1; @@ -239,6 +241,20 @@ const Preview: React.FC = props => { } }, [open]); + // =========================== Focus ============================ + useEffect(() => { + if (open) { + lastActiveRef.current = (document.activeElement as HTMLElement) || null; + + if (wrapperRef.current) { + wrapperRef.current.focus(); + } + } else if (!open && lastActiveRef.current) { + lastActiveRef.current.focus(); + lastActiveRef.current = null; + } + }, [open]); + // ========================== Image =========================== const onDoubleClick = (event: React.MouseEvent) => { if (open) { @@ -418,10 +434,15 @@ const Preview: React.FC = props => { return (
{/* Mask */}
{ expect(baseElement.querySelector('.rc-image-preview')).toHaveClass(customClassnames.popup.root); expect(baseElement.querySelector('.rc-image-preview')).toHaveStyle(customStyles.popup.root); }); + + it('Image wrapper should be keyboard focusable when preview enabled', () => { + const { container } = render(keyboard test); + + const wrapper = container.querySelector('.rc-image') as HTMLElement; + expect(wrapper).toHaveAttribute('role', 'button'); + expect(wrapper).toHaveAttribute('tabindex', '0'); + }); + + it('Pressing Enter on image wrapper should open preview', () => { + const { container } = render(keyboard open); + + const wrapper = container.querySelector('.rc-image') as HTMLElement; + wrapper.focus(); + fireEvent.keyDown(wrapper, { key: 'Enter' }); + + act(() => { + jest.runAllTimers(); + }); + + expect(document.querySelector('.rc-image-preview')).toBeTruthy(); + }); + + it('Preview dialog should have role dialog and receive focus', () => { + render(dialog a11y); + + const preview = document.querySelector('.rc-image-preview') as HTMLElement; + expect(preview).toHaveAttribute('role', 'dialog'); + expect(preview).toHaveAttribute('aria-modal', 'true'); + expect(preview).toHaveAttribute('aria-label', 'dialog a11y'); + expect(document.activeElement).toBe(preview); + }); }); From 9315b8b49ca0855fc32e68107e4c74c826a75f9d Mon Sep 17 00:00:00 2001 From: Clayton Date: Fri, 13 Mar 2026 02:39:51 -0500 Subject: [PATCH 02/12] fix: test --- tests/__snapshots__/basic.test.tsx.snap | 3 +++ tests/preview.test.tsx | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/__snapshots__/basic.test.tsx.snap b/tests/__snapshots__/basic.test.tsx.snap index 4c1c335..a5c3a12 100644 --- a/tests/__snapshots__/basic.test.tsx.snap +++ b/tests/__snapshots__/basic.test.tsx.snap @@ -2,8 +2,11 @@ exports[`Basic snapshot 1`] = `
{ expect(preview).toHaveAttribute('role', 'dialog'); expect(preview).toHaveAttribute('aria-modal', 'true'); expect(preview).toHaveAttribute('aria-label', 'dialog a11y'); - expect(document.activeElement).toBe(preview); }); }); From c9ddd2926fc06af6d5ab141653de9da80d92716c Mon Sep 17 00:00:00 2001 From: Clayton Date: Fri, 13 Mar 2026 06:09:05 -0500 Subject: [PATCH 03/12] fix: test --- src/Preview/Footer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Preview/Footer.tsx b/src/Preview/Footer.tsx index 0623db8..d487672 100644 --- a/src/Preview/Footer.tsx +++ b/src/Preview/Footer.tsx @@ -20,7 +20,7 @@ interface RenderOperationParams { icon: React.ReactNode; type: OperationType; disabled?: boolean; - onClick: (e: React.MouseEvent) => void; + onClick: React.MouseEventHandler; } export interface FooterProps extends Actions { From 46c52592c63a6bef4ea6fbf4cee69122a0073505 Mon Sep 17 00:00:00 2001 From: Clayton Date: Tue, 17 Mar 2026 01:47:13 -0500 Subject: [PATCH 04/12] fix: update --- assets/preview.less | 7 +++++++ src/Image.tsx | 24 +++++++++++++++++++++--- tests/preview.test.tsx | 14 ++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/assets/preview.less b/assets/preview.less index e60e906..0882b33 100644 --- a/assets/preview.less +++ b/assets/preview.less @@ -58,9 +58,12 @@ height: 40px; color: #fff; background: rgba(0, 0, 0, 0.3); + border: 0; + padding: 0; border-radius: 9999px; transform: translateY(-50%); cursor: pointer; + font: inherit; &-disabled { cursor: default; @@ -104,6 +107,10 @@ &-action { color: #fff; cursor: pointer; + border: 0; + padding: 0; + background: transparent; + font: inherit; &-disabled { cursor: default; diff --git a/src/Image.tsx b/src/Image.tsx index fcadb5f..80f6598 100644 --- a/src/Image.tsx +++ b/src/Image.tsx @@ -211,7 +211,20 @@ const ImageInternal: CompoundedComponent = props => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); - onPreview(event as any); + + const rect = (event.target as HTMLDivElement).getBoundingClientRect(); + const left = rect.x + rect.width / 2; + const top = rect.y + rect.height / 2; + + if (groupContext) { + groupContext.onPreview(imageId, src, left, top); + } else { + setMousePosition({ + x: left, + y: top, + }); + triggerPreviewOpen(true); + } } }; @@ -226,8 +239,13 @@ const ImageInternal: CompoundedComponent = props => { onClick={canPreview ? onPreview : onClick} role={canPreview ? 'button' : otherProps.role} tabIndex={canPreview && otherProps.tabIndex == null ? 0 : otherProps.tabIndex} - aria-label={canPreview ? (alt || 'Preview image') : otherProps['aria-label']} - onKeyDown={onPreviewKeyDown} + aria-label={ + canPreview ? (otherProps['aria-label'] ?? alt ?? 'Preview image') : otherProps['aria-label'] + } + onKeyDown={event => { + onPreviewKeyDown(event); + (otherProps as any).onKeyDown?.(event); + }} style={{ width, height, diff --git a/tests/preview.test.tsx b/tests/preview.test.tsx index e7ca5a3..161496d 100644 --- a/tests/preview.test.tsx +++ b/tests/preview.test.tsx @@ -1167,6 +1167,20 @@ describe('Preview', () => { expect(document.querySelector('.rc-image-preview')).toBeTruthy(); }); + it('Pressing Space on image wrapper should open preview', () => { + const { container } = render(keyboard open space); + + const wrapper = container.querySelector('.rc-image') as HTMLElement; + wrapper.focus(); + fireEvent.keyDown(wrapper, { key: ' ' }); + + act(() => { + jest.runAllTimers(); + }); + + expect(document.querySelector('.rc-image-preview')).toBeTruthy(); + }); + it('Preview dialog should have role dialog and receive focus', () => { render(dialog a11y); From 875e3cf5fdd6b41aefb4a2ad5489a7e860d4de0c Mon Sep 17 00:00:00 2001 From: Clayton Date: Wed, 18 Mar 2026 03:05:39 -0500 Subject: [PATCH 05/12] fix: test --- tests/preview.test.tsx | 21 +++++++++++++++++++++ tests/previewGroup.test.tsx | 19 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/tests/preview.test.tsx b/tests/preview.test.tsx index 161496d..61128b8 100644 --- a/tests/preview.test.tsx +++ b/tests/preview.test.tsx @@ -1189,4 +1189,25 @@ describe('Preview', () => { expect(preview).toHaveAttribute('aria-modal', 'true'); expect(preview).toHaveAttribute('aria-label', 'dialog a11y'); }); + + it('Preview open should render focusable wrapper', () => { + render(focus test); + + const preview = document.querySelector('.rc-image-preview') as HTMLElement; + expect(preview).toHaveAttribute('tabindex', '-1'); + }); + + it('Pressing Enter should not open preview when preview is disabled', () => { + const { container } = render(disabled preview); + + const wrapper = container.querySelector('.rc-image') as HTMLElement; + wrapper.focus(); + fireEvent.keyDown(wrapper, { key: 'Enter' }); + + act(() => { + jest.runAllTimers(); + }); + + expect(document.querySelector('.rc-image-preview')).toBeFalsy(); + }); }); diff --git a/tests/previewGroup.test.tsx b/tests/previewGroup.test.tsx index 0c847ed..178950d 100644 --- a/tests/previewGroup.test.tsx +++ b/tests/previewGroup.test.tsx @@ -108,6 +108,25 @@ describe('PreviewGroup', () => { expect(document.querySelector('.rc-image-preview')).toBeFalsy(); }); + it('Keyboard Enter should open preview from group image', () => { + const { container } = render( + + first + second + , + ); + + const first = container.querySelector('.rc-image') as HTMLElement; + first.focus(); + fireEvent.keyDown(first, { key: 'Enter' }); + + act(() => { + jest.runAllTimers(); + }); + + expect(document.querySelector('.rc-image-preview')).toBeTruthy(); + }); + it('Preview with Custom Preview Property', () => { const { container } = render( Date: Wed, 18 Mar 2026 08:19:43 -0500 Subject: [PATCH 06/12] fix: codecov --- src/Preview/index.tsx | 30 ++++++++++++++++-------------- tests/preview.test.tsx | 13 +++++++++++++ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/Preview/index.tsx b/src/Preview/index.tsx index a4d48fd..52d4812 100644 --- a/src/Preview/index.tsx +++ b/src/Preview/index.tsx @@ -241,20 +241,6 @@ const Preview: React.FC = props => { } }, [open]); - // =========================== Focus ============================ - useEffect(() => { - if (open) { - lastActiveRef.current = (document.activeElement as HTMLElement) || null; - - if (wrapperRef.current) { - wrapperRef.current.focus(); - } - } else if (!open && lastActiveRef.current) { - lastActiveRef.current.focus(); - lastActiveRef.current = null; - } - }, [open]); - // ========================== Image =========================== const onDoubleClick = (event: React.MouseEvent) => { if (open) { @@ -398,6 +384,22 @@ const Preview: React.FC = props => { } }; + // =========================== Focus ============================ + useEffect(() => { + if (open) { + lastActiveRef.current = (document.activeElement as HTMLElement) || null; + + // When `open` is initially true, the portal content is rendered in a later effect. + // Depend on `portalRender` so we can focus once the wrapper is actually mounted. + if (wrapperRef.current && portalRender) { + wrapperRef.current.focus(); + } + } else if (!open && lastActiveRef.current) { + lastActiveRef.current.focus(); + lastActiveRef.current = null; + } + }, [open, portalRender]); + // ========================== Render ========================== const bodyStyle: React.CSSProperties = { ...styles.body, diff --git a/tests/preview.test.tsx b/tests/preview.test.tsx index 61128b8..0b8652b 100644 --- a/tests/preview.test.tsx +++ b/tests/preview.test.tsx @@ -1190,6 +1190,19 @@ describe('Preview', () => { expect(preview).toHaveAttribute('aria-label', 'dialog a11y'); }); + it('Preview should focus wrapper after portal renders', () => { + const focusSpy = jest.spyOn(HTMLElement.prototype, 'focus'); + + render(focus portal); + + act(() => { + jest.runAllTimers(); + }); + + expect(focusSpy).toHaveBeenCalled(); + focusSpy.mockRestore(); + }); + it('Preview open should render focusable wrapper', () => { render(focus test); From 18ee0414c534916031a6027c5773a0fe99844fae Mon Sep 17 00:00:00 2001 From: Clayton Date: Mon, 23 Mar 2026 01:27:27 -0500 Subject: [PATCH 07/12] fix: update --- src/Image.tsx | 2 +- src/Preview/PrevNext.tsx | 2 -- src/Preview/index.tsx | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Image.tsx b/src/Image.tsx index 80f6598..d96e571 100644 --- a/src/Image.tsx +++ b/src/Image.tsx @@ -240,7 +240,7 @@ const ImageInternal: CompoundedComponent = props => { role={canPreview ? 'button' : otherProps.role} tabIndex={canPreview && otherProps.tabIndex == null ? 0 : otherProps.tabIndex} aria-label={ - canPreview ? (otherProps['aria-label'] ?? alt ?? 'Preview image') : otherProps['aria-label'] + canPreview ? (otherProps['aria-label'] ?? alt) : otherProps['aria-label'] } onKeyDown={event => { onPreviewKeyDown(event); diff --git a/src/Preview/PrevNext.tsx b/src/Preview/PrevNext.tsx index 9b82a51..6f09ae9 100644 --- a/src/Preview/PrevNext.tsx +++ b/src/Preview/PrevNext.tsx @@ -30,7 +30,6 @@ export default function PrevNext(props: PrevNextProps) { })} onClick={() => onActive(-1)} disabled={current === 0} - aria-label="Previous image" > {prev ?? left} @@ -41,7 +40,6 @@ export default function PrevNext(props: PrevNextProps) { })} onClick={() => onActive(1)} disabled={current === count - 1} - aria-label="Next image" > {next ?? right} diff --git a/src/Preview/index.tsx b/src/Preview/index.tsx index 52d4812..dae1432 100644 --- a/src/Preview/index.tsx +++ b/src/Preview/index.tsx @@ -443,7 +443,7 @@ const Preview: React.FC = props => { style={mergedStyle} role="dialog" aria-modal="true" - aria-label={alt || 'Image preview'} + aria-label={alt} tabIndex={-1} > {/* Mask */} From 0c341b3fc58a9585695300c297c54a258b7f46be Mon Sep 17 00:00:00 2001 From: Clayton Date: Mon, 23 Mar 2026 01:33:53 -0500 Subject: [PATCH 08/12] fix: update --- src/Preview/index.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Preview/index.tsx b/src/Preview/index.tsx index dae1432..f71b040 100644 --- a/src/Preview/index.tsx +++ b/src/Preview/index.tsx @@ -388,16 +388,16 @@ const Preview: React.FC = props => { useEffect(() => { if (open) { lastActiveRef.current = (document.activeElement as HTMLElement) || null; - - // When `open` is initially true, the portal content is rendered in a later effect. - // Depend on `portalRender` so we can focus once the wrapper is actually mounted. - if (wrapperRef.current && portalRender) { - wrapperRef.current.focus(); - } - } else if (!open && lastActiveRef.current) { + } else if (lastActiveRef.current) { lastActiveRef.current.focus(); lastActiveRef.current = null; } + }, [open]); + + useEffect(() => { + if (open && portalRender && wrapperRef.current) { + wrapperRef.current.focus(); + } }, [open, portalRender]); // ========================== Render ========================== From ec69621aece753a091659054fffd2c9f433f6e4f Mon Sep 17 00:00:00 2001 From: Clayton Date: Mon, 23 Mar 2026 01:47:34 -0500 Subject: [PATCH 09/12] fix: test --- tests/__snapshots__/basic.test.tsx.snap | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/__snapshots__/basic.test.tsx.snap b/tests/__snapshots__/basic.test.tsx.snap index a5c3a12..5a7d194 100644 --- a/tests/__snapshots__/basic.test.tsx.snap +++ b/tests/__snapshots__/basic.test.tsx.snap @@ -2,7 +2,6 @@ exports[`Basic snapshot 1`] = `
Date: Mon, 23 Mar 2026 17:31:21 +0800 Subject: [PATCH 10/12] chore: checkpoint local changes --- package.json | 2 +- src/Image.tsx | 15 +++++++-------- src/Preview/PrevNext.tsx | 12 +++++++----- src/Preview/index.tsx | 16 ++-------------- 4 files changed, 17 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 0efb43a..5feb3be 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "dependencies": { "@rc-component/motion": "^1.0.0", "@rc-component/portal": "^2.1.2", - "@rc-component/util": "^1.3.0", + "@rc-component/util": "^1.10.0", "clsx": "^2.1.1" }, "devDependencies": { diff --git a/src/Image.tsx b/src/Image.tsx index d96e571..22c37a0 100644 --- a/src/Image.tsx +++ b/src/Image.tsx @@ -44,7 +44,7 @@ export interface PreviewConfig extends Omit, 'placeholder' | 'onClick'> { + extends Omit, 'placeholder' | 'onClick' | 'onKeyDown'> { // Misc prefixCls?: string; previewPrefixCls?: string; @@ -73,6 +73,7 @@ export interface ImageProps // Events onClick?: (e: React.MouseEvent) => void; onError?: (e: React.SyntheticEvent) => void; + onKeyDown?: (e: React.KeyboardEvent) => void; } interface CompoundedComponent

extends React.FC

{ @@ -108,6 +109,7 @@ const ImageInternal: CompoundedComponent = props => { // Events onClick, onError, + onKeyDown, ...otherProps } = props; @@ -205,6 +207,8 @@ const ImageInternal: CompoundedComponent = props => { // ======================= Keyboard Preview ===================== const onPreviewKeyDown: React.KeyboardEventHandler = event => { + onKeyDown?.(event); + if (!canPreview) { return; } @@ -239,13 +243,8 @@ const ImageInternal: CompoundedComponent = props => { onClick={canPreview ? onPreview : onClick} role={canPreview ? 'button' : otherProps.role} tabIndex={canPreview && otherProps.tabIndex == null ? 0 : otherProps.tabIndex} - aria-label={ - canPreview ? (otherProps['aria-label'] ?? alt) : otherProps['aria-label'] - } - onKeyDown={event => { - onPreviewKeyDown(event); - (otherProps as any).onKeyDown?.(event); - }} + aria-label={canPreview ? otherProps['aria-label'] ?? alt : otherProps['aria-label']} + onKeyDown={onPreviewKeyDown} style={{ width, height, diff --git a/src/Preview/PrevNext.tsx b/src/Preview/PrevNext.tsx index 6f09ae9..338f782 100644 --- a/src/Preview/PrevNext.tsx +++ b/src/Preview/PrevNext.tsx @@ -21,25 +21,27 @@ export default function PrevNext(props: PrevNextProps) { const switchCls = `${prefixCls}-switch`; + const prevDisabled = current === 0; + const nextDisabled = current === count - 1; + return ( <> diff --git a/src/Preview/index.tsx b/src/Preview/index.tsx index f71b040..3ccc80f 100644 --- a/src/Preview/index.tsx +++ b/src/Preview/index.tsx @@ -1,6 +1,7 @@ import CSSMotion from '@rc-component/motion'; import Portal, { type PortalProps } from '@rc-component/portal'; import { useEvent } from '@rc-component/util'; +import { useLockFocus } from '@rc-component/util/lib/Dom/focus'; import useLayoutEffect from '@rc-component/util/lib/hooks/useLayoutEffect'; import KeyCode from '@rc-component/util/lib/KeyCode'; import { clsx } from 'clsx'; @@ -385,20 +386,7 @@ const Preview: React.FC = props => { }; // =========================== Focus ============================ - useEffect(() => { - if (open) { - lastActiveRef.current = (document.activeElement as HTMLElement) || null; - } else if (lastActiveRef.current) { - lastActiveRef.current.focus(); - lastActiveRef.current = null; - } - }, [open]); - - useEffect(() => { - if (open && portalRender && wrapperRef.current) { - wrapperRef.current.focus(); - } - }, [open, portalRender]); + useLockFocus(open && portalRender, () => wrapperRef.current); // ========================== Render ========================== const bodyStyle: React.CSSProperties = { From 1a4f463378d99627bedc1064c5a9ca1aea0c5797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 23 Mar 2026 17:47:13 +0800 Subject: [PATCH 11/12] test: fix preview focus assertion in jsdom --- tests/preview.test.tsx | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/tests/preview.test.tsx b/tests/preview.test.tsx index 0b8652b..a2878f2 100644 --- a/tests/preview.test.tsx +++ b/tests/preview.test.tsx @@ -5,10 +5,10 @@ import RotateLeftOutlined from '@ant-design/icons/RotateLeftOutlined'; import RotateRightOutlined from '@ant-design/icons/RotateRightOutlined'; import ZoomInOutlined from '@ant-design/icons/ZoomInOutlined'; import ZoomOutOutlined from '@ant-design/icons/ZoomOutOutlined'; +import Dialog from '@rc-component/dialog'; import { spyElementPrototypes } from '@rc-component/util/lib/test/domHook'; import { act, createEvent, fireEvent, render } from '@testing-library/react'; import React from 'react'; -import Dialog from '@rc-component/dialog'; jest.mock('../src/Preview', () => { const MockPreview = (props: any) => { @@ -1190,8 +1190,18 @@ describe('Preview', () => { expect(preview).toHaveAttribute('aria-label', 'dialog a11y'); }); - it('Preview should focus wrapper after portal renders', () => { - const focusSpy = jest.spyOn(HTMLElement.prototype, 'focus'); + it('Preview wrapper should be focusable after portal renders', () => { + 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); render(focus portal); @@ -1199,8 +1209,11 @@ describe('Preview', () => { jest.runAllTimers(); }); - expect(focusSpy).toHaveBeenCalled(); - focusSpy.mockRestore(); + const preview = document.querySelector('.rc-image-preview') as HTMLElement; + + expect(preview.contains(document.activeElement)).toBeTruthy(); + + rectSpy.mockRestore(); }); it('Preview open should render focusable wrapper', () => { From a3165980196ef5b138e02c76c68ff72eea4dcada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 23 Mar 2026 17:53:34 +0800 Subject: [PATCH 12/12] chore: remove unused preview focus ref --- src/Preview/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Preview/index.tsx b/src/Preview/index.tsx index 3ccc80f..2030504 100644 --- a/src/Preview/index.tsx +++ b/src/Preview/index.tsx @@ -197,7 +197,6 @@ const Preview: React.FC = props => { const imgRef = useRef(); const wrapperRef = useRef(null); - const lastActiveRef = useRef(null); const groupContext = useContext(PreviewGroupContext); const showLeftOrRightSwitches = groupContext && count > 1; const showOperationsProgress = groupContext && count >= 1;