fix: portal local edit prompts outside canvas transforms
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-05-23 14:43:25 +08:00
parent 9ddae7423e
commit 852498b6bb

View File

@ -2057,8 +2057,12 @@ export function ImageNode({
// 文件输入的 ref // 文件输入的 ref
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const [localEditPromptLayerElement, setLocalEditPromptLayerElement] = const [localEditPromptPortalFrame, setLocalEditPromptPortalFrame] = useState<{
useState<HTMLDivElement | null>(null); left: number;
top: number;
width: number;
height: number;
} | null>(null);
const displayImageRef = useRef<HTMLImageElement>(null); const displayImageRef = useRef<HTMLImageElement>(null);
const compareFrameRef = useRef<HTMLDivElement>(null); const compareFrameRef = useRef<HTMLDivElement>(null);
const cropOverlayRef = useRef<HTMLDivElement>(null); const cropOverlayRef = useRef<HTMLDivElement>(null);
@ -2493,6 +2497,59 @@ export function ImageNode({
})), })),
[localEditPromptInlineOverlays, nodeContentScale] [localEditPromptInlineOverlays, nodeContentScale]
); );
useEffect(() => {
if (
!localEditMode ||
!data.src ||
localEditPromptViewportOverlays.length === 0 ||
typeof window === "undefined"
) {
setLocalEditPromptPortalFrame(null);
return;
}
let animationFrame = 0;
const updatePortalFrame = () => {
const rect = containerRef.current?.getBoundingClientRect();
if (!rect) {
return;
}
const nextFrame = {
left: rect.left,
top: rect.top,
width: rect.width,
height: rect.height,
};
setLocalEditPromptPortalFrame((currentFrame) => {
if (
currentFrame &&
Math.abs(currentFrame.left - nextFrame.left) < 0.5 &&
Math.abs(currentFrame.top - nextFrame.top) < 0.5 &&
Math.abs(currentFrame.width - nextFrame.width) < 0.5 &&
Math.abs(currentFrame.height - nextFrame.height) < 0.5
) {
return currentFrame;
}
return nextFrame;
});
};
const schedulePortalFrameUpdate = () => {
window.cancelAnimationFrame(animationFrame);
animationFrame = window.requestAnimationFrame(updatePortalFrame);
};
updatePortalFrame();
const intervalId = window.setInterval(updatePortalFrame, 250);
window.addEventListener("resize", schedulePortalFrameUpdate);
window.addEventListener("scroll", schedulePortalFrameUpdate, true);
return () => {
window.cancelAnimationFrame(animationFrame);
window.clearInterval(intervalId);
window.removeEventListener("resize", schedulePortalFrameUpdate);
window.removeEventListener("scroll", schedulePortalFrameUpdate, true);
};
}, [data.src, localEditMode, localEditPromptViewportOverlays.length]);
const currentPickerClickStyle = useMemo(() => { const currentPickerClickStyle = useMemo(() => {
if (!boxCutoutMetrics || !currentClick) return null; if (!boxCutoutMetrics || !currentClick) return null;
@ -8147,10 +8204,20 @@ export function ImageNode({
</div> </div>
); );
})} })}
{localEditPromptLayerElement {typeof document !== "undefined" && localEditPromptPortalFrame
? createPortal( ? createPortal(
localEditPromptViewportOverlays.map( <div
({ region, isEditing, style }) => ( className="pointer-events-none fixed z-[9998] overflow-visible"
data-local-edit-control="true"
style={{
left: localEditPromptPortalFrame.left,
top: localEditPromptPortalFrame.top,
width: localEditPromptPortalFrame.width,
height: localEditPromptPortalFrame.height,
}}
>
{localEditPromptViewportOverlays.map(
({ region, isEditing, style }) => (
<div <div
className="nodrag nopan nowheel pointer-events-auto absolute z-[170] flex flex-col items-stretch gap-1.5" className="nodrag nopan nowheel pointer-events-auto absolute z-[170] flex flex-col items-stretch gap-1.5"
data-canvas-no-drag="true" data-canvas-no-drag="true"
@ -8435,9 +8502,10 @@ export function ImageNode({
</div> </div>
) : null} ) : null}
</div> </div>
) )
), )}
localEditPromptLayerElement </div>,
document.body
) )
: null} : null}
{localEditDraftRectStyle ? ( {localEditDraftRectStyle ? (
@ -8829,10 +8897,6 @@ export function ImageNode({
</button> </button>
) : null} ) : null}
</div> </div>
<div
className="pointer-events-none absolute inset-0 z-[260] overflow-visible"
ref={setLocalEditPromptLayerElement}
/>
</div> </div>
<AgentExecutionSnapshotDialog <AgentExecutionSnapshotDialog
onOpenChange={setSnapshotDialogOpen} onOpenChange={setSnapshotDialogOpen}