Files
Genarrative/src/components/image-editor/useImageCanvasFloatingOptionDismiss.ts
T
lhk229 44ac7f1252
Project CI / Backend tests (pull_request) Failing after 12s
Project CI / Repository checks (pull_request) Failing after 12s
Project CI / Frontend tests (pull_request) Successful in 2m45s
Project CI / Native shell tests (pull_request) Successful in 13m37s
修正参数浮层 Escape 与全局快捷键叠加
浮层处理 Escape 后截断冒泡,避免画布全局快捷键顺带关闭整个生成面板。

补齐 Escape 不外泄、外部点击不抢焦点两条回归测试。

预设轨道锁定测试改为同时校验 CSS 规则本体,堵住只断言类名的空转。
2026-08-07 05:16:31 +00:00

85 lines
2.6 KiB
TypeScript

import { useEffect } from 'react';
type FloatingOptionBoundaryRef = {
readonly current: HTMLElement | null;
};
type UseImageCanvasFloatingOptionDismissOptions = {
isOpen: boolean;
boundaryRefs: Array<FloatingOptionBoundaryRef | null | undefined>;
onDismiss: () => void;
/** Escape 关闭后把焦点还给的触发按钮;不传则只关闭不移焦。 */
restoreFocusRef?: FloatingOptionBoundaryRef | null;
};
function isEventInsideBoundary(
target: EventTarget | null,
boundaryRefs: Array<FloatingOptionBoundaryRef | null | undefined>,
) {
if (!(target instanceof Node)) {
return false;
}
return boundaryRefs.some((boundaryRef) => {
const element = boundaryRef?.current;
return element ? element.contains(target) : false;
});
}
function isEventInsideFloatingMenu(target: EventTarget | null) {
return (
target instanceof Element &&
target.closest('.image-canvas-editor__portal-menu')
);
}
export function useImageCanvasFloatingOptionDismiss({
isOpen,
boundaryRefs,
onDismiss,
restoreFocusRef,
}: UseImageCanvasFloatingOptionDismissOptions) {
useEffect(() => {
if (!isOpen || typeof document === 'undefined') {
return undefined;
}
const handleClick = (event: MouseEvent) => {
// 中文注释:选项项点击后要保留浮层;父级面板其它区域点击才收起。
if (
isEventInsideBoundary(event.target, boundaryRefs) ||
isEventInsideFloatingMenu(event.target)
) {
return;
}
onDismiss();
};
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key !== 'Escape') {
return;
}
// 画布全局快捷键在 window 上监听同一个 Escape,且会把整个生成 dialog 关掉。
// 浮层打开时 Escape 归浮层所有:document 在冒泡路径上早于 window,这里截断,
// 否则用户只想收起参数浮层却会连整个面板一起丢失。
event.stopPropagation();
// 浮层 portal 到 body,关闭时焦点会掉到 body;只有焦点确实在浮层里才把它还给
// 触发按钮,避免抢走用户正在编辑的输入框。
const shouldRestoreFocus = isEventInsideFloatingMenu(
document.activeElement,
);
onDismiss();
if (shouldRestoreFocus) {
restoreFocusRef?.current?.focus();
}
};
document.addEventListener('click', handleClick);
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('click', handleClick);
document.removeEventListener('keydown', handleKeyDown);
};
}, [boundaryRefs, isOpen, onDismiss, restoreFocusRef]);
}