import type { RefObject } from "react"; import { type DomEditSelection } from "./domEditing"; import type { GroupOverlayItem, OverlayRect } from "./domEditOverlayGeometry"; import type { BlockedMoveState, ResizeHandle } from "./domEditOverlayGestures"; import type { createDomEditOverlayGestureHandlers } from "./useDomEditOverlayGestures"; import { DomEditCropHandles } from "./DomEditCropHandles"; import { DomEditRotateHandle } from "./DomEditRotateHandle"; import { resolveRotatedResizeCursor } from "./domEditResizeLocal"; // Corner resize handles, Canva-style: one per corner, diagonal cursors. // Corners scale about the element center; the translate keeps the center // planted, so they need the manual-offset capability in addition to manual-size. const RESIZE_HANDLE_DEFS: Array<{ handle: ResizeHandle; cursor: string; x: "left" | "right"; y: "top" | "bottom"; }> = [ { handle: "nw", cursor: "nwse-resize", x: "left", y: "top" }, { handle: "ne", cursor: "nesw-resize", x: "right", y: "top" }, { handle: "sw", cursor: "nesw-resize", x: "left", y: "bottom" }, { handle: "se", cursor: "nwse-resize", x: "right", y: "bottom" }, ]; // Visible dot is 9px; the pointer target is a 16px invisible square centered // on the corner so click targets don't shrink with the smaller dot. const RESIZE_HANDLE_HIT_PX = 16; type CropInset = { top: number; right: number; bottom: number; left: number }; const NO_CROP_INSET: CropInset = { top: 0, right: 0, bottom: 0, left: 0 }; function resizeHandleStyle( def: (typeof RESIZE_HANDLE_DEFS)[number], overlayRect: { left: number; top: number; width: number; height: number }, cropInset?: CropInset, ): React.CSSProperties { const half = RESIZE_HANDLE_HIT_PX / 2; const inset = cropInset ?? NO_CROP_INSET; const style: React.CSSProperties = { cursor: def.cursor, touchAction: "none" }; // Position relative to the overlay container (not the selection box). // This ensures the dots render as siblings of the box border div — strictly // above it — rather than as children where the parent border can visually // overlap the dot circle at the corner. style.left = def.x === "left" ? overlayRect.left + inset.left - half : overlayRect.left + overlayRect.width - inset.right - half; style.top = def.y === "top" ? overlayRect.top + inset.top - half : overlayRect.top + overlayRect.height - inset.bottom - half; return style; } type GestureHandlers = ReturnType; interface DomEditGroupChromeProps { groupOverlayItems: GroupOverlayItem[]; groupBounds: OverlayRect; allowCanvasMovement: boolean; groupCanMove: boolean; gestures: GestureHandlers; onBoxMouseDown: (e: React.MouseEvent) => void; onBoxClick: (event: React.MouseEvent) => void; } // Multi-selection chrome: per-member outlines plus a single draggable bounding // box spanning the union of the members. export function DomEditGroupChrome({ groupOverlayItems, groupBounds, allowCanvasMovement, groupCanMove, gestures, onBoxMouseDown, onBoxClick, }: DomEditGroupChromeProps) { return ( <> {groupOverlayItems.map((item) => (