Skip to content
17 changes: 15 additions & 2 deletions src/Preview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -327,10 +327,23 @@ const Preview: React.FC<PreviewProps> = props => {
}
};

const escClosingRef = useRef(false);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

test 来一个?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

AI加的,看能接受不?


// >>>>> Effect: Keyboard
const onKeyDown = useEvent((event: KeyboardEvent) => {
if (open) {
const { keyCode } = event;
const { keyCode, key } = event;

if (keyCode === KeyCode.ESC || key === 'Escape') {
escClosingRef.current = true;
event.preventDefault();
onClose?.();
Comment on lines +339 to +346
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@copilot Review it again.


Promise.resolve().then(() => {
escClosingRef.current = false;
});
return;
Comment on lines 335 to +347
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the Preview component file and the specific lines mentioned
cd /repo && head -c 500000 src/Preview/index.tsx | wc -l

Repository: react-component/image

Length of output: 120


🏁 Script executed:

# Get the relevant code sections
sed -n '333,345p' src/Preview/index.tsx

Repository: react-component/image

Length of output: 433


🏁 Script executed:

# Check the other mentioned line ranges
sed -n '358,364p' src/Preview/index.tsx
sed -n '392,395p' src/Preview/index.tsx

Repository: react-component/image

Length of output: 373


🏁 Script executed:

# Search for window.addEventListener in the file to see capture phase usage
rg -n "addEventListener.*keydown" src/Preview/index.tsx

Repository: react-component/image

Length of output: 127


🏁 Script executed:

# Look for escClosingRef definition and usage
rg -n "escClosingRef" src/Preview/index.tsx

Repository: react-component/image

Length of output: 239


🏁 Script executed:

# Find Portal component and understand its onEsc handling
fd Portal src/ -type f

Repository: react-component/image

Length of output: 235


🏁 Script executed:

# Check if there's a Portal component and how it handles ESC
rg -n "onEsc" src/ -A 3

Repository: react-component/image

Length of output: 469


🏁 Script executed:

find . -name "*.ts" -o -name "*.tsx" | xargs grep -l "onEsc.*top"

Repository: react-component/image

Length of output: 247


🏁 Script executed:

# Look for Portal component definition
rg -n "interface PortalProps|type PortalProps" -A 10

Repository: react-component/image

Length of output: 962


🏁 Script executed:

# Check how Portal uses the onEsc callback and what 'top' means
rg -n "onEsc\(\{.*top" -B 5 -A 5

Repository: react-component/image

Length of output: 1260


🏁 Script executed:

# Check the test file to understand expected behavior
cat tests/preview.portal.test.tsx

Repository: react-component/image

Length of output: 2045


🏁 Script executed:

# Search for any nested Portal or child component ESC handling tests
rg -n "imageRender|actionsRender" tests/ -A 5 -B 5

Repository: react-component/image

Length of output: 2796


🏁 Script executed:

# Look for any tests with nested Dropdown or Portal scenarios
rg -n "Dropdown|nested" tests/

Repository: react-component/image

Length of output: 47


window capture 阶段的 ESC 处理会直接关闭 Preview,无法让内部子浮层优先响应。

window.addEventListener('keydown', onKeyDown, true) 在捕获阶段执行,早于任何子层 keydownPortal.onEsc 回调。这里直接调用 onClose(),设置 escClosingRef.current = true 后,虽然能防止 Portal.onEsc 重复调用 onClose,但无法让子浮层(如 imageRender/actionsRender 中的 Dropdown 或嵌套 Portal)优先处理 ESC。按 ESC 时 Preview 会先关闭,打破了 Portal 的 top 语义。此外只有 preventDefault() 而无 stopPropagation(),父层的原生 keydown 监听仍会收到事件。

建议:

  1. 把直接 onClose 的路径收窄为真正的 fallback(检查子层是否已处理)
  2. 补充"Preview 内嵌 Dropdown/子 Portal + ESC 优先权"的回归用例
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Preview/index.tsx` around lines 333 - 345, The capture-phase global
keydown listener (window.addEventListener(..., true)) in onKeyDown currently
forces Preview to close before child Portals/Dropdowns can handle ESC; change
the global listener to the bubble phase (remove the third-arg true / use false)
so descendant handlers get priority, and in onKeyDown first check
event.defaultPrevented and return early (do not call onClose) to treat
child-handled ESC as authoritative; when you do call onClose, keep escClosingRef
handling but also call event.stopPropagation() in addition to
event.preventDefault() to avoid parent/native handlers receiving the event;
update tests to cover "Preview with nested Dropdown/Portal ESC priority" to
ensure child handlers can prevent Preview closing.

}

if (showLeftOrRightSwitches) {
if (keyCode === KeyCode.LEFT) {
Expand Down Expand Up @@ -377,7 +390,7 @@ const Preview: React.FC<PreviewProps> = props => {
}, [open]);

const onEsc: PortalProps['onEsc'] = ({ top }) => {
if (top) {
if (top && !escClosingRef.current) {
onClose?.();
}
};
Expand Down
Loading