diff --git a/packages/editor/src/editor/Canvas.tsx b/packages/editor/src/editor/Canvas.tsx index 7fdc2cf..80305ed 100644 --- a/packages/editor/src/editor/Canvas.tsx +++ b/packages/editor/src/editor/Canvas.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import type { Screen, WidgetNode } from '@astralview/sdk'; import { assertNever } from '@astralview/sdk'; import { Button, Space, Typography } from 'antd'; @@ -55,6 +55,8 @@ export function Canvas(props: CanvasProps) { const ref = useRef(null); const [box, setBox] = useState<{ x1: number; y1: number; x2: number; y2: number } | null>(null); const [ctx, setCtx] = useState(null); + const ctxMenuRef = useRef(null); + const [ctxMenuSize, setCtxMenuSize] = useState<{ w: number; h: number }>({ w: 220, h: 320 }); const bounds = useMemo(() => ({ w: props.screen.width, h: props.screen.height }), [props.screen.width, props.screen.height]); @@ -66,22 +68,27 @@ export function Canvas(props: CanvasProps) { const selectionAllLocked = selection.length > 0 && selection.every((n) => n.locked); const selectionAllHidden = selection.length > 0 && selection.every((n) => n.hidden); + useLayoutEffect(() => { + if (!ctx) return; + const el = ctxMenuRef.current; + if (!el) return; + + // Measure after render so clamping matches the real menu size. + const rect = el.getBoundingClientRect(); + if (rect.width && rect.height) setCtxMenuSize({ w: rect.width, h: rect.height }); + }, [ctx]); + const ctxMenuPos = useMemo(() => { if (!ctx) return null; - // Rough clamp so the menu stays inside the viewport. - // (We don't measure actual size to keep this simple + stable.) - const w = 220; - const h = 320; - const vw = typeof window === 'undefined' ? 10_000 : window.innerWidth; const vh = typeof window === 'undefined' ? 10_000 : window.innerHeight; return { - x: Math.max(8, Math.min(ctx.clientX, vw - w - 8)), - y: Math.max(8, Math.min(ctx.clientY, vh - h - 8)), + x: Math.max(8, Math.min(ctx.clientX, vw - ctxMenuSize.w - 8)), + y: Math.max(8, Math.min(ctx.clientY, vh - ctxMenuSize.h - 8)), }; - }, [ctx]); + }, [ctx, ctxMenuSize.h, ctxMenuSize.w]); const clientToCanvas = useCallback((clientX: number, clientY: number) => { const el = ref.current; @@ -292,6 +299,7 @@ export function Canvas(props: CanvasProps) { > {ctx && ctxMenuPos && (