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
const fileInputRef = useRef<HTMLInputElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const [localEditPromptLayerElement, setLocalEditPromptLayerElement] =
useState<HTMLDivElement | null>(null);
const [localEditPromptPortalFrame, setLocalEditPromptPortalFrame] = useState<{
left: number;
top: number;
width: number;
height: number;
} | null>(null);
const displayImageRef = useRef<HTMLImageElement>(null);
const compareFrameRef = useRef<HTMLDivElement>(null);
const cropOverlayRef = useRef<HTMLDivElement>(null);
@ -2493,6 +2497,59 @@ export function ImageNode({
})),
[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(() => {
if (!boxCutoutMetrics || !currentClick) return null;
@ -8147,10 +8204,20 @@ export function ImageNode({
</div>
);
})}
{localEditPromptLayerElement
{typeof document !== "undefined" && localEditPromptPortalFrame
? createPortal(
localEditPromptViewportOverlays.map(
({ region, isEditing, style }) => (
<div
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
className="nodrag nopan nowheel pointer-events-auto absolute z-[170] flex flex-col items-stretch gap-1.5"
data-canvas-no-drag="true"
@ -8435,9 +8502,10 @@ export function ImageNode({
</div>
) : null}
</div>
)
),
localEditPromptLayerElement
)
)}
</div>,
document.body
)
: null}
{localEditDraftRectStyle ? (
@ -8829,10 +8897,6 @@ export function ImageNode({
</button>
) : null}
</div>
<div
className="pointer-events-none absolute inset-0 z-[260] overflow-visible"
ref={setLocalEditPromptLayerElement}
/>
</div>
<AgentExecutionSnapshotDialog
onOpenChange={setSnapshotDialogOpen}