diff --git a/packages/editor/src/components/editor/floorplan-panel.tsx b/packages/editor/src/components/editor/floorplan-panel.tsx index 146a0051..ebd807d6 100644 --- a/packages/editor/src/components/editor/floorplan-panel.tsx +++ b/packages/editor/src/components/editor/floorplan-panel.tsx @@ -4519,7 +4519,17 @@ const FloorplanPolygonHandleLayer = memo(function FloorplanPolygonHandleLayer({ ) }) -export function FloorplanPanel() { +export function FloorplanPanel({ + /** + * Element to portal the compass button into. The 2D/3D navigation poses stay + * in sync (`navigationSyncPose`), so hosting the compass on the always-visible + * viewer-area container keeps it correct — needle and align-to-north alike — + * in 2d, 3d, and split modes, while this panel itself may be display:none. + */ + compassHost, +}: { + compassHost?: HTMLElement | null +}) { const viewportHostRef = useRef(null) const svgRef = useRef(null) const floorplanSceneRef = useRef(null) @@ -10135,12 +10145,21 @@ export function FloorplanPanel() { only action menu the floor plan mounts. */} - {(levelNode?.type === 'level' || hasAmbientBuildingLevel) && ( - - )} + {(levelNode?.type === 'level' || hasAmbientBuildingLevel) && + (compassHost ? ( + createPortal( + , + compassHost, + ) + ) : ( + + ))} {referenceScaleDraft && (
diff --git a/packages/editor/src/components/editor/index.tsx b/packages/editor/src/components/editor/index.tsx index 88e96497..965bcebe 100644 --- a/packages/editor/src/components/editor/index.tsx +++ b/packages/editor/src/components/editor/index.tsx @@ -816,6 +816,13 @@ const ViewerCanvas = memo(function ViewerCanvas({ ) const viewerAreaRef = useRef(null) + // State mirror of `viewerAreaRef` so the floorplan compass portal re-renders + // once the container exists (a plain ref mutation wouldn't trigger it). + const [viewerAreaEl, setViewerAreaEl] = useState(null) + const setViewerAreaNode = useCallback((el: HTMLDivElement | null) => { + viewerAreaRef.current = el + setViewerAreaEl(el) + }, []) const viewer3dRef = useRef(null) const isResizingFloorplan = useRef(false) @@ -861,7 +868,9 @@ const ViewerCanvas = memo(function ViewerCanvas({ return ( }> -
+ {/* `relative` so the floorplan compass (portaled here to stay visible in + 2d / 3d / split alike) can anchor to this container's bottom-left. */} +
{/* 2D floorplan — always mounted once shown, hidden via CSS to preserve state */}
- +
{viewMode === 'split' && (
{ + // One physical click can reach here twice: node clicks (`slab:click`, + // `item:click`, …) are synthesized on *pointerup* (`use-node-events`), + // while `grid:click` rides the browser's native *click* event from a + // canvas DOM listener (`use-grid-events`) that deliberately ignores + // stopPropagation — and this effect stays subscribed until React + // re-renders after `exitMoveMode`. Without this guard the second pass + // finds the fresh draft already deleted and takes the orphan re-create + // path below, minting a hidden ghost copy and replaying the SFX. + if (committed) return // Ignore a commit that fires before the cursor has moved into place — // it's the stray trailing click of whatever armed this move, not a // deliberate drop. Prevents preset re-arm from double-placing.