@ahrowe/ui 0.31.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/common/actionButtons/actionButtons.mjs +1 -1
- package/dist/esm/common/actionButtons/actionButtons.mjs.map +1 -1
- package/dist/esm/common/alert/alert.mjs +1 -1
- package/dist/esm/common/alert/alert.mjs.map +1 -1
- package/dist/esm/common/alert/alert.types.mjs.map +1 -1
- package/dist/esm/common/breadcrumb/breadcrumb.mjs +1 -1
- package/dist/esm/common/breadcrumb/breadcrumb.mjs.map +1 -1
- package/dist/esm/common/carousel/carousel.mjs +1 -1
- package/dist/esm/common/carousel/carousel.mjs.map +1 -1
- package/dist/esm/common/chip/chip.mjs +1 -1
- package/dist/esm/common/chip/chip.mjs.map +1 -1
- package/dist/esm/common/chip/chip.types.mjs.map +1 -1
- package/dist/esm/common/colorPicker/colorPicker.mjs +1 -1
- package/dist/esm/common/colorPicker/colorPicker.mjs.map +1 -1
- package/dist/esm/common/configProvider/useLabels.mjs +2 -0
- package/dist/esm/common/configProvider/useLabels.mjs.map +1 -0
- package/dist/esm/common/confirmModal/confirmModal.mjs +1 -1
- package/dist/esm/common/confirmModal/confirmModal.mjs.map +1 -1
- package/dist/esm/common/datePicker/components/datePickerMonth/datePickerMonth.mjs.map +1 -1
- package/dist/esm/common/datePicker/datePicker.mjs.map +1 -1
- package/dist/esm/common/emptyState/emptyState.mjs +1 -1
- package/dist/esm/common/emptyState/emptyState.mjs.map +1 -1
- package/dist/esm/common/input/input.mjs +1 -1
- package/dist/esm/common/input/input.mjs.map +1 -1
- package/dist/esm/common/input/input.types.mjs.map +1 -1
- package/dist/esm/common/klipyPicker/klipyPicker.mjs +1 -1
- package/dist/esm/common/klipyPicker/klipyPicker.mjs.map +1 -1
- package/dist/esm/common/modal/modal.mjs +1 -1
- package/dist/esm/common/modal/modal.mjs.map +1 -1
- package/dist/esm/common/multiSelect/multiSelect.mjs +1 -1
- package/dist/esm/common/multiSelect/multiSelect.mjs.map +1 -1
- package/dist/esm/common/numberInput/numberInput.mjs +1 -1
- package/dist/esm/common/numberInput/numberInput.mjs.map +1 -1
- package/dist/esm/common/otpInput/otpInput.mjs +1 -1
- package/dist/esm/common/otpInput/otpInput.mjs.map +1 -1
- package/dist/esm/common/otpInput/otpInput.types.mjs.map +1 -1
- package/dist/esm/common/pagination/pagination.mjs +1 -1
- package/dist/esm/common/pagination/pagination.mjs.map +1 -1
- package/dist/esm/common/roomDrawer/roomDrawer.mjs +1 -1
- package/dist/esm/common/roomDrawer/roomDrawer.mjs.map +1 -1
- package/dist/esm/common/splitButton/splitButton.mjs +1 -1
- package/dist/esm/common/splitButton/splitButton.mjs.map +1 -1
- package/dist/esm/common/timer/timer.mjs +1 -1
- package/dist/esm/common/timer/timer.mjs.map +1 -1
- package/dist/esm/common/toast/toast.mjs +1 -1
- package/dist/esm/common/toast/toast.mjs.map +1 -1
- package/dist/esm/common/toast/toastProvider.mjs +1 -1
- package/dist/esm/common/toast/toastProvider.mjs.map +1 -1
- package/dist/esm/common/tree/tree.mjs +1 -1
- package/dist/esm/common/tree/tree.mjs.map +1 -1
- package/dist/esm/common/virtualList/virtualList.mjs +1 -1
- package/dist/esm/common/virtualList/virtualList.mjs.map +1 -1
- package/dist/esm/common/virtualList/virtualListHeader.mjs +1 -1
- package/dist/esm/common/virtualList/virtualListHeader.mjs.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/types/common/actionButtons/actionButtons.types.d.ts +9 -2
- package/dist/types/common/alert/alert.types.d.ts +7 -0
- package/dist/types/common/breadcrumb/breadcrumb.types.d.ts +7 -0
- package/dist/types/common/carousel/carousel.types.d.ts +13 -0
- package/dist/types/common/chip/chip.types.d.ts +7 -1
- package/dist/types/common/colorPicker/colorPicker.d.ts +1 -1
- package/dist/types/common/colorPicker/colorPicker.types.d.ts +7 -0
- package/dist/types/common/configProvider/configProvider.types.d.ts +22 -1
- package/dist/types/common/configProvider/index.d.ts +1 -0
- package/dist/types/common/configProvider/useLabels.d.ts +22 -0
- package/dist/types/common/confirmModal/confirmModal.types.d.ts +9 -2
- package/dist/types/common/datePicker/components/datePickerMonth/datePickerMonth.d.ts +1 -13
- package/dist/types/common/datePicker/components/datePickerMonth/datePickerMonth.types.d.ts +20 -0
- package/dist/types/common/datePicker/datePicker.d.ts +1 -22
- package/dist/types/common/datePicker/datePicker.types.d.ts +51 -0
- package/dist/types/common/datePicker/index.d.ts +1 -0
- package/dist/types/common/emptyState/emptyState.d.ts +1 -1
- package/dist/types/common/emptyState/emptyState.types.d.ts +7 -0
- package/dist/types/common/input/input.types.d.ts +13 -0
- package/dist/types/common/klipyPicker/klipyPicker.d.ts +1 -1
- package/dist/types/common/klipyPicker/klipyPicker.types.d.ts +7 -0
- package/dist/types/common/modal/modal.types.d.ts +7 -0
- package/dist/types/common/multiSelect/multiSelect.types.d.ts +13 -6
- package/dist/types/common/numberInput/numberInput.types.d.ts +9 -2
- package/dist/types/common/otpInput/otpInput.types.d.ts +9 -0
- package/dist/types/common/pagination/pagination.types.d.ts +15 -0
- package/dist/types/common/roomDrawer/roomDrawer.types.d.ts +6 -0
- package/dist/types/common/splitButton/splitButton.types.d.ts +7 -0
- package/dist/types/common/timer/timer.types.d.ts +7 -0
- package/dist/types/common/toast/toast.d.ts +4 -2
- package/dist/types/common/toast/toast.types.d.ts +11 -1
- package/dist/types/common/toast/toastProvider.d.ts +1 -1
- package/dist/types/common/tree/tree.types.d.ts +7 -2
- package/dist/types/common/virtualList/virtualList.types.d.ts +15 -6
- package/dist/types/common/virtualList/virtualListHeader.d.ts +3 -3
- package/docs/ActionButtons.md +7 -6
- package/docs/Alert.md +2 -0
- package/docs/Breadcrumb.md +2 -0
- package/docs/Carousel.md +2 -0
- package/docs/Chip.md +3 -1
- package/docs/ColorPicker.md +2 -0
- package/docs/ConfigProvider.md +23 -0
- package/docs/ConfirmModal.md +7 -5
- package/docs/DatePicker.md +2 -1
- package/docs/EmptyState.md +2 -0
- package/docs/FormValidatorGroup.md +1 -1
- package/docs/Input.md +2 -0
- package/docs/KlipyPicker.md +2 -0
- package/docs/Modal.md +2 -0
- package/docs/MultiSelect.md +7 -5
- package/docs/NumberInput.md +4 -2
- package/docs/OtpInput.md +2 -0
- package/docs/Pagination.md +2 -0
- package/docs/RoomDrawer.md +2 -0
- package/docs/SplitButton.md +2 -0
- package/docs/Timer.md +2 -0
- package/docs/Toast.md +2 -0
- package/docs/Tree.md +3 -1
- package/docs/VirtualList.md +3 -2
- package/docs/Wizard.md +1 -2
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roomDrawer.mjs","names":[],"sources":["../../../../package/common/roomDrawer/roomDrawer.tsx"],"sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type PointerEvent as ReactPointerEvent,\n type ReactElement,\n} from 'react';\nimport cx from 'classnames';\nimport {\n faArrowPointer,\n faArrowRotateLeft,\n faArrowRotateRight,\n faBorderAll,\n faClone,\n faDoorOpen,\n faDownload,\n faDraftingCompass,\n faExpand,\n faMagnet,\n faMagnifyingGlassMinus,\n faMagnifyingGlassPlus,\n faAngleDown,\n faAngleUp,\n faLayerGroup,\n faMinus,\n faPlus,\n faRulerCombined,\n faSquare,\n faUpload,\n faTrash,\n faWindowMaximize,\n} from '@fortawesome/free-solid-svg-icons';\n\nimport ActionIcon from '../actionIcon';\nimport { useComponentDefaults } from '../configProvider';\nimport { useCoarsePointer } from '../hooks/useCoarsePointer';\nimport Input from '../input';\nimport NumberInput from '../numberInput';\nimport PlanCanvas from '../planCanvas';\nimport { renderIcon } from '../utils/renderIcon';\nimport {\n DEFAULT_WALL_ATTRS,\n HIT_PX,\n HIT_PX_COARSE,\n OpeningKind,\n SnapKind,\n WallType,\n addOpening,\n addRect,\n addWall,\n clientToWorld,\n commitDerivation,\n derivePlan,\n detachWall,\n dissolveNode,\n emptyFloorPlan,\n floorBounds,\n getFloor,\n hitTest,\n mergeNodes,\n MIN_WALL_LENGTH,\n addFloor,\n duplicateFloor,\n floorBelow,\n floorIndex,\n moveNode,\n MIN_FLOOR_HEIGHT,\n moveFloor,\n planFromJson,\n planToJson,\n removeFloor,\n renameFloor,\n setFloorHeight,\n removeOpening,\n removeWall,\n resolveSnap,\n selectionsEqual,\n setFloor,\n splitWall,\n updateOpening,\n weldShortWalls,\n worldTolerances,\n type FloorPlan,\n type PlanFloor,\n type PlanParseResult,\n type PlanPoint,\n type PlanSelection,\n type PlanView,\n type SnapResult,\n} from '../floorPlan';\nimport { pointToSegment } from '../floorPlan/floorPlan.geometry';\nimport { usePlanHistory } from './usePlanHistory';\nimport type {\n RoomDrawerChangeMeta,\n RoomDrawerChangeReason,\n RoomDrawerHandle,\n RoomDrawerProps,\n RoomDrawerTool,\n} from './roomDrawer.types';\nimport styles from './roomDrawer.module.pcss';\n\nconst ALL_TOOLS: RoomDrawerTool[] = ['select', 'wall', 'rect', 'door', 'window', 'archway', 'measure'];\n\nconst TOOL_KEYS: Record<string, RoomDrawerTool> = {\n v: 'select',\n w: 'wall',\n r: 'rect',\n d: 'door',\n n: 'window',\n a: 'archway',\n m: 'measure',\n};\n\nconst OPENING_TOOL_KIND: Partial<Record<RoomDrawerTool, OpeningKind>> = {\n door: OpeningKind.Door,\n window: OpeningKind.Window,\n archway: OpeningKind.Archway,\n};\n\n/** What the inspector calls each kind of opening. */\nconst OPENING_KIND_NOUN: Record<OpeningKind, string> = {\n [OpeningKind.Door]: 'Door',\n [OpeningKind.Window]: 'Window',\n [OpeningKind.Archway]: 'Archway',\n [OpeningKind.Gap]: 'Gap',\n};\n\nconst BUILT_IN_OPENING_DEFAULTS: Record<OpeningKind, { width: number; sill: number; head: number }> = {\n [OpeningKind.Door]: { width: 900, sill: 0, head: 2100 },\n [OpeningKind.Window]: { width: 1200, sill: 900, head: 2100 },\n [OpeningKind.Archway]: { width: 1200, sill: 0, head: 2100 },\n [OpeningKind.Gap]: { width: 1000, sill: 0, head: 2500 },\n};\n\n/** Below this length a ruler drag is a slip of the hand, not a measurement. */\nconst EPS_MEASURE = 1;\n\n/** Past this many pixels a press becomes a drag rather than a click. */\nconst DRAG_THRESHOLD_PX = 4;\n\n/** Stable identity for an empty selection, so memos keyed on it do not re-run. */\nconst EMPTY_SELECTION: readonly PlanSelection[] = [];\n\ntype Gesture =\n | { kind: 'none' }\n | { kind: 'pan'; startClient: PlanPoint; startView: PlanView }\n | { kind: 'pinch'; startDist: number; startView: PlanView; startMid: PlanPoint }\n /**\n * `origin`, `referenceAngle` and `excludeWallIds` are captured ONCE at gesture start.\n * Recomputing them per frame makes them follow the node being dragged, so angle lock\n * ends up measuring from a basis that rotates with the cursor, and the walls attached\n * to the corner become snap targets for the corner itself.\n */\n | {\n kind: 'node';\n nodeId: string;\n moved: boolean;\n origin?: PlanPoint;\n referenceAngle?: number;\n excludeWallIds: ReadonlySet<string>;\n }\n | {\n kind: 'wall';\n wallId: string;\n startWorld: PlanPoint;\n origA: PlanPoint;\n origB: PlanPoint;\n moved: boolean;\n /** Ctrl was held: pull the wall off its neighbours on the first real movement. */\n detach?: boolean;\n /** Which way it was pulled, once it has been. It cannot be pushed back across that line. */\n detachedSign?: number;\n /** A Ctrl press that has not become a drag yet, and so may still be a selection toggle. */\n toggle?: boolean;\n }\n | { kind: 'opening'; openingId: string; moved: boolean }\n | { kind: 'room'; nodeIds: string[]; startWorld: PlanPoint; orig: Record<string, PlanPoint>; moved: boolean }\n /**\n * The midpoint handle of a selected wall has been pressed. The split is deliberately\n * deferred until the pointer actually moves, so a stray click on the handle leaves the\n * wall exactly as it was instead of silently adding a corner.\n */\n | { kind: 'wallSplit'; wallId: string; at: PlanPoint }\n | { kind: 'rect'; start: PlanPoint; current: PlanPoint }\n | { kind: 'measure'; start: PlanPoint; current: PlanPoint }\n /** Dragging one end of a measurement already taken. */\n | { kind: 'measureEnd'; id: number; end: 'a' | 'b' }\n /** Sliding a whole measurement, both ends together. */\n | { kind: 'measureMove'; id: number; startWorld: PlanPoint; origA: PlanPoint; origB: PlanPoint }\n /** Rubber-band selection over empty canvas. `base` is what was selected before it began. */\n | {\n kind: 'marquee';\n start: PlanPoint;\n current: PlanPoint;\n additive: boolean;\n base: readonly PlanSelection[];\n };\n\nfunction defaultFormatLength(mm: number): string {\n return mm >= 1000 ? `${(mm / 1000).toFixed(2)} m` : `${Math.round(mm)} mm`;\n}\n\nfunction defaultFormatArea(mm2: number): string {\n const m2 = mm2 / 1_000_000;\n return `${m2.toFixed(m2 < 10 ? 1 : 0)} m²`;\n}\n\n/** Radians to a signed degree reading in (-180, 180], which is how a plan angle reads. */\nfunction formatAngle(radians: number): string {\n let deg = (radians * 180) / Math.PI;\n deg = ((deg % 360) + 360) % 360;\n if (deg > 180) deg -= 360;\n // -0 would render as \"-0°\".\n return `${Math.abs(deg) < 0.05 ? 0 : Math.round(deg * 10) / 10}°`;\n}\n\nconst RoomDrawer = forwardRef<RoomDrawerHandle, RoomDrawerProps>(function RoomDrawer(props, ref) {\n const {\n value,\n defaultValue,\n onChange,\n onChangeEnd,\n floorId,\n defaultFloorId,\n onFloorChange,\n showFloorBar = true,\n showUnderlay = true,\n\n tool: toolProp,\n defaultTool = 'select',\n onToolChange,\n tools = ALL_TOOLS,\n\n selection: selectionProp,\n defaultSelection,\n onSelectionChange,\n multiSelect = true,\n onHoverChange,\n\n view: viewProp,\n defaultView,\n onViewChange,\n minZoom = 0.005,\n maxZoom = 2,\n fitOnMount = true,\n\n gridSize = 100,\n showGrid = true,\n snap = true,\n angleLock = false,\n constrainAngle = 45,\n showSnapGuides = true,\n\n wallThickness = DEFAULT_WALL_ATTRS.thickness,\n wallHeight,\n openingDefaults,\n\n showMeasurements = true,\n formatLength = defaultFormatLength,\n formatArea = defaultFormatArea,\n\n showToolbar = true,\n showInspector = true,\n showHint = true,\n showHistoryControls = true,\n showFileControls = true,\n fileName,\n onImport,\n\n historyMode = 'internal',\n historyLimit = 50,\n onHistoryChange,\n\n readOnly = false,\n disabled = false,\n\n canvasHeight = '420px',\n getRoomColor,\n renderRoomLabel,\n\n labels,\n icons,\n 'aria-label': ariaLabel,\n\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('RoomDrawer', props);\n\n const coarse = useCoarsePointer();\n const hitPx = coarse ? HIT_PX_COARSE : HIT_PX;\n\n const rootRef = useRef<HTMLDivElement>(null);\n const canvasWrapRef = useRef<HTMLDivElement>(null);\n const svgRef = useRef<SVGSVGElement>(null);\n const lastEmittedRef = useRef<FloorPlan | null>(null);\n const gestureRef = useRef<Gesture>({ kind: 'none' });\n const pointersRef = useRef<Map<number, PlanPoint>>(new Map());\n const pressRef = useRef<{ client: PlanPoint; moved: boolean } | null>(null);\n const spaceHeldRef = useRef(false);\n const altHeldRef = useRef(false);\n const shiftHeldRef = useRef(false);\n\n // ─── Plan state ───────────────────────────────────────────────────────────────\n\n const initialPlan = useMemo(\n () => defaultValue ?? value ?? emptyFloorPlan(),\n // Only ever read once, to seed the uncontrolled state.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [],\n );\n const history = usePlanHistory(initialPlan, historyLimit);\n const isControlled = value !== undefined;\n const plan = isControlled ? value : history.present;\n\n // Keep the internal history in step with an externally driven value, so undo still\n // works for a controlled consumer that has not implemented its own.\n useEffect(() => {\n if (!isControlled || !value) return;\n if (value === lastEmittedRef.current) {\n history.replace(value);\n return;\n }\n history.commit(value, 'external');\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [value, isControlled]);\n\n const [floorIdState, setFloorIdState] = useState<string | undefined>(defaultFloorId);\n // The floor a stale id points at is gone, so fall back to the lowest rather than\n // rendering nothing: a plan always has at least one floor.\n const activeFloorId = floorId ?? floorIdState ?? plan.floors[0]?.id;\n const floor: PlanFloor = useMemo(\n () => getFloor(plan, activeFloorId) ?? getFloor(plan) ?? emptyFloorPlan().floors[0],\n [plan, activeFloorId],\n );\n const [underlayOn, setUnderlayOn] = useState(showUnderlay);\n useEffect(() => setUnderlayOn(showUnderlay), [showUnderlay]);\n const underlayFloor = useMemo(() => (underlayOn ? floorBelow(plan, floor.id) : null), [underlayOn, plan, floor.id]);\n\n const selectFloor = useCallback(\n (nextId: string) => {\n if (nextId === floor.id) return;\n if (floorId === undefined) setFloorIdState(nextId);\n onFloorChange?.(nextId);\n },\n [floor.id, floorId, onFloorChange],\n );\n\n // Memoised on the floor identity, so a gesture that changed nothing re-derives nothing.\n const derivation = useMemo(() => derivePlan(floor), [floor]);\n const rooms = derivation.rooms;\n\n // ─── Tool / selection / view ──────────────────────────────────────────────────\n\n const [toolState, setToolState] = useState<RoomDrawerTool>(defaultTool);\n const tool = toolProp ?? toolState;\n const setTool = useCallback(\n (next: RoomDrawerTool) => {\n if (toolProp === undefined) setToolState(next);\n onToolChange?.(next);\n },\n [toolProp, onToolChange],\n );\n\n const [selectionState, setSelectionState] = useState<readonly PlanSelection[]>(defaultSelection ?? EMPTY_SELECTION);\n const selection = selectionProp !== undefined ? selectionProp : selectionState;\n /**\n * Mirrors `selection` synchronously, so `handle.select(x)` immediately followed by\n * `handle.deleteSelection()` acts on `x` rather than on whatever was selected before\n * React re-rendered.\n */\n const selectionRef = useRef<readonly PlanSelection[]>(selection);\n selectionRef.current = selection;\n\n const setSelection = useCallback(\n (next: readonly PlanSelection[] | PlanSelection | null) => {\n const list = next === null ? EMPTY_SELECTION : Array.isArray(next) ? next : [next as PlanSelection];\n selectionRef.current = list;\n if (selectionProp === undefined) setSelectionState(list);\n onSelectionChange?.([...list]);\n },\n [selectionProp, onSelectionChange],\n );\n\n /** Adds an entry if absent, removes it if present. */\n const toggleSelection = useCallback(\n (entry: PlanSelection) => {\n const current = selectionRef.current;\n const without = current.filter((e) => !(e.type === entry.type && e.id === entry.id));\n setSelection(without.length === current.length ? [...current, entry] : without);\n },\n [setSelection],\n );\n\n /**\n * Exactly one thing selected. The per-entity chrome (the inspector's fields, the\n * midpoint handle) keys off this rather than off `selection[0]`, so it disappears\n * cleanly once a second thing joins the selection instead of silently acting on\n * whichever entry happened to come first.\n */\n const single = selection.length === 1 ? selection[0] : null;\n\n /**\n * Every node the selection implies, which is what a translation actually moves. A room\n * is its face's corners, a wall is its two ends, a corner is itself. Openings have no\n * corners of their own and so translate with whatever wall they are cut into.\n */\n const nodeIdsForSelection = useCallback(\n (entries: readonly PlanSelection[]): Set<string> => {\n const ids = new Set<string>();\n for (const entry of entries) {\n if (entry.type === 'node') {\n ids.add(entry.id);\n } else if (entry.type === 'wall') {\n const wall = floor.walls[entry.id];\n if (wall) {\n ids.add(wall.a);\n ids.add(wall.b);\n }\n } else if (entry.type === 'room') {\n const room = rooms.find((r) => r.roomId === entry.id);\n for (const id of room?.nodeIds ?? []) ids.add(id);\n }\n }\n return ids;\n },\n [floor.walls, rooms],\n );\n\n const selectedNodeIds = useMemo(() => nodeIdsForSelection(selection), [nodeIdsForSelection, selection]);\n\n const [hovered, setHovered] = useState<PlanSelection | null>(null);\n const [viewState, setViewState] = useState<PlanView>(defaultView ?? { x: 0, y: 0, zoom: 0.05 });\n const view = viewProp ?? viewState;\n const setView = useCallback(\n (next: PlanView) => {\n if (viewProp === undefined) setViewState(next);\n onViewChange?.(next);\n },\n [viewProp, onViewChange],\n );\n\n const [size, setSize] = useState({ width: 800, height: 420 });\n const [wallChain, setWallChain] = useState<PlanPoint[]>([]);\n const [cursor, setCursor] = useState<PlanPoint | null>(null);\n\n /**\n * Latched snapping and angle lock. Seeded from the props, then owned by the toolbar\n * toggles and their hotkeys; changing the prop resets the latch.\n */\n const [snapEnabled, setSnapEnabled] = useState(snap);\n const [angleLockEnabled, setAngleLockEnabled] = useState(angleLock);\n useEffect(() => setSnapEnabled(snap), [snap]);\n useEffect(() => setAngleLockEnabled(angleLock), [angleLock]);\n\n const [gridVisible, setGridVisible] = useState(showGrid);\n useEffect(() => setGridVisible(showGrid), [showGrid]);\n\n /** The most recent snap result, so the overlay can explain what it snapped to. */\n const lastSnapRef = useRef<SnapResult | null>(null);\n const [snapHint, setSnapHint] = useState<SnapResult | null>(null);\n\n /**\n * Ruler measurements. Deliberately component state rather than plan data: a line in\n * the wall graph would close faces, and faces become rooms, so a \"guide\" stored as\n * geometry would fight the whole derived-room model. Nothing here reaches `onChange`,\n * a downloaded file, or the undo stack.\n */\n const [measurements, setMeasurements] = useState<Array<{ id: number; a: PlanPoint; b: PlanPoint }>>([]);\n const measureSeqRef = useRef(0);\n const [hoveredMeasure, setHoveredMeasure] = useState<number | null>(null);\n\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n /**\n * What of the ruler is under a point, nearest first: an end before the line, so the\n * ends stay grabbable where they sit on top of it. Only live while the measure tool is\n * up, so measurements are inert scenery under every other tool.\n */\n const measureHit = useCallback(\n (world: PlanPoint): { id: number; end?: 'a' | 'b' } | null => {\n const tol = worldTolerances(hitPx, view.zoom);\n for (let i = measurements.length - 1; i >= 0; i--) {\n const m = measurements[i];\n if (Math.hypot(world.x - m.a.x, world.y - m.a.y) <= tol.node) return { id: m.id, end: 'a' };\n if (Math.hypot(world.x - m.b.x, world.y - m.b.y) <= tol.node) return { id: m.id, end: 'b' };\n }\n for (let i = measurements.length - 1; i >= 0; i--) {\n const m = measurements[i];\n if (pointToSegment(world, m.a, m.b).dist <= tol.wall) return { id: m.id };\n }\n return null;\n },\n [measurements, hitPx, view.zoom],\n );\n\n /**\n * What to say about the file that was just opened, or `null` when there is nothing to\n * say. An empty array means it failed outright; a filled one means it loaded but had to\n * be repaired. Shown in the hint line, and cleared as soon as the user carries on.\n */\n const [importReport, setImportReport] = useState<string[] | null>(null);\n\n const editable = !readOnly && !disabled;\n\n /** Direction of the last committed chain segment, so the next one can go square to it. */\n const chainReferenceAngle = useMemo(() => {\n if (wallChain.length < 2) return undefined;\n const a = wallChain[wallChain.length - 2];\n const b = wallChain[wallChain.length - 1];\n return Math.atan2(b.y - a.y, b.x - a.x);\n }, [wallChain]);\n\n // ─── Sizing ───────────────────────────────────────────────────────────────────\n\n useEffect(() => {\n const element = canvasWrapRef.current;\n if (!element) return undefined;\n const observer = new ResizeObserver(() => {\n const rect = element.getBoundingClientRect();\n if (rect.width > 0 && rect.height > 0) {\n setSize({ width: Math.round(rect.width), height: Math.round(rect.height) });\n }\n });\n observer.observe(element);\n const rect = element.getBoundingClientRect();\n if (rect.width > 0 && rect.height > 0) {\n setSize({ width: Math.round(rect.width), height: Math.round(rect.height) });\n }\n return () => observer.disconnect();\n }, []);\n\n // ─── Coordinates ──────────────────────────────────────────────────────────────\n\n const toWorld = useCallback(\n (clientX: number, clientY: number): PlanPoint => {\n const rect = canvasWrapRef.current?.getBoundingClientRect() ?? { left: 0, top: 0 };\n return clientToWorld(clientX, clientY, rect, view);\n },\n [view],\n );\n\n const toScreen = useCallback(\n (p: PlanPoint) => ({ x: (p.x - view.x) * view.zoom, y: (p.y - view.y) * view.zoom }),\n [view],\n );\n\n const snapAt = useCallback(\n (\n world: PlanPoint,\n origin?: PlanPoint,\n excludeNodeIds?: ReadonlySet<string>,\n referenceAngle?: number,\n excludeWallIds?: ReadonlySet<string>,\n ) => {\n // Alt INVERTS the current setting rather than only switching snapping off, so it\n // doubles as a momentary \"snap just this once\" when snapping is latched off.\n const useSnap = snapEnabled !== altHeldRef.current;\n // Likewise Shift: momentary lock when the latch is off, momentary release when on.\n const lockAngle = angleLockEnabled !== shiftHeldRef.current;\n const result = resolveSnap(world, {\n floor,\n pxToWorld: view.zoom > 0 ? 1 / view.zoom : 1,\n origin,\n referenceAngle,\n excludeNodeIds,\n excludeWallIds,\n options: {\n enabled: useSnap\n ? [\n SnapKind.Node,\n SnapKind.Intersection,\n SnapKind.Wall,\n SnapKind.Angle,\n SnapKind.Extension,\n SnapKind.Alignment,\n SnapKind.Grid,\n ]\n : [],\n gridSize,\n angleStepDeg: lockAngle ? constrainAngle : 0,\n toleranceScreenPx: coarse ? 16 : 10,\n },\n });\n lastSnapRef.current = result;\n return result;\n },\n [floor, view.zoom, snapEnabled, angleLockEnabled, gridSize, constrainAngle, coarse],\n );\n\n /**\n * The snapping context for dragging a corner, measured once before anything moves.\n *\n * `origin` is the far end of one incident wall, so angle lock has something fixed to\n * measure from. `referenceAngle` is the direction of a wall meeting the graph at that\n * ORIGIN, which makes \"square to the run this corner hangs off\" available. It is taken\n * from there rather than from a second incident wall because neither of the origin\n * wall's endpoints moves during the drag, so the basis stays put; a wall touching the\n * dragged corner would rotate as the corner moves and the lock would chase itself.\n *\n * Every incident wall is excluded from snapping: they move with the corner, so leaving\n * them in means the corner snaps to itself.\n */\n const nodeDragContext = useCallback(\n (nodeId: string): { origin?: PlanPoint; referenceAngle?: number; excludeWallIds: ReadonlySet<string> } => {\n const incident = Object.values(floor.walls).filter((w) => w.a === nodeId || w.b === nodeId);\n // Every wall meeting the corner moves with it, so none of them may be snapped to:\n // leaving them in means the corner snaps to itself.\n const excludeWallIds = new Set(incident.map((w) => w.id));\n\n const node = floor.nodes[nodeId];\n if (!node) return { excludeWallIds };\n\n // Angle lock constrains the DIRECTION OF TRAVEL, measured from where the corner\n // started, which is what Shift-dragging means in every editor and makes all eight\n // directions reachable. Measuring from a neighbouring corner instead (as this once\n // did) only ever puts one ray near the corner you are holding: the one running\n // along the existing wall. Everything else lands metres away, so in practice the\n // lock appeared to work in one direction only.\n const origin = { x: node.x, y: node.y };\n\n // Plus the direction of a wall meeting here, so travel can also be taken parallel\n // or square to it rather than only to the page.\n let referenceAngle: number | undefined;\n const first = incident[0];\n if (first) {\n const farId = first.a === nodeId ? first.b : first.a;\n const far = floor.nodes[farId];\n if (far) referenceAngle = Math.atan2(far.y - node.y, far.x - node.x);\n }\n\n return { origin, referenceAngle, excludeWallIds };\n },\n [floor],\n );\n\n const fitToContent = useCallback(\n (options: { padding?: number } = {}) => {\n const padding = options.padding ?? 24;\n const bounds = floorBounds(floor);\n if (!bounds || bounds.width <= 0 || bounds.height <= 0) {\n setView({ x: -1000, y: -1000, zoom: 0.05 });\n return;\n }\n const zoom = Math.min((size.width - padding * 2) / bounds.width, (size.height - padding * 2) / bounds.height);\n const clamped = Math.max(minZoom, Math.min(maxZoom, zoom));\n setView({\n x: bounds.minX - (size.width / clamped - bounds.width) / 2,\n y: bounds.minY - (size.height / clamped - bounds.height) / 2,\n zoom: clamped,\n });\n },\n [floor, size.width, size.height, minZoom, maxZoom, setView],\n );\n\n // Fit once, as soon as there is something to fit and a measured canvas.\n const didFitRef = useRef(false);\n useEffect(() => {\n if (!fitOnMount || didFitRef.current) return;\n if (Object.keys(floor.nodes).length === 0) return;\n if (size.width <= 1) return;\n didFitRef.current = true;\n fitToContent();\n }, [fitOnMount, floor, size.width, fitToContent]);\n\n // ─── Emitting changes ─────────────────────────────────────────────────────────\n\n const emit = useCallback(\n (\n nextFloor: PlanFloor,\n reason: RoomDrawerChangeReason,\n committed: boolean,\n targetIds: string[] = [],\n /**\n * `false` when the edit is the tail of a gesture that already pushed its own\n * snapshot, so the two do not become two undo steps for one action.\n */\n options: { push?: boolean } = {},\n ) => {\n if (nextFloor === floor) return plan;\n const nextPlan = setFloor(plan, nextFloor);\n lastEmittedRef.current = nextPlan;\n\n // History is bookkeeping, not rendering state, so it runs in BOTH modes. Skipping\n // it while controlled left discrete edits (delete, rename, nudge) with no entry at\n // all, and Undo then jumped back past them to whatever the last drag had stashed.\n if (committed) {\n if (options.push === false) history.replace(nextPlan);\n else history.commit(nextPlan, reason, targetIds);\n } else {\n history.updateGesture(nextPlan);\n }\n\n const meta: RoomDrawerChangeMeta = { reason, committed, targetIds };\n onChange?.(nextPlan, meta);\n if (committed) onChangeEnd?.(nextPlan, meta);\n return nextPlan;\n },\n [floor, plan, history, onChange, onChangeEnd],\n );\n\n /** A discrete edit: names new rooms and pushes exactly one history entry. */\n const commitEdit = useCallback(\n (\n nextFloor: PlanFloor,\n reason: RoomDrawerChangeReason,\n targetIds: string[] = [],\n options: { push?: boolean } = {},\n ) => {\n // Welding here rather than per-gesture: a drag is not the only way to squash a\n // wall to nothing, and a plan with two corners on the same point derives as two\n // rooms and refuses to round-trip through JSON without being \"repaired\".\n const named = commitDerivation(weldShortWalls(nextFloor).floor).floor;\n return emit(named, reason, true, targetIds, options);\n },\n [emit],\n );\n\n useEffect(() => {\n onHistoryChange?.({ canUndo: history.canUndo, canRedo: history.canRedo });\n }, [history.canUndo, history.canRedo, onHistoryChange]);\n\n // ─── Selection helpers ────────────────────────────────────────────────────────\n\n const selectedRoom = useMemo(\n () => (single?.type === 'room' ? (rooms.find((r) => r.roomId === single.id) ?? null) : null),\n [single, rooms],\n );\n const selectedWall = single?.type === 'wall' ? floor.walls[single.id] : undefined;\n const selectedOpening = single?.type === 'opening' ? floor.openings[single.id] : undefined;\n\n /** Midpoint of the selected wall, where its \"add a corner\" handle sits. */\n const selectedWallMidpoint = useMemo(() => {\n if (!selectedWall) return null;\n const a = floor.nodes[selectedWall.a];\n const b = floor.nodes[selectedWall.b];\n if (!a || !b) return null;\n return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };\n }, [selectedWall, floor.nodes]);\n\n /** Splits the selected wall at its midpoint and selects the corner that appears. */\n const addCornerToSelectedWall = useCallback(() => {\n if (!editable || !selectedWall) return;\n const edit = splitWall(floor, selectedWall.id, 0.5);\n if (!edit.result.wallIds) return;\n commitEdit(edit.floor, 'edit-wall', [edit.result.nodeId]);\n setSelection({ type: 'node', id: edit.result.nodeId });\n }, [editable, selectedWall, floor, commitEdit, setSelection]);\n\n /** Removes one entry. Split out so deleting a whole selection can reuse it. */\n const deleteOne = useCallback(\n (\n entry: PlanSelection,\n from: PlanFloor,\n /** Room ids going in the same operation, so they do not count as needing a wall. */\n alsoDeleting: ReadonlySet<string> = new Set(),\n ): { floor: PlanFloor; reselect?: PlanSelection } => {\n if (entry.type === 'wall') return { floor: removeWall(from, entry.id).floor };\n if (entry.type === 'opening') return { floor: removeOpening(from, entry.id).floor };\n\n if (entry.type === 'node') {\n // Deleting a corner between exactly two walls means \"straighten this out\": the\n // two walls rejoin into one. Removing both of them instead would leave a hole\n // where the user only wanted the bend gone. `force` is needed because the corner\n // is normally bent by the time anyone wants rid of it, which the unforced\n // dissolve refuses.\n const rejoined = dissolveNode(from, entry.id, { force: true });\n if (rejoined.result) {\n return { floor: rejoined.floor, reselect: { type: 'wall', id: rejoined.result.wallId } };\n }\n // A loose end or a junction of three or more: there is nothing to rejoin, so the\n // walls meeting there go with it.\n let next = from;\n for (const wall of Object.values(from.walls)) {\n if (wall.a === entry.id || wall.b === entry.id) {\n next = removeWall(next, wall.id, { dissolveCollinear: false }).floor;\n }\n }\n return { floor: next };\n }\n\n // A room: drop its metadata and the walls that ONLY it uses. A wall a neighbouring\n // room still needs survives, which is what \"walls are shared\" has to mean.\n const room = rooms.find((r) => r.roomId === entry.id);\n if (!room) return { floor: from };\n const usedElsewhere = new Set<string>();\n for (const other of rooms) {\n if (other.roomId === entry.id) continue;\n // A neighbour being deleted in the same breath does not need the wall either,\n // otherwise deleting two adjacent rooms together would strand the wall they share.\n if (other.roomId && alsoDeleting.has(other.roomId)) continue;\n for (const id of other.wallIds) usedElsewhere.add(id);\n }\n let next = from;\n for (const wallId of new Set(room.wallIds)) {\n if (usedElsewhere.has(wallId)) continue;\n next = removeWall(next, wallId, { dissolveCollinear: false }).floor;\n }\n const nextRooms = { ...next.rooms };\n delete nextRooms[entry.id];\n return { floor: { ...next, rooms: nextRooms } };\n },\n [rooms],\n );\n\n const deleteSelection = useCallback(() => {\n const entries = selectionRef.current;\n if (!editable || entries.length === 0) return;\n\n // Rooms first, then walls, then corners. A corner deleted early can dissolve a wall\n // that a later entry still refers to, and rooms need their walls intact to work out\n // which of them a neighbour is still using.\n const order: Record<PlanSelection['type'], number> = { room: 0, opening: 1, wall: 2, node: 3 };\n const ordered = [...entries].sort((a, b) => order[a.type] - order[b.type]);\n\n const deletingRooms = new Set(ordered.filter((e) => e.type === 'room').map((e) => e.id));\n let next = floor;\n let reselect: PlanSelection | undefined;\n for (const entry of ordered) {\n const result = deleteOne(entry, next, deletingRooms);\n next = result.floor;\n // Only meaningful when one thing was deleted; a bulk delete just clears.\n if (ordered.length === 1) reselect = result.reselect;\n }\n\n if (next === floor) return;\n commitEdit(\n next,\n 'delete',\n ordered.map((e) => e.id),\n );\n setSelection(reselect ?? null);\n }, [editable, floor, deleteOne, commitEdit, setSelection]);\n\n // ─── Pointer handling ─────────────────────────────────────────────────────────\n\n /**\n * The snap hint belongs to whatever is being drawn, and only `finishGesture` used to\n * clear it — so ending a chain from the keyboard left its guide and marker painted on\n * the canvas with nothing to explain them. Identity is preserved when there is nothing\n * to clear, so the tool-change effect below costs no render.\n */\n const endWallChain = useCallback(() => {\n setWallChain((prev) => (prev.length === 0 ? prev : []));\n setSnapHint(null);\n }, []);\n\n // Switching tool abandons whatever the last one had half-drawn, hint included.\n useEffect(() => {\n endWallChain();\n }, [tool, endWallChain]);\n\n const handlePointerDown = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>) => {\n if (disabled) return;\n rootRef.current?.focus();\n // Whatever the last opened file had to say, the user has read it or does not care.\n setImportReport(null);\n pointersRef.current.set(event.pointerId, { x: event.clientX, y: event.clientY });\n (event.currentTarget as HTMLElement).setPointerCapture?.(event.pointerId);\n\n // Two pointers abort whatever tool gesture was running and become a pinch.\n if (pointersRef.current.size === 2) {\n const [p1, p2] = [...pointersRef.current.values()];\n if (gestureRef.current.kind !== 'none' && gestureRef.current.kind !== 'pan') {\n history.cancelGesture();\n }\n gestureRef.current = {\n kind: 'pinch',\n startDist: Math.hypot(p2.x - p1.x, p2.y - p1.y),\n startView: view,\n startMid: { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 },\n };\n return;\n }\n if (pointersRef.current.size > 2) return;\n\n pressRef.current = { client: { x: event.clientX, y: event.clientY }, moved: false };\n\n // Pan: middle button, or space held.\n if (event.button === 1 || spaceHeldRef.current) {\n gestureRef.current = {\n kind: 'pan',\n startClient: { x: event.clientX, y: event.clientY },\n startView: view,\n };\n return;\n }\n if (event.button !== 0) return;\n\n const world = toWorld(event.clientX, event.clientY);\n\n if (!editable) {\n const hit = hitTest(floor, rooms, world, worldTolerances(hitPx, view.zoom));\n setSelection(hit);\n return;\n }\n\n if (tool === 'rect') {\n const start = snapAt(world).point;\n gestureRef.current = { kind: 'rect', start, current: start };\n history.beginGesture();\n return;\n }\n\n if (tool === 'measure') {\n // No `history.beginGesture()` anywhere in here: measuring changes nothing, so\n // there is nothing to take back and an undo entry would be a lie.\n const grabbed = measureHit(world);\n if (grabbed) {\n const m = measurements.find((x) => x.id === grabbed.id)!;\n gestureRef.current = grabbed.end\n ? { kind: 'measureEnd', id: grabbed.id, end: grabbed.end }\n : { kind: 'measureMove', id: grabbed.id, startWorld: world, origA: m.a, origB: m.b };\n return;\n }\n const start = snapAt(world).point;\n gestureRef.current = { kind: 'measure', start, current: start };\n return;\n }\n\n if (tool === 'wall') {\n const origin = wallChain[wallChain.length - 1];\n const snapped = snapAt(world, origin, undefined, chainReferenceAngle).point;\n if (wallChain.length === 0) {\n setWallChain([snapped]);\n return;\n }\n const from = wallChain[wallChain.length - 1];\n const edit = addWall(\n floor,\n from,\n snapped,\n { thickness: wallThickness, type: WallType.Interior, height: wallHeight },\n worldTolerances(hitPx, view.zoom).node,\n );\n if (edit.result.wallIds.length > 0) commitEdit(edit.floor, 'draw-wall', edit.result.wallIds);\n // Closing the loop on the chain's own start finishes it.\n const closes =\n wallChain.length >= 2 &&\n Math.hypot(snapped.x - wallChain[0].x, snapped.y - wallChain[0].y) < worldTolerances(hitPx, view.zoom).node;\n if (closes) endWallChain();\n else setWallChain((prev) => [...prev, snapped]);\n return;\n }\n\n const openingKind = OPENING_TOOL_KIND[tool];\n if (openingKind) {\n const tolerances = worldTolerances(hitPx, view.zoom);\n const hit = hitTest(floor, rooms, world, tolerances, { accept: ['wall'] });\n if (hit?.type !== 'wall') return;\n const wall = floor.walls[hit.id];\n const a = floor.nodes[wall.a];\n const b = floor.nodes[wall.b];\n const len = Math.hypot(b.x - a.x, b.y - a.y);\n const t = ((world.x - a.x) * (b.x - a.x) + (world.y - a.y) * (b.y - a.y)) / (len * len || 1);\n const defaults = {\n ...BUILT_IN_OPENING_DEFAULTS[openingKind],\n ...openingDefaults?.[openingKind],\n };\n const edit = addOpening(floor, {\n wallId: wall.id,\n kind: openingKind,\n offset: Math.min(Math.max(t, 0), 1) * len,\n width: defaults.width,\n sill: defaults.sill,\n head: defaults.head,\n });\n if (edit.result.openingId) {\n commitEdit(edit.floor, 'add-opening', [edit.result.openingId]);\n setSelection({ type: 'opening', id: edit.result.openingId });\n }\n return;\n }\n\n // Select tool.\n const tolerances = worldTolerances(hitPx, view.zoom);\n\n // The selected wall's midpoint handle sits on top of the wall itself, so it has to\n // be tested before the general hit test or the wall underneath always wins.\n if (selectedWallMidpoint && single?.type === 'wall') {\n const grabbed =\n Math.hypot(world.x - selectedWallMidpoint.x, world.y - selectedWallMidpoint.y) <= tolerances.node;\n if (grabbed) {\n history.beginGesture();\n gestureRef.current = {\n kind: 'wallSplit',\n wallId: single.id,\n at: selectedWallMidpoint,\n };\n return;\n }\n }\n\n const hit = hitTest(floor, rooms, world, tolerances);\n const additive = multiSelect && (event.ctrlKey || event.metaKey);\n\n if (!hit) {\n // Empty canvas. With multi-select on, a drag from here is a marquee; a plain\n // click clears. Ctrl is honoured so a stray miss does not wipe the selection.\n if (!additive) setSelection(null);\n gestureRef.current = multiSelect\n ? { kind: 'marquee', start: world, current: world, additive, base: selectionRef.current }\n : { kind: 'none' };\n return;\n }\n\n if ((event.ctrlKey || event.metaKey) && hit.type === 'wall' && floor.walls[hit.id]) {\n // Ctrl on a wall stays ambiguous until the pointer moves: a click toggles it in\n // or out of the selection, a drag pulls it off its neighbours. The drag\n // threshold decides, which is the same gate that stops a wobbling click from\n // moving what it just picked up, so the toggle waits for pointerup.\n const wall = floor.walls[hit.id];\n if (!additive) setSelection(hit);\n history.beginGesture();\n gestureRef.current = {\n kind: 'wall',\n wallId: hit.id,\n startWorld: world,\n origA: { ...floor.nodes[wall.a] },\n origB: { ...floor.nodes[wall.b] },\n moved: false,\n detach: true,\n toggle: additive,\n };\n return;\n }\n\n if (additive) {\n // Toggling is a selection change on its own: no drag follows, so that a\n // Ctrl-click which happens to wobble does not also move what it just picked up.\n toggleSelection(hit);\n gestureRef.current = { kind: 'none' };\n return;\n }\n\n const alreadySelected = selectionRef.current.some((entry) => entry.type === hit.type && entry.id === hit.id);\n // Pressing something already in a multi-selection moves the whole set. Pressing\n // anything else selects just it, which is what every editor does.\n if (!alreadySelected) setSelection(hit);\n\n history.beginGesture();\n\n if (selectionRef.current.length > 1) {\n const nodeIds = [...nodeIdsForSelection(selectionRef.current)];\n const orig: Record<string, PlanPoint> = {};\n for (const id of nodeIds) {\n const node = floor.nodes[id];\n if (node) orig[id] = { x: node.x, y: node.y };\n }\n gestureRef.current = { kind: 'room', nodeIds, startWorld: world, orig, moved: false };\n return;\n }\n\n if (hit.type === 'node') {\n gestureRef.current = {\n kind: 'node',\n nodeId: hit.id,\n moved: false,\n ...nodeDragContext(hit.id),\n };\n } else if (hit.type === 'wall') {\n const wall = floor.walls[hit.id];\n gestureRef.current = {\n kind: 'wall',\n wallId: hit.id,\n startWorld: world,\n origA: { ...floor.nodes[wall.a] },\n origB: { ...floor.nodes[wall.b] },\n moved: false,\n };\n } else if (hit.type === 'opening') {\n gestureRef.current = { kind: 'opening', openingId: hit.id, moved: false };\n } else {\n const room = rooms.find((r) => r.roomId === hit.id);\n const nodeIds = room ? [...new Set(room.nodeIds)] : [];\n const orig: Record<string, PlanPoint> = {};\n for (const id of nodeIds) {\n const node = floor.nodes[id];\n if (node) orig[id] = { x: node.x, y: node.y };\n }\n gestureRef.current = { kind: 'room', nodeIds, startWorld: world, orig, moved: false };\n }\n },\n [\n disabled,\n editable,\n view,\n tool,\n floor,\n rooms,\n hitPx,\n toWorld,\n snapAt,\n wallChain,\n wallThickness,\n wallHeight,\n openingDefaults,\n commitEdit,\n setSelection,\n selectedWallMidpoint,\n single,\n chainReferenceAngle,\n nodeDragContext,\n nodeIdsForSelection,\n multiSelect,\n toggleSelection,\n history,\n endWallChain,\n measureHit,\n measurements,\n ],\n );\n\n const handlePointerMove = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>) => {\n if (disabled) return;\n if (pointersRef.current.has(event.pointerId)) {\n pointersRef.current.set(event.pointerId, { x: event.clientX, y: event.clientY });\n }\n\n const gesture = gestureRef.current;\n\n if (gesture.kind === 'pinch' && pointersRef.current.size >= 2) {\n const [p1, p2] = [...pointersRef.current.values()];\n const distNow = Math.hypot(p2.x - p1.x, p2.y - p1.y);\n if (gesture.startDist <= 0) return;\n const nextZoom = Math.max(minZoom, Math.min(maxZoom, gesture.startView.zoom * (distNow / gesture.startDist)));\n const rect = canvasWrapRef.current?.getBoundingClientRect() ?? { left: 0, top: 0 };\n const mid = { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 };\n // Keep the world point under the pinch midpoint where it is.\n const worldMid = clientToWorld(gesture.startMid.x, gesture.startMid.y, rect, gesture.startView);\n setView({\n x: worldMid.x - (mid.x - rect.left) / nextZoom,\n y: worldMid.y - (mid.y - rect.top) / nextZoom,\n zoom: nextZoom,\n });\n return;\n }\n\n if (pressRef.current) {\n const dx = event.clientX - pressRef.current.client.x;\n const dy = event.clientY - pressRef.current.client.y;\n if (Math.hypot(dx, dy) > DRAG_THRESHOLD_PX) pressRef.current.moved = true;\n }\n\n if (gesture.kind === 'pan') {\n setView({\n x: gesture.startView.x - (event.clientX - gesture.startClient.x) / gesture.startView.zoom,\n y: gesture.startView.y - (event.clientY - gesture.startClient.y) / gesture.startView.zoom,\n zoom: gesture.startView.zoom,\n });\n return;\n }\n\n const world = toWorld(event.clientX, event.clientY);\n setCursor(world);\n\n if (gesture.kind === 'none') {\n if (!editable) return;\n if (tool === 'wall' && wallChain.length > 0) {\n // Preview where the wall will actually land, not where the pointer is.\n const snapped = snapAt(world, wallChain[wallChain.length - 1], undefined, chainReferenceAngle);\n setCursor(snapped.point);\n setSnapHint(snapped);\n return;\n }\n if (tool === 'select') {\n const hit = hitTest(floor, rooms, world, worldTolerances(hitPx, view.zoom));\n if (!selectionsEqual(hit, hovered)) {\n setHovered(hit);\n onHoverChange?.(hit);\n }\n }\n if (tool === 'measure') setHoveredMeasure(measureHit(world)?.id ?? null);\n return;\n }\n\n if (!pressRef.current?.moved) return;\n\n if (gesture.kind === 'measureEnd') {\n const point = snapAt(world).point;\n setMeasurements((list) => list.map((m) => (m.id === gesture.id ? { ...m, [gesture.end]: point } : m)));\n setCursor(point);\n return;\n }\n\n if (gesture.kind === 'measureMove') {\n const dx = world.x - gesture.startWorld.x;\n const dy = world.y - gesture.startWorld.y;\n setMeasurements((list) =>\n list.map((m) =>\n m.id === gesture.id\n ? {\n ...m,\n a: { x: gesture.origA.x + dx, y: gesture.origA.y + dy },\n b: { x: gesture.origB.x + dx, y: gesture.origB.y + dy },\n }\n : m,\n ),\n );\n return;\n }\n\n if (gesture.kind === 'rect' || gesture.kind === 'measure') {\n gestureRef.current = { ...gesture, current: snapAt(world).point };\n setCursor(snapAt(world).point);\n return;\n }\n\n if (gesture.kind === 'marquee') {\n // Unsnapped: a selection rectangle is not geometry, so pulling it onto the grid\n // would only make it harder to enclose exactly what you meant.\n gestureRef.current = { ...gesture, current: world };\n setCursor(world);\n return;\n }\n\n if (gesture.kind === 'wallSplit') {\n // First real movement: cut the wall in two and hand the drag over to the new\n // corner, so adding a corner and placing it are one continuous gesture.\n const edit = splitWall(floor, gesture.wallId, gesture.at);\n if (!edit.result.wallIds) {\n gestureRef.current = { kind: 'none' };\n return;\n }\n const nodeId = edit.result.nodeId;\n // The two halves of the wall just split both move with the new corner, so they\n // are excluded like any other incident wall. The original wall's direction is\n // the reference, which makes \"square to the wall I am bending\" available.\n const wasWall = floor.walls[gesture.wallId];\n const originNode = wasWall ? edit.floor.nodes[wasWall.a] : undefined;\n const farNode = wasWall ? edit.floor.nodes[wasWall.b] : undefined;\n const context = {\n origin: originNode ? { x: originNode.x, y: originNode.y } : undefined,\n referenceAngle:\n originNode && farNode ? Math.atan2(farNode.y - originNode.y, farNode.x - originNode.x) : undefined,\n excludeWallIds: new Set(edit.result.wallIds),\n };\n const snapped = snapAt(\n world,\n context.origin,\n new Set([nodeId]),\n context.referenceAngle,\n context.excludeWallIds,\n ).point;\n gestureRef.current = { kind: 'node', nodeId, moved: true, ...context };\n setSelection({ type: 'node', id: nodeId });\n setSnapHint(lastSnapRef.current);\n emit(moveNode(edit.floor, nodeId, snapped).floor, 'move-node', false, [nodeId]);\n return;\n }\n\n if (gesture.kind === 'node') {\n const snapped = snapAt(\n world,\n gesture.origin,\n new Set([gesture.nodeId]),\n gesture.referenceAngle,\n gesture.excludeWallIds,\n ).point;\n setSnapHint(lastSnapRef.current);\n gestureRef.current = { ...gesture, moved: true };\n emit(moveNode(floor, gesture.nodeId, snapped).floor, 'move-node', false, [gesture.nodeId]);\n return;\n }\n\n if (gesture.kind === 'wall') {\n // Constrained to the wall's own normal: pushing a wall sideways. Because the\n // endpoints are shared, the neighbouring room follows, which is the point.\n const wall = floor.walls[gesture.wallId];\n if (!wall) return;\n const dx = gesture.origB.x - gesture.origA.x;\n const dy = gesture.origB.y - gesture.origA.y;\n const len = Math.hypot(dx, dy);\n if (len < 1e-6) return;\n const normal = { x: -dy / len, y: dx / len };\n const raw = (world.x - gesture.startWorld.x) * normal.x + (world.y - gesture.startWorld.y) * normal.y;\n const step = snap !== altHeldRef.current && gridSize > 0 ? gridSize : 1;\n const offset = Math.round(raw / step) * step;\n // How far the wall has to move before it is worth detaching: the connectors are\n // exactly this long, so anything shorter is a wall the model will not accept.\n const minDetach = Math.max(step, MIN_WALL_LENGTH);\n\n if (gesture.detach) {\n // Nothing happens until the wall has somewhere to go: detaching at zero would\n // mint two zero-length connectors, which the model forbids.\n if (Math.abs(offset) < minDetach) return;\n const edit = detachWall(floor, gesture.wallId, {\n x: normal.x * offset,\n y: normal.y * offset,\n });\n if (!edit.result) return;\n gestureRef.current = {\n ...gesture,\n detach: false,\n toggle: false,\n detachedSign: Math.sign(offset),\n moved: true,\n };\n setSelection({ type: 'wall', id: gesture.wallId });\n emit(edit.floor, 'move-wall', false, [gesture.wallId]);\n return;\n }\n\n let next = moveNode(floor, wall.a, {\n x: gesture.origA.x + normal.x * offset,\n y: gesture.origA.y + normal.y * offset,\n }).floor;\n next = moveNode(next, wall.b, {\n x: gesture.origB.x + normal.x * offset,\n y: gesture.origB.y + normal.y * offset,\n }).floor;\n\n if (gesture.detachedSign && Math.abs(offset) < MIN_WALL_LENGTH) {\n // Pushed back onto the line it came off. The connectors have no length left,\n // so their ends weld and the rest of the drag is an ordinary wall drag: the\n // corner that survives a weld is the one that was already there, which is\n // exactly the corner the wall was pulled off.\n next = weldShortWalls(next).floor;\n gestureRef.current = { ...gesture, detachedSign: undefined, moved: true };\n } else {\n gestureRef.current = { ...gesture, moved: true };\n }\n emit(next, 'move-wall', false, [gesture.wallId]);\n return;\n }\n\n if (gesture.kind === 'opening') {\n const opening = floor.openings[gesture.openingId];\n if (!opening) return;\n const wall = floor.walls[opening.wallId];\n const a = floor.nodes[wall.a];\n const b = floor.nodes[wall.b];\n const len = Math.hypot(b.x - a.x, b.y - a.y);\n const t = ((world.x - a.x) * (b.x - a.x) + (world.y - a.y) * (b.y - a.y)) / (len * len || 1);\n gestureRef.current = { ...gesture, moved: true };\n emit(\n updateOpening(floor, gesture.openingId, { offset: Math.min(Math.max(t, 0), 1) * len }).floor,\n 'move-opening',\n false,\n [gesture.openingId],\n );\n return;\n }\n\n if (gesture.kind === 'room') {\n const rawDx = world.x - gesture.startWorld.x;\n const rawDy = world.y - gesture.startWorld.y;\n const step = snap !== altHeldRef.current && gridSize > 0 ? gridSize : 1;\n const dx = Math.round(rawDx / step) * step;\n const dy = Math.round(rawDy / step) * step;\n let next = floor;\n for (const id of gesture.nodeIds) {\n const orig = gesture.orig[id];\n if (!orig) continue;\n next = moveNode(next, id, { x: orig.x + dx, y: orig.y + dy }).floor;\n }\n gestureRef.current = { ...gesture, moved: true };\n emit(next, 'move-room', false, gesture.nodeIds);\n }\n },\n [\n disabled,\n editable,\n tool,\n floor,\n rooms,\n hovered,\n hitPx,\n view.zoom,\n minZoom,\n maxZoom,\n gridSize,\n snap,\n toWorld,\n snapAt,\n emit,\n setView,\n setSelection,\n wallChain,\n chainReferenceAngle,\n onHoverChange,\n measureHit,\n ],\n );\n\n const finishGesture = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>, cancelled: boolean) => {\n pointersRef.current.delete(event.pointerId);\n const gesture = gestureRef.current;\n gestureRef.current = { kind: 'none' };\n const moved = pressRef.current?.moved ?? false;\n pressRef.current = null;\n setSnapHint(null);\n\n if (gesture.kind === 'none' || gesture.kind === 'pan' || gesture.kind === 'pinch') return;\n\n if (gesture.kind === 'marquee') {\n // The marquee never touches the plan, so it has no gesture snapshot to unwind.\n if (cancelled || !moved) {\n if (cancelled) setSelection([...gesture.base]);\n return;\n }\n const minX = Math.min(gesture.start.x, gesture.current.x);\n const maxX = Math.max(gesture.start.x, gesture.current.x);\n const minY = Math.min(gesture.start.y, gesture.current.y);\n const maxY = Math.max(gesture.start.y, gesture.current.y);\n\n // Corners only. A room is not a primitive you can enclose, it is whatever its\n // corners form, and selecting the corners is what makes a drag move it. Walls and\n // rooms light up on their own wherever all of their corners are in the set.\n const caught: PlanSelection[] = [];\n for (const node of Object.values(floor.nodes)) {\n if (node.x >= minX && node.x <= maxX && node.y >= minY && node.y <= maxY) {\n caught.push({ type: 'node', id: node.id });\n }\n }\n\n if (gesture.additive) {\n const existing = new Set(gesture.base.map((e) => `${e.type}:${e.id}`));\n setSelection([...gesture.base, ...caught.filter((e) => !existing.has(`${e.type}:${e.id}`))]);\n } else {\n setSelection(caught);\n }\n return;\n }\n\n if (cancelled) {\n const restored = history.cancelGesture();\n if (restored) onChange?.(restored, { reason: 'external', committed: false, targetIds: [] });\n return;\n }\n\n if (gesture.kind === 'measureEnd' || gesture.kind === 'measureMove') return;\n\n if (gesture.kind === 'measure') {\n // A measurement stays on the canvas after the drag, so it can be read while\n // drawing against it. A tap that went nowhere leaves nothing behind.\n const span = Math.hypot(gesture.current.x - gesture.start.x, gesture.current.y - gesture.start.y);\n if (moved && span > EPS_MEASURE) {\n measureSeqRef.current += 1;\n setMeasurements((list) => [...list, { id: measureSeqRef.current, a: gesture.start, b: gesture.current }]);\n }\n return;\n }\n\n if (gesture.kind === 'rect') {\n if (!moved) {\n history.cancelGesture();\n return;\n }\n const edit = addRect(\n floor,\n gesture.start,\n gesture.current,\n { thickness: wallThickness, type: WallType.Interior, height: wallHeight },\n worldTolerances(hitPx, view.zoom).node,\n );\n if (edit.result.wallIds.length === 0) {\n history.cancelGesture();\n return;\n }\n // One entry for the whole gesture, not one per pointermove.\n const pushedRect = history.endGesture();\n commitEdit(edit.floor, 'draw-rect', edit.result.wallIds, { push: !pushedRect });\n return;\n }\n\n if (!moved) {\n history.cancelGesture();\n // A Ctrl press on a wall that never became a detach drag was a selection toggle.\n if (gesture.kind === 'wall' && gesture.toggle) {\n toggleSelection({ type: 'wall', id: gesture.wallId });\n }\n return;\n }\n\n if (gesture.kind === 'node') {\n // Dropping a node onto another welds them.\n const node = floor.nodes[gesture.nodeId];\n if (node) {\n const tolerance = worldTolerances(hitPx, view.zoom).node;\n const target = Object.values(floor.nodes).find(\n (other) => other.id !== gesture.nodeId && Math.hypot(other.x - node.x, other.y - node.y) <= tolerance,\n );\n if (target) {\n const pushedWeld = history.endGesture();\n commitEdit(mergeNodes(floor, target.id, gesture.nodeId).floor, 'move-node', [target.id], {\n push: !pushedWeld,\n });\n setSelection({ type: 'node', id: target.id });\n return;\n }\n }\n }\n\n const pushedDrag = history.endGesture();\n const reason: RoomDrawerChangeReason =\n gesture.kind === 'wall'\n ? 'move-wall'\n : gesture.kind === 'opening'\n ? 'move-opening'\n : gesture.kind === 'room'\n ? 'move-room'\n : 'move-node';\n commitEdit(floor, reason, [], { push: !pushedDrag });\n },\n [floor, history, hitPx, view.zoom, wallThickness, wallHeight, commitEdit, setSelection, toggleSelection, onChange],\n );\n\n // Wheel zoom needs a non-passive listener: React's synthetic wheel handler is passive\n // and cannot preventDefault, so the page would scroll instead.\n useEffect(() => {\n const element = canvasWrapRef.current;\n if (!element || disabled) return undefined;\n function onWheel(e: WheelEvent) {\n e.preventDefault();\n const rect = element!.getBoundingClientRect();\n const factor = Math.exp(-e.deltaY * 0.002);\n setView(\n (() => {\n const current = viewRef.current;\n const nextZoom = Math.max(minZoom, Math.min(maxZoom, current.zoom * factor));\n const world = clientToWorld(e.clientX, e.clientY, rect, current);\n return {\n x: world.x - (e.clientX - rect.left) / nextZoom,\n y: world.y - (e.clientY - rect.top) / nextZoom,\n zoom: nextZoom,\n };\n })(),\n );\n }\n element.addEventListener('wheel', onWheel, { passive: false });\n return () => element.removeEventListener('wheel', onWheel);\n }, [disabled, minZoom, maxZoom, setView]);\n\n const viewRef = useRef(view);\n viewRef.current = view;\n\n // ─── Keyboard ─────────────────────────────────────────────────────────────────\n\n const zoomBy = useCallback(\n (factor: number) => {\n const nextZoom = Math.max(minZoom, Math.min(maxZoom, view.zoom * factor));\n const centreWorld = {\n x: view.x + size.width / 2 / view.zoom,\n y: view.y + size.height / 2 / view.zoom,\n };\n setView({\n x: centreWorld.x - size.width / 2 / nextZoom,\n y: centreWorld.y - size.height / 2 / nextZoom,\n zoom: nextZoom,\n });\n },\n [view, size, minZoom, maxZoom, setView],\n );\n\n const undo = useCallback(() => {\n if (historyMode === 'none') return;\n const target = history.undo();\n if (!target) return;\n lastEmittedRef.current = target;\n onChange?.(target, { reason: 'undo', committed: true, targetIds: [] });\n onChangeEnd?.(target, { reason: 'undo', committed: true, targetIds: [] });\n }, [historyMode, history, onChange, onChangeEnd]);\n\n const redo = useCallback(() => {\n if (historyMode === 'none') return;\n const target = history.redo();\n if (!target) return;\n lastEmittedRef.current = target;\n onChange?.(target, { reason: 'redo', committed: true, targetIds: [] });\n onChangeEnd?.(target, { reason: 'redo', committed: true, targetIds: [] });\n }, [historyMode, history, onChange, onChangeEnd]);\n\n /** Selects every room, or every corner when the plan has no enclosed rooms yet. */\n const selectAll = useCallback(() => {\n const asRooms = rooms.filter((r) => r.roomId).map((r) => ({ type: 'room', id: r.roomId! }) as PlanSelection);\n if (asRooms.length > 0) {\n setSelection(asRooms);\n return;\n }\n setSelection(Object.keys(floor.nodes).map((id) => ({ type: 'node', id }) as PlanSelection));\n }, [rooms, floor.nodes, setSelection]);\n\n const nudge = useCallback(\n (dx: number, dy: number) => {\n if (!editable || selectedNodeIds.size === 0) return;\n const step = gridSize > 0 ? gridSize : 100;\n const amount = shiftHeldRef.current ? step * 10 : step;\n let next = floor;\n for (const id of selectedNodeIds) {\n const node = next.nodes[id];\n if (node) next = moveNode(next, id, { x: node.x + dx * amount, y: node.y + dy * amount }).floor;\n }\n commitEdit(next, 'nudge', [...selectedNodeIds]);\n },\n [editable, selectedNodeIds, floor, gridSize, commitEdit],\n );\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return;\n // The old implementation put this on `window`, so any mounted drawer swallowed\n // Delete and `+` for the whole page. Scoping it to the focused root fixes that.\n const target = event.target as HTMLElement;\n if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {\n if (event.key === 'Escape') (target as HTMLElement).blur();\n return;\n }\n\n if (event.key === 'Shift') shiftHeldRef.current = true;\n if (event.key === 'Alt') altHeldRef.current = true;\n if (event.key === ' ' && !spaceHeldRef.current) {\n spaceHeldRef.current = true;\n event.preventDefault();\n return;\n }\n\n const meta = event.metaKey || event.ctrlKey;\n if (meta && event.key.toLowerCase() === 'z') {\n event.preventDefault();\n if (event.shiftKey) redo();\n else undo();\n return;\n }\n if (meta && event.key.toLowerCase() === 'a') {\n event.preventDefault();\n if (multiSelect) selectAll();\n return;\n }\n if (meta && event.key.toLowerCase() === 'y') {\n event.preventDefault();\n redo();\n return;\n }\n\n if (event.key === 'Escape') {\n if (wallChain.length > 0) endWallChain();\n // Clears the ruler before the selection: with measurements on screen, Escape\n // most likely means \"put that away\".\n else if (measurements.length > 0) setMeasurements([]);\n else setSelection(null);\n return;\n }\n if (event.key === 'Enter' && tool === 'wall') {\n endWallChain();\n return;\n }\n if (event.key === 'Delete' || event.key === 'Backspace') {\n event.preventDefault();\n // With the ruler up, Delete belongs to the ruler and NOTHING else. It takes the\n // measurement under the pointer, and with none there it does nothing: falling\n // through to the selection would let a measuring tool quietly destroy a room that\n // was selected minutes earlier with another tool.\n if (tool === 'measure') {\n if (hoveredMeasure !== null) {\n setMeasurements((list) => list.filter((m) => m.id !== hoveredMeasure));\n setHoveredMeasure(null);\n }\n return;\n }\n deleteSelection();\n return;\n }\n if (event.key === '+' || event.key === '=') {\n zoomBy(1.25);\n return;\n }\n if (event.key === '-') {\n zoomBy(1 / 1.25);\n return;\n }\n if (event.key === '0') {\n fitToContent();\n return;\n }\n if (event.key.startsWith('Arrow')) {\n event.preventDefault();\n const map: Record<string, [number, number]> = {\n ArrowLeft: [-1, 0],\n ArrowRight: [1, 0],\n ArrowUp: [0, -1],\n ArrowDown: [0, 1],\n };\n const delta = map[event.key];\n if (delta) nudge(delta[0], delta[1]);\n return;\n }\n\n const lower = event.key.toLowerCase();\n if (lower === 's') {\n setSnapEnabled((on) => !on);\n return;\n }\n if (lower === 'l') {\n setAngleLockEnabled((on) => !on);\n return;\n }\n if (lower === 'g') {\n setGridVisible((on) => !on);\n return;\n }\n\n const toolKey = TOOL_KEYS[event.key.toLowerCase()];\n if (toolKey && editable && tools.includes(toolKey)) {\n setTool(toolKey);\n endWallChain();\n }\n },\n [\n disabled,\n editable,\n tools,\n tool,\n wallChain,\n measurements.length,\n hoveredMeasure,\n redo,\n undo,\n setSelection,\n endWallChain,\n deleteSelection,\n zoomBy,\n fitToContent,\n nudge,\n setTool,\n multiSelect,\n selectAll,\n ],\n );\n\n // ─── Files ────────────────────────────────────────────────────────────────────\n\n /**\n * Swaps the whole plan out as one history entry. Opening a file and the `setPlan`\n * handle share it, so a file and a programmatic replacement behave identically, and\n * both stay undoable: opening a file over an hour's work is exactly the mistake that\n * most needs taking back.\n */\n const replacePlan = useCallback(\n (next: FloorPlan, reason: RoomDrawerChangeReason, options?: { resetHistory?: boolean }) => {\n lastEmittedRef.current = next;\n if (options?.resetHistory) {\n history.clearHistory();\n history.replace(next);\n } else {\n history.commit(next, reason);\n }\n const meta: RoomDrawerChangeMeta = { reason, committed: true, targetIds: [] };\n onChange?.(next, meta);\n onChangeEnd?.(next, meta);\n },\n [history, onChange, onChangeEnd],\n );\n\n // ─── Floors ───────────────────────────────────────────────────────────────────\n\n /**\n * Floor operations go through `emit` like any other edit, so each is one undo step and\n * reaches a controlled parent through `onChange`. Adding or copying a storey also\n * switches to it: nobody adds a floor in order to keep looking at the one below.\n */\n const addFloorAt = useCallback(\n (options: { label?: string } = {}) => {\n const { plan: next, floor: added } = addFloor(plan, {\n ...options,\n index: floorIndex(plan, floor.id) + 1,\n });\n replacePlan(next, 'add-floor');\n selectFloor(added.id);\n return added.id;\n },\n [plan, floor.id, replacePlan, selectFloor],\n );\n\n const duplicateActiveFloor = useCallback(() => {\n const { plan: next, floor: copy } = duplicateFloor(plan, floor.id);\n if (!copy) return null;\n replacePlan(next, 'duplicate-floor');\n selectFloor(copy.id);\n return copy.id;\n }, [plan, floor.id, replacePlan, selectFloor]);\n\n const removeFloorById = useCallback(\n (id?: string) => {\n const target = id ?? floor.id;\n const next = removeFloor(plan, target);\n if (next === plan) return; // the last floor, which stays\n replacePlan(next, 'remove-floor');\n if (target === floor.id) {\n // Step down to the storey below where there is one, so the view lands somewhere\n // adjacent rather than at the bottom of the building.\n const was = floorIndex(plan, target);\n selectFloor(next.floors[Math.max(0, was - 1)].id);\n }\n },\n [plan, floor.id, replacePlan, selectFloor],\n );\n\n const moveFloorTo = useCallback(\n (id: string, toIndex: number) => {\n const next = moveFloor(plan, id, toIndex);\n if (next !== plan) replacePlan(next, 'move-floor');\n },\n [plan, replacePlan],\n );\n\n const setFloorHeightById = useCallback(\n (id: string, height: number) => {\n const next = setFloorHeight(plan, id, height);\n if (next !== plan) replacePlan(next, 'edit-floor');\n },\n [plan, replacePlan],\n );\n\n const renameFloorById = useCallback(\n (id: string, label: string) => {\n const next = renameFloor(plan, id, label);\n if (next !== plan) replacePlan(next, 'rename-floor');\n },\n [plan, replacePlan],\n );\n\n const downloadPlan = useCallback(\n (name?: string) => {\n const chosen = (name ?? fileName ?? plan.name ?? '').trim() || 'floor-plan';\n const url = URL.createObjectURL(new Blob([planToJson(plan)], { type: 'application/json' }));\n const link = document.createElement('a');\n link.href = url;\n link.download = chosen.endsWith('.json') ? chosen : `${chosen}.json`;\n link.click();\n // Revoking in the same tick cancels the download in some browsers, so let the task\n // queue drain first.\n setTimeout(() => URL.revokeObjectURL(url), 0);\n },\n [fileName, plan],\n );\n\n const loadJson = useCallback(\n (text: string): PlanParseResult => {\n const result = planFromJson(text);\n if (result.plan) replacePlan(result.plan, 'import');\n // Silence is the report for a clean file: the canvas changing says it worked.\n setImportReport(result.plan && result.issues.length === 0 ? null : result.issues);\n onImport?.(result);\n return result;\n },\n [onImport, replacePlan],\n );\n\n const openFile = useCallback(\n (file: File) => {\n file\n .text()\n .then((text) => loadJson(text))\n .catch((error: Error) => {\n const issues = [`Could not read that file: ${error.message}`];\n setImportReport(issues);\n onImport?.({ plan: null, issues });\n });\n },\n [loadJson, onImport],\n );\n\n const clearMeasurements = useCallback(() => setMeasurements([]), []);\n\n const handleKeyUp = useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Shift') shiftHeldRef.current = false;\n if (event.key === 'Alt') altHeldRef.current = false;\n if (event.key === ' ') spaceHeldRef.current = false;\n }, []);\n\n // ─── Imperative handle ────────────────────────────────────────────────────────\n\n useImperativeHandle(\n ref,\n () => ({\n getPlan: () => plan,\n setPlan: (next, options) => replacePlan(next, 'import', options),\n clear: () => replacePlan(emptyFloorPlan(), 'clear'),\n getFloorId: () => floor.id,\n setFloorId: selectFloor,\n addFloor: (options) => addFloorAt(options),\n duplicateFloor: duplicateActiveFloor,\n removeFloor: removeFloorById,\n renameFloor: renameFloorById,\n moveFloor: moveFloorTo,\n setFloorHeight: setFloorHeightById,\n clearMeasurements,\n download: downloadPlan,\n loadJson,\n undo,\n redo,\n canUndo: () => historyMode !== 'none' && history.canUndo,\n canRedo: () => historyMode !== 'none' && history.canRedo,\n clearHistory: history.clearHistory,\n getSelection: () => [...selectionRef.current],\n select: setSelection,\n addToSelection: (next) => {\n const additions = Array.isArray(next) ? next : [next as PlanSelection];\n const existing = new Set(selectionRef.current.map((e) => `${e.type}:${e.id}`));\n setSelection([...selectionRef.current, ...additions.filter((e) => !existing.has(`${e.type}:${e.id}`))]);\n },\n toggleSelection,\n selectAll,\n deleteSelection,\n getView: () => view,\n setView,\n zoomBy,\n fitToContent,\n toWorld,\n toScreen,\n getSvgElement: () => svgRef.current,\n }),\n [\n plan,\n history,\n replacePlan,\n floor,\n selectFloor,\n addFloorAt,\n duplicateActiveFloor,\n removeFloorById,\n renameFloorById,\n moveFloorTo,\n setFloorHeightById,\n clearMeasurements,\n downloadPlan,\n loadJson,\n undo,\n redo,\n historyMode,\n setSelection,\n toggleSelection,\n selectAll,\n deleteSelection,\n view,\n setView,\n zoomBy,\n fitToContent,\n toWorld,\n toScreen,\n ],\n );\n\n // ─── Overlay (screen space) ───────────────────────────────────────────────────\n\n const gesture = gestureRef.current;\n const overlay = useMemo(() => {\n if (!editable) return null;\n const parts: ReactElement[] = [];\n\n // The ruler: measurements already taken, plus the one in flight. Drawn in screen\n // space, so ticks and figures stay the same size at any zoom.\n const rulerLines = [\n ...measurements,\n ...(gesture.kind === 'measure' ? [{ id: -1, a: gesture.start, b: gesture.current }] : []),\n ];\n for (const line of rulerLines) {\n const a = toScreen(line.a);\n const b = toScreen(line.b);\n const length = Math.hypot(line.b.x - line.a.x, line.b.y - line.a.y);\n if (length < EPS_MEASURE) continue;\n const angle = Math.atan2(line.b.y - line.a.y, line.b.x - line.a.x);\n // End ticks, perpendicular to the run, so a measurement reads as a dimension\n // rather than as something that was drawn.\n const nx = -Math.sin(angle) * 5;\n const ny = Math.cos(angle) * 5;\n const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };\n parts.push(\n <g\n key={`measure-${line.id}`}\n className={cx(\n styles.roomDrawerMeasureLine,\n hoveredMeasure === line.id && styles.roomDrawerMeasureLineHovered,\n classNames?.measureLine,\n )}\n >\n <line x1={a.x} y1={a.y} x2={b.x} y2={b.y} />\n <line x1={a.x - nx} y1={a.y - ny} x2={a.x + nx} y2={a.y + ny} />\n <line x1={b.x - nx} y1={b.y - ny} x2={b.x + nx} y2={b.y + ny} />\n </g>,\n );\n // Grab handles, only while the ruler is the active tool: under any other tool a\n // measurement is inert scenery and its handles would compete with the plan's own.\n if (tool === 'measure' && line.id >= 0) {\n for (const [end, p] of [\n ['a', a],\n ['b', b],\n ] as const) {\n parts.push(\n <circle\n key={`measure-grip-${line.id}-${end}`}\n className={styles.roomDrawerMeasureGrip}\n cx={p.x}\n cy={p.y}\n r={4}\n />,\n );\n }\n }\n parts.push(\n <text\n key={`measure-label-${line.id}`}\n className={cx(styles.roomDrawerMeasure, classNames?.measureLine)}\n x={mid.x}\n y={mid.y - 8}\n textAnchor='middle'\n >\n {`${formatLength(length)} · ${formatAngle(angle)}`}\n </text>,\n );\n }\n\n // Snap guides: the construction lines explaining what the pointer locked onto.\n // resolveSnap has always produced these; without drawing them, snapping reads as the\n // app quietly moving the pointer somewhere the user did not ask for.\n if (showSnapGuides && snapHint) {\n snapHint.guides.forEach((guide, i) => {\n const a = toScreen(guide.a);\n const b = toScreen(guide.b);\n parts.push(\n <line\n key={`guide-${i}`}\n className={cx(styles.roomDrawerGuide, guide.kind === SnapKind.Angle && styles.roomDrawerGuideAngle)}\n x1={a.x}\n y1={a.y}\n x2={b.x}\n y2={b.y}\n />,\n );\n });\n\n // A marker on the point itself, so a node or wall snap is visible too, not just\n // the constraint-style snaps that come with a line.\n if (snapHint.kind !== SnapKind.None && snapHint.kind !== SnapKind.Grid) {\n const p = toScreen(snapHint.point);\n parts.push(\n <rect\n key='snap-marker'\n className={styles.roomDrawerSnapMarker}\n x={p.x - 4}\n y={p.y - 4}\n width={8}\n height={8}\n />,\n );\n }\n }\n\n // Rect preview.\n if (gesture.kind === 'rect') {\n const a = toScreen(gesture.start);\n const b = toScreen(gesture.current);\n const left = Math.min(a.x, b.x);\n const top = Math.min(a.y, b.y);\n const right = Math.max(a.x, b.x);\n const bottom = Math.max(a.y, b.y);\n parts.push(\n <rect\n key='rect-preview'\n className={styles.roomDrawerPreview}\n x={left}\n y={top}\n width={right - left}\n height={bottom - top}\n />,\n );\n\n // Both sides and the area, each sitting on what it measures. The figures are the\n // wall CENTRELINES, which is also what the finished room reports its area from, so\n // the number does not change the moment the drag ends.\n const width = Math.abs(gesture.current.x - gesture.start.x);\n const height = Math.abs(gesture.current.y - gesture.start.y);\n if (showMeasurements && (width > 0 || height > 0)) {\n parts.push(\n <text\n key='rect-width'\n className={styles.roomDrawerMeasure}\n x={(left + right) / 2}\n y={top - 8}\n textAnchor='middle'\n >\n {formatLength(width)}\n </text>,\n <text\n key='rect-height'\n className={styles.roomDrawerMeasure}\n x={left - 8}\n y={(top + bottom) / 2}\n textAnchor='end'\n dominantBaseline='central'\n >\n {formatLength(height)}\n </text>,\n );\n if (width > 0 && height > 0) {\n parts.push(\n <text\n key='rect-area'\n className={cx(styles.roomDrawerMeasure, styles.roomDrawerMeasureSecondary)}\n x={(left + right) / 2}\n y={(top + bottom) / 2}\n textAnchor='middle'\n dominantBaseline='central'\n >\n {formatArea(width * height)}\n </text>,\n );\n }\n }\n }\n\n // Wall chain preview.\n if (tool === 'wall' && wallChain.length > 0) {\n const points = [...wallChain, cursor].filter(Boolean) as PlanPoint[];\n const screen = points.map(toScreen);\n parts.push(\n <polyline\n key='chain'\n className={styles.roomDrawerPreview}\n points={screen.map((p) => `${p.x},${p.y}`).join(' ')}\n fill='none'\n />,\n );\n screen.forEach((p, i) => {\n parts.push(<circle key={`chain-dot-${i}`} className={styles.roomDrawerHandle} cx={p.x} cy={p.y} r={4} />);\n });\n if (showMeasurements && cursor && wallChain.length > 0) {\n const from = wallChain[wallChain.length - 1];\n const length = Math.hypot(cursor.x - from.x, cursor.y - from.y);\n const mid = toScreen({ x: (from.x + cursor.x) / 2, y: (from.y + cursor.y) / 2 });\n const locked = snapHint?.kind === SnapKind.Angle;\n parts.push(\n <text\n key='chain-len'\n className={cx(styles.roomDrawerMeasure, locked && styles.roomDrawerMeasureLocked)}\n x={mid.x}\n y={mid.y - 8}\n textAnchor='middle'\n >\n {`${formatLength(length)} · ${formatAngle(\n // Relative to the previous segment when the lock is measuring from it, since\n // \"90° to the last wall\" is the number the user is actually aiming for.\n snapHint?.angleRelativeTo === 'reference' && chainReferenceAngle !== undefined\n ? Math.atan2(cursor.y - from.y, cursor.x - from.x) - chainReferenceAngle\n : Math.atan2(cursor.y - from.y, cursor.x - from.x),\n )}${snapHint?.angleRelativeTo === 'reference' ? ' rel' : ''}`}\n </text>,\n );\n }\n }\n\n // A handle on every corner the selection implies, so a multi-selection shows exactly\n // what a drag would move. Corners picked explicitly are filled; ones that came along\n // because their wall or room was selected are outlined.\n const explicitNodes = new Set(selection.filter((e) => e.type === 'node').map((e) => e.id));\n for (const id of selectedNodeIds) {\n const node = floor.nodes[id];\n if (!node) continue;\n const p = toScreen(node);\n parts.push(\n <circle\n key={`handle-${id}`}\n className={cx(styles.roomDrawerHandle, explicitNodes.has(id) && styles.roomDrawerHandleActive)}\n cx={p.x}\n cy={p.y}\n r={5}\n />,\n );\n }\n\n // Marquee rectangle.\n if (gesture.kind === 'marquee' && pressRef.current?.moved) {\n const a = toScreen(gesture.start);\n const b = toScreen(gesture.current);\n parts.push(\n <rect\n key='marquee'\n className={styles.roomDrawerMarquee}\n x={Math.min(a.x, b.x)}\n y={Math.min(a.y, b.y)}\n width={Math.abs(b.x - a.x)}\n height={Math.abs(b.y - a.y)}\n />,\n );\n }\n\n // The selected wall's \"add a corner\" handle. Hollow and smaller than a real corner,\n // so it reads as something that is not there yet rather than as an existing node.\n if (selectedWallMidpoint && gesture.kind !== 'node') {\n const p = toScreen(selectedWallMidpoint);\n parts.push(<circle key='midpoint' className={styles.roomDrawerMidHandle} cx={p.x} cy={p.y} r={4} />);\n }\n\n // Dragging a whole ROOM is a rigid translation of every node in its face, so its own\n // walls and area are all frozen; the only things changing are how far it has moved\n // and any wall straddling its boundary. Reporting its own dimensions here would put\n // five stuck numbers on screen.\n if (showMeasurements && gesture.kind === 'room' && gesture.moved) {\n const moving = new Set(gesture.nodeIds);\n\n const sampleId = gesture.nodeIds.find((id) => floor.nodes[id] && gesture.orig[id]);\n if (sampleId) {\n const now = floor.nodes[sampleId];\n const was = gesture.orig[sampleId];\n const dx = now.x - was.x;\n const dy = now.y - was.y;\n const room = single?.type === 'room' ? rooms.find((r) => r.roomId === single.id) : undefined;\n const at = toScreen(room ? room.anchor : now);\n parts.push(\n <text\n key='room-offset'\n className={cx(styles.roomDrawerMeasure, styles.roomDrawerMeasureOffset)}\n x={at.x}\n y={at.y - 26}\n textAnchor='middle'\n >\n {`${formatLength(Math.hypot(dx, dy))} · ${formatAngle(Math.atan2(dy, dx))}`}\n </text>,\n );\n }\n\n // A wall with both ends moving travels with the room and keeps its shape; one with\n // neither is untouched. Only the ones bridging the two are being stretched.\n for (const wall of Object.values(floor.walls)) {\n if (moving.has(wall.a) === moving.has(wall.b)) continue;\n const a = floor.nodes[wall.a];\n const b = floor.nodes[wall.b];\n if (!a || !b) continue;\n const tail = moving.has(wall.a) ? a : b;\n const head = moving.has(wall.a) ? b : a;\n const mid = toScreen({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });\n parts.push(\n <text\n key={`room-stretch-${wall.id}`}\n className={cx(styles.roomDrawerMeasure, styles.roomDrawerMeasureSecondary)}\n x={mid.x}\n y={mid.y - 8}\n textAnchor='middle'\n >\n {`${formatLength(Math.hypot(head.x - tail.x, head.y - tail.y))} · ${formatAngle(\n Math.atan2(head.y - tail.y, head.x - tail.x),\n )}`}\n </text>,\n );\n }\n }\n\n // A CORNER reports every wall meeting it, since reshaping it changes all of them. A\n // WALL reports itself plus the walls running off its ends: dragging a wall moves both\n // its endpoints, so its own length is the one number that cannot change. Single\n // selections only; a multi-selection moves rigidly and uses the offset readout above.\n if (showMeasurements && (single?.type === 'node' || single?.type === 'wall')) {\n const measured: Array<{ wall: (typeof floor.walls)[string]; from: string; primary: boolean }> = [];\n const seen = new Set<string>();\n const add = (wallId: string, from: string, primary: boolean) => {\n if (seen.has(wallId)) return;\n const wall = floor.walls[wallId];\n if (!wall) return;\n seen.add(wallId);\n measured.push({ wall, from, primary });\n };\n\n if (single.type === 'node') {\n for (const wall of Object.values(floor.walls)) {\n if (wall.a === single.id || wall.b === single.id) add(wall.id, single.id, true);\n }\n } else {\n const selected = floor.walls[single.id];\n if (selected) {\n add(selected.id, selected.a, true);\n // Everything attached at either end. These are what actually resize as the\n // selected wall is pushed sideways.\n for (const wall of Object.values(floor.walls)) {\n if (wall.id === selected.id) continue;\n for (const end of [selected.a, selected.b]) {\n if (wall.a === end || wall.b === end) add(wall.id, end, false);\n }\n }\n }\n }\n\n const locked = snapHint?.kind === SnapKind.Angle && gesture.kind !== 'none';\n for (const { wall, from, primary } of measured) {\n const a = floor.nodes[wall.a];\n const b = floor.nodes[wall.b];\n if (!a || !b) continue;\n // Measured outward from the shared end, so the angle describes the wall as it\n // leaves that point rather than depending on which end it was stored from.\n const tail = wall.a === from ? a : b;\n const head = wall.a === from ? b : a;\n const mid = toScreen({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });\n parts.push(\n <text\n key={`measure-${wall.id}`}\n className={cx(\n styles.roomDrawerMeasure,\n !primary && styles.roomDrawerMeasureSecondary,\n primary && locked && styles.roomDrawerMeasureLocked,\n )}\n x={mid.x}\n y={mid.y - 8}\n textAnchor='middle'\n >\n {`${formatLength(Math.hypot(head.x - tail.x, head.y - tail.y))} · ${formatAngle(\n Math.atan2(head.y - tail.y, head.x - tail.x),\n )}`}\n </text>,\n );\n }\n }\n\n return <>{parts}</>;\n }, [\n editable,\n gesture,\n tool,\n wallChain,\n cursor,\n selection,\n floor,\n rooms,\n single,\n selectedNodeIds,\n selectedWallMidpoint,\n showMeasurements,\n showSnapGuides,\n snapHint,\n chainReferenceAngle,\n formatLength,\n formatArea,\n measurements,\n hoveredMeasure,\n classNames?.measureLine,\n toScreen,\n ]);\n\n // ─── Chrome ───────────────────────────────────────────────────────────────────\n\n const TOOL_META: Record<RoomDrawerTool, { icon: typeof faSquare; label: string; key: string }> = {\n select: { icon: faArrowPointer, label: labels?.select ?? 'Select and edit', key: 'V' },\n wall: { icon: faMinus, label: labels?.wall ?? 'Draw walls', key: 'W' },\n rect: { icon: faSquare, label: labels?.rect ?? 'Draw a room', key: 'R' },\n door: { icon: faDoorOpen, label: labels?.door ?? 'Add a door', key: 'D' },\n window: { icon: faWindowMaximize, label: labels?.window ?? 'Add a window', key: 'N' },\n archway: { icon: faExpand, label: labels?.archway ?? 'Add an archway', key: 'A' },\n measure: { icon: faRulerCombined, label: labels?.measure ?? 'Measure', key: 'M' },\n };\n\n const hint = (() => {\n if (!editable) return '';\n if (tool === 'wall') {\n return (\n labels?.hintWall ??\n 'Click to place corners · Shift for a fixed angle, L to latch it · Click the first corner or press Enter to close'\n );\n }\n if (tool === 'rect') return labels?.hintRect ?? 'Drag to draw a room';\n if (tool === 'measure') {\n return (\n labels?.hintMeasure ??\n 'Drag to measure · Drag an end or the line to adjust it · Delete removes one, Escape clears them'\n );\n }\n if (OPENING_TOOL_KIND[tool]) return labels?.hintOpening ?? 'Click a wall to cut an opening into it';\n return (\n labels?.hintSelect ??\n (multiSelect\n ? 'Drag a corner to reshape · Drag a wall to push it, and the room next door moves with it · Ctrl-click or drag a box to select several · Delete to remove'\n : 'Drag a corner to reshape · Drag a wall to push it, and the room next door moves with it · Drag the dot on a selected wall to add a corner · Delete to remove')\n );\n })();\n\n /**\n * What the inspector calls the current selection. It is the only thing in the bar for a\n * corner, which has nothing to edit that dragging it does not already do.\n */\n const selectionNoun = (() => {\n if (selection.length === 0) return labels?.selectedFloor ?? 'Floor';\n if (selection.length > 1) {\n return (labels?.selectedCount ?? '{count} selected').replace('{count}', String(selection.length));\n }\n if (selectedOpening) return labels?.selectedOpening ?? OPENING_KIND_NOUN[selectedOpening.kind];\n if (selectedWall) return labels?.selectedWall ?? 'Wall';\n if (single?.type === 'room') return labels?.selectedRoom ?? 'Room';\n return labels?.selectedCorner ?? 'Corner';\n })();\n\n const visibleTools = editable ? tools : [];\n\n return (\n <div\n {...rest}\n ref={rootRef}\n className={cx(styles.roomDrawer, disabled && styles.roomDrawerDisabled, className, classNames?.root)}\n style={{ ...style, ...slotStyles?.root }}\n role='application'\n aria-label={ariaLabel ?? 'Floor plan editor'}\n tabIndex={disabled ? -1 : 0}\n onKeyDown={handleKeyDown}\n onKeyUp={handleKeyUp}\n >\n {showToolbar && (\n <div\n className={cx(styles.roomDrawerToolbar, classNames?.toolbar)}\n style={slotStyles?.toolbar}\n role='toolbar'\n aria-label='Drawing tools'\n >\n {visibleTools.map((t) => {\n const meta = TOOL_META[t];\n return (\n <button\n key={t}\n type='button'\n className={cx(\n styles.roomDrawerToolButton,\n t === tool && styles.roomDrawerToolButtonActive,\n classNames?.toolButton,\n )}\n style={slotStyles?.toolButton}\n title={`${meta.label} (${meta.key})`}\n aria-label={meta.label}\n aria-pressed={t === tool}\n onClick={() => {\n setTool(t);\n endWallChain();\n }}\n >\n {renderIcon(icons?.[t] ?? meta.icon, {\n className: cx(styles.roomDrawerToolIcon, classNames?.toolIcon),\n style: slotStyles?.toolIcon,\n })}\n </button>\n );\n })}\n\n {visibleTools.length > 0 && <span className={styles.roomDrawerToolbarSeparator} />}\n\n {editable && (\n <>\n {(\n [\n {\n on: snapEnabled,\n toggle: () => setSnapEnabled((v) => !v),\n icon: icons?.snap ?? faMagnet,\n label: labels?.snap ?? 'Snap to grid and geometry',\n key: 'S',\n },\n {\n on: angleLockEnabled,\n toggle: () => setAngleLockEnabled((v) => !v),\n icon: icons?.angleLock ?? faDraftingCompass,\n label: labels?.angleLock ?? `Lock to ${constrainAngle}° angles`,\n key: 'L',\n },\n {\n on: gridVisible,\n toggle: () => setGridVisible((v) => !v),\n icon: icons?.grid ?? faBorderAll,\n label: labels?.grid ?? 'Show grid',\n key: 'G',\n },\n // Only worth offering when there is a storey below to trace.\n ...(floorBelow(plan, floor.id)\n ? [\n {\n on: underlayOn,\n toggle: () => setUnderlayOn((v) => !v),\n icon: icons?.underlay ?? faLayerGroup,\n label: labels?.underlay ?? 'Show the floor below',\n key: 'B',\n },\n ]\n : []),\n ] as const\n ).map((toggle) => (\n <button\n key={toggle.key}\n type='button'\n className={cx(\n styles.roomDrawerToolButton,\n toggle.on && styles.roomDrawerToolButtonOn,\n classNames?.toolButton,\n )}\n style={slotStyles?.toolButton}\n title={`${toggle.label} (${toggle.key})`}\n aria-label={toggle.label}\n aria-pressed={toggle.on}\n onClick={toggle.toggle}\n >\n {renderIcon(toggle.icon, {\n className: cx(styles.roomDrawerToolIcon, classNames?.toolIcon),\n style: slotStyles?.toolIcon,\n })}\n </button>\n ))}\n <span className={styles.roomDrawerToolbarSeparator} />\n </>\n )}\n\n {showHistoryControls && historyMode !== 'none' && editable && (\n <>\n <button\n type='button'\n className={cx(styles.roomDrawerViewButton, classNames?.viewButton)}\n style={slotStyles?.viewButton}\n title={labels?.undo ?? 'Undo'}\n aria-label={labels?.undo ?? 'Undo'}\n disabled={!history.canUndo}\n onClick={undo}\n >\n {renderIcon(icons?.undo ?? faArrowRotateLeft)}\n </button>\n <button\n type='button'\n className={cx(styles.roomDrawerViewButton, classNames?.viewButton)}\n style={slotStyles?.viewButton}\n title={labels?.redo ?? 'Redo'}\n aria-label={labels?.redo ?? 'Redo'}\n disabled={!history.canRedo}\n onClick={redo}\n >\n {renderIcon(icons?.redo ?? faArrowRotateRight)}\n </button>\n <span className={styles.roomDrawerToolbarSeparator} />\n </>\n )}\n\n {showFileControls && (\n <>\n <button\n type='button'\n className={cx(styles.roomDrawerViewButton, classNames?.viewButton)}\n style={slotStyles?.viewButton}\n title={labels?.download ?? 'Download plan'}\n aria-label={labels?.download ?? 'Download plan'}\n onClick={() => downloadPlan()}\n >\n {renderIcon(icons?.download ?? faDownload)}\n </button>\n {editable && (\n <button\n type='button'\n className={cx(styles.roomDrawerViewButton, classNames?.viewButton)}\n style={slotStyles?.viewButton}\n title={labels?.upload ?? 'Open a plan file'}\n aria-label={labels?.upload ?? 'Open a plan file'}\n onClick={() => fileInputRef.current?.click()}\n >\n {renderIcon(icons?.upload ?? faUpload)}\n </button>\n )}\n <span className={styles.roomDrawerToolbarSeparator} />\n </>\n )}\n\n <div className={cx(styles.roomDrawerViewControls, classNames?.viewControls)} style={slotStyles?.viewControls}>\n <button\n type='button'\n className={cx(styles.roomDrawerViewButton, classNames?.viewButton)}\n style={slotStyles?.viewButton}\n title={labels?.zoomOut ?? 'Zoom out'}\n aria-label={labels?.zoomOut ?? 'Zoom out'}\n onClick={() => zoomBy(1 / 1.25)}\n >\n {renderIcon(icons?.zoomOut ?? faMagnifyingGlassMinus)}\n </button>\n <button\n type='button'\n className={cx(styles.roomDrawerZoomLabel, classNames?.zoomLabel)}\n style={slotStyles?.zoomLabel}\n title={labels?.fit ?? 'Fit to content'}\n onClick={() => fitToContent()}\n >\n {Math.round(view.zoom * 1000) / 10}%\n </button>\n <button\n type='button'\n className={cx(styles.roomDrawerViewButton, classNames?.viewButton)}\n style={slotStyles?.viewButton}\n title={labels?.zoomIn ?? 'Zoom in'}\n aria-label={labels?.zoomIn ?? 'Zoom in'}\n onClick={() => zoomBy(1.25)}\n >\n {renderIcon(icons?.zoomIn ?? faMagnifyingGlassPlus)}\n </button>\n </div>\n </div>\n )}\n\n {showFloorBar && plan.floors.length > 0 && (\n <div\n className={cx(styles.roomDrawerFloorBar, classNames?.floorBar)}\n style={slotStyles?.floorBar}\n role='tablist'\n aria-label={labels?.selectedFloor ?? 'Floor'}\n >\n {/* Top storey first, so the strip reads the way a building does in section\n rather than the way the array happens to be ordered. */}\n {[...plan.floors].reverse().map((f) => (\n <button\n key={f.id}\n type='button'\n role='tab'\n className={cx(\n styles.roomDrawerFloorTab,\n f.id === floor.id && styles.roomDrawerFloorTabActive,\n classNames?.floorTab,\n )}\n style={slotStyles?.floorTab}\n aria-selected={f.id === floor.id}\n onClick={() => selectFloor(f.id)}\n >\n {f.label}\n </button>\n ))}\n {editable && (\n <button\n type='button'\n className={cx(styles.roomDrawerFloorTab, styles.roomDrawerFloorTabAdd)}\n title={labels?.addFloor ?? 'Add a floor above'}\n aria-label={labels?.addFloor ?? 'Add a floor above'}\n onClick={() => addFloorAt()}\n >\n {renderIcon(icons?.addFloor ?? faPlus)}\n </button>\n )}\n </div>\n )}\n\n <div\n ref={canvasWrapRef}\n className={cx(\n styles.roomDrawerCanvasWrapper,\n editable && tool !== 'select' && styles.roomDrawerCanvasWrapperDrawing,\n classNames?.canvasWrapper,\n )}\n style={{ height: canvasHeight, ...slotStyles?.canvasWrapper }}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={(e) => finishGesture(e, false)}\n onPointerCancel={(e) => finishGesture(e, true)}\n >\n <PlanCanvas\n ref={svgRef}\n floor={floor}\n underlay={underlayFloor}\n rooms={rooms}\n view={view}\n size={size}\n selection={selection}\n hovered={hovered}\n interactiveRooms={false}\n showGrid={gridVisible}\n gridSize={gridSize}\n showRoomLabels\n formatArea={formatArea}\n getRoomColor={getRoomColor}\n renderRoomLabel={renderRoomLabel}\n overlay={overlay}\n classNames={classNames}\n styles={slotStyles}\n />\n </div>\n\n {showInspector && editable && (\n <div className={cx(styles.roomDrawerInspector, classNames?.inspector)} style={slotStyles?.inspector}>\n <span\n className={cx(\n styles.roomDrawerInspectorKind,\n selection.length === 0 && styles.roomDrawerInspectorKindEmpty,\n classNames?.inspectorLabel,\n )}\n >\n {selectionNoun}\n </span>\n\n {selection.length === 0 && (\n <>\n <Input\n className={cx(styles.roomDrawerInspectorName, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n label={labels?.floorName ?? 'Floor name'}\n value={floor.label}\n onChange={(next) => renameFloorById(floor.id, next)}\n />\n <NumberInput\n className={cx(styles.roomDrawerInspectorField, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n label={labels?.floorHeight ?? 'Height (mm)'}\n title='Storey height. The floors above move to sit on top of it.'\n value={floor.height}\n decimalScale={0}\n min={MIN_FLOOR_HEIGHT}\n step={100}\n showArrows\n onChange={(next) => {\n if (next === undefined) return;\n setFloorHeightById(floor.id, next);\n }}\n />\n {/* The strip lists storeys top first, so \"up\" is a higher index. Only\n offered when there is somewhere to go. */}\n {plan.floors.length > 1 && (\n <>\n <button\n type='button'\n className={cx(styles.roomDrawerInspectorAction, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n title={labels?.moveFloorUp ?? 'Move this floor up'}\n aria-label={labels?.moveFloorUp ?? 'Move this floor up'}\n disabled={floorIndex(plan, floor.id) >= plan.floors.length - 1}\n onClick={() => moveFloorTo(floor.id, floorIndex(plan, floor.id) + 1)}\n >\n {renderIcon(icons?.moveFloorUp ?? faAngleUp)}\n </button>\n <button\n type='button'\n className={cx(styles.roomDrawerInspectorAction, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n title={labels?.moveFloorDown ?? 'Move this floor down'}\n aria-label={labels?.moveFloorDown ?? 'Move this floor down'}\n disabled={floorIndex(plan, floor.id) <= 0}\n onClick={() => moveFloorTo(floor.id, floorIndex(plan, floor.id) - 1)}\n >\n {renderIcon(icons?.moveFloorDown ?? faAngleDown)}\n </button>\n </>\n )}\n <button\n type='button'\n className={cx(styles.roomDrawerInspectorAction, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n title={labels?.duplicateFloor ?? 'Copy this floor to a new one above'}\n onClick={() => duplicateActiveFloor()}\n >\n {renderIcon(icons?.duplicateFloor ?? faClone)}\n </button>\n {plan.floors.length > 1 && (\n <ActionIcon\n className={styles.roomDrawerInspectorDelete}\n icon={icons?.delete ?? faTrash}\n title={labels?.deleteFloor ?? 'Delete this floor'}\n onClick={() => removeFloorById()}\n />\n )}\n </>\n )}\n\n {selectedRoom?.meta && (\n <>\n <Input\n className={cx(styles.roomDrawerInspectorName, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n label={labels?.roomName ?? 'Room name'}\n value={selectedRoom.meta.name}\n onChange={(next) => {\n const room = selectedRoom.meta!;\n commitEdit({ ...floor, rooms: { ...floor.rooms, [room.id]: { ...room, name: next } } }, 'edit-room', [\n room.id,\n ]);\n }}\n />\n <span className={cx(styles.roomDrawerInspectorLabel, classNames?.inspectorLabel)}>\n {formatArea(selectedRoom.area)}\n </span>\n </>\n )}\n\n {selectedWall && (\n <button\n type='button'\n className={cx(styles.roomDrawerInspectorAction, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n onClick={addCornerToSelectedWall}\n title={labels?.addCorner ?? 'Add a corner to this wall'}\n >\n {labels?.addCorner ?? 'Add corner'}\n </button>\n )}\n\n {selectedWall && (\n <NumberInput\n className={cx(styles.roomDrawerInspectorField, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n label={labels?.thickness ?? 'Thickness (mm)'}\n value={selectedWall.thickness}\n decimalScale={0}\n min={10}\n showArrows\n step={10}\n onChange={(next) => {\n if (next === undefined) return;\n commitEdit(\n {\n ...floor,\n walls: { ...floor.walls, [selectedWall.id]: { ...selectedWall, thickness: next } },\n },\n 'edit-wall',\n [selectedWall.id],\n );\n }}\n />\n )}\n\n {selectedOpening && (\n <>\n <NumberInput\n className={cx(styles.roomDrawerInspectorField, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n label={labels?.width ?? 'Width (mm)'}\n value={selectedOpening.width}\n decimalScale={0}\n min={100}\n step={50}\n showArrows\n onChange={(next) => {\n if (next === undefined) return;\n commitEdit(updateOpening(floor, selectedOpening.id, { width: next }).floor, 'edit-opening', [\n selectedOpening.id,\n ]);\n }}\n />\n <NumberInput\n className={cx(styles.roomDrawerInspectorField, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n label={labels?.sill ?? 'Sill (mm)'}\n title='How far above the floor the opening starts. Stored for a 3D view; it does not change the plan.'\n showArrows\n value={selectedOpening.sill}\n decimalScale={0}\n min={0}\n step={50}\n onChange={(next) => {\n if (next === undefined) return;\n commitEdit(updateOpening(floor, selectedOpening.id, { sill: next }).floor, 'edit-opening', [\n selectedOpening.id,\n ]);\n }}\n />\n </>\n )}\n\n {selection.length > 0 && (\n <ActionIcon\n className={styles.roomDrawerInspectorDelete}\n icon={icons?.delete ?? faTrash}\n title={labels?.deleteSelection ?? 'Delete selection'}\n onClick={deleteSelection}\n />\n )}\n </div>\n )}\n\n {showFileControls && editable && (\n <input\n ref={fileInputRef}\n type='file'\n accept='application/json,.json'\n className={styles.roomDrawerFileInput}\n tabIndex={-1}\n aria-hidden='true'\n onChange={(event) => {\n const file = event.target.files?.[0];\n // Cleared so picking the same file twice running still fires a change.\n event.target.value = '';\n if (file) openFile(file);\n }}\n />\n )}\n\n {showHint && (importReport !== null || hint) && (\n <div\n className={cx(styles.roomDrawerHint, importReport !== null && styles.roomDrawerHintReport, classNames?.hint)}\n style={slotStyles?.hint}\n role={importReport !== null ? 'status' : undefined}\n >\n {importReport === null\n ? hint\n : importReport.length === 0\n ? (labels?.importFailed ?? 'That file could not be read as a floor plan')\n : importReport.join(' · ')}\n </div>\n )}\n </div>\n );\n});\n\nexport default RoomDrawer;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"roomDrawer.mjs","names":[],"sources":["../../../../package/common/roomDrawer/roomDrawer.tsx"],"sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type PointerEvent as ReactPointerEvent,\n type ReactElement,\n} from 'react';\nimport cx from 'classnames';\nimport {\n faArrowPointer,\n faArrowRotateLeft,\n faArrowRotateRight,\n faBorderAll,\n faClone,\n faDoorOpen,\n faDownload,\n faDraftingCompass,\n faExpand,\n faMagnet,\n faMagnifyingGlassMinus,\n faMagnifyingGlassPlus,\n faAngleDown,\n faAngleUp,\n faLayerGroup,\n faMinus,\n faPlus,\n faRulerCombined,\n faSquare,\n faUpload,\n faTrash,\n faWindowMaximize,\n} from '@fortawesome/free-solid-svg-icons';\n\nimport ActionIcon from '../actionIcon';\nimport { useComponentDefaults } from '../configProvider';\nimport { useCoarsePointer } from '../hooks/useCoarsePointer';\nimport Input from '../input';\nimport NumberInput from '../numberInput';\nimport PlanCanvas from '../planCanvas';\nimport { renderIcon } from '../utils/renderIcon';\nimport {\n DEFAULT_WALL_ATTRS,\n HIT_PX,\n HIT_PX_COARSE,\n OpeningKind,\n SnapKind,\n WallType,\n addOpening,\n addRect,\n addWall,\n clientToWorld,\n commitDerivation,\n derivePlan,\n detachWall,\n dissolveNode,\n emptyFloorPlan,\n floorBounds,\n getFloor,\n hitTest,\n mergeNodes,\n MIN_WALL_LENGTH,\n addFloor,\n duplicateFloor,\n floorBelow,\n floorIndex,\n moveNode,\n MIN_FLOOR_HEIGHT,\n moveFloor,\n planFromJson,\n planToJson,\n removeFloor,\n renameFloor,\n setFloorHeight,\n removeOpening,\n removeWall,\n resolveSnap,\n selectionsEqual,\n setFloor,\n splitWall,\n updateOpening,\n weldShortWalls,\n worldTolerances,\n type FloorPlan,\n type PlanFloor,\n type PlanParseResult,\n type PlanPoint,\n type PlanSelection,\n type PlanView,\n type SnapResult,\n} from '../floorPlan';\nimport { pointToSegment } from '../floorPlan/floorPlan.geometry';\nimport { usePlanHistory } from './usePlanHistory';\nimport type {\n RoomDrawerChangeMeta,\n RoomDrawerChangeReason,\n RoomDrawerHandle,\n RoomDrawerProps,\n RoomDrawerTool,\n} from './roomDrawer.types';\nimport styles from './roomDrawer.module.pcss';\n\nconst ALL_TOOLS: RoomDrawerTool[] = ['select', 'wall', 'rect', 'door', 'window', 'archway', 'measure'];\n\nconst TOOL_KEYS: Record<string, RoomDrawerTool> = {\n v: 'select',\n w: 'wall',\n r: 'rect',\n d: 'door',\n n: 'window',\n a: 'archway',\n m: 'measure',\n};\n\nconst OPENING_TOOL_KIND: Partial<Record<RoomDrawerTool, OpeningKind>> = {\n door: OpeningKind.Door,\n window: OpeningKind.Window,\n archway: OpeningKind.Archway,\n};\n\n/** What the inspector calls each kind of opening. */\nconst OPENING_KIND_NOUN: Record<OpeningKind, string> = {\n [OpeningKind.Door]: 'Door',\n [OpeningKind.Window]: 'Window',\n [OpeningKind.Archway]: 'Archway',\n [OpeningKind.Gap]: 'Gap',\n};\n\nconst BUILT_IN_OPENING_DEFAULTS: Record<OpeningKind, { width: number; sill: number; head: number }> = {\n [OpeningKind.Door]: { width: 900, sill: 0, head: 2100 },\n [OpeningKind.Window]: { width: 1200, sill: 900, head: 2100 },\n [OpeningKind.Archway]: { width: 1200, sill: 0, head: 2100 },\n [OpeningKind.Gap]: { width: 1000, sill: 0, head: 2500 },\n};\n\n/** Below this length a ruler drag is a slip of the hand, not a measurement. */\nconst EPS_MEASURE = 1;\n\n/** Past this many pixels a press becomes a drag rather than a click. */\nconst DRAG_THRESHOLD_PX = 4;\n\n/** Stable identity for an empty selection, so memos keyed on it do not re-run. */\nconst EMPTY_SELECTION: readonly PlanSelection[] = [];\n\ntype Gesture =\n | { kind: 'none' }\n | { kind: 'pan'; startClient: PlanPoint; startView: PlanView }\n | { kind: 'pinch'; startDist: number; startView: PlanView; startMid: PlanPoint }\n /**\n * `origin`, `referenceAngle` and `excludeWallIds` are captured ONCE at gesture start.\n * Recomputing them per frame makes them follow the node being dragged, so angle lock\n * ends up measuring from a basis that rotates with the cursor, and the walls attached\n * to the corner become snap targets for the corner itself.\n */\n | {\n kind: 'node';\n nodeId: string;\n moved: boolean;\n origin?: PlanPoint;\n referenceAngle?: number;\n excludeWallIds: ReadonlySet<string>;\n }\n | {\n kind: 'wall';\n wallId: string;\n startWorld: PlanPoint;\n origA: PlanPoint;\n origB: PlanPoint;\n moved: boolean;\n /** Ctrl was held: pull the wall off its neighbours on the first real movement. */\n detach?: boolean;\n /** Which way it was pulled, once it has been. It cannot be pushed back across that line. */\n detachedSign?: number;\n /** A Ctrl press that has not become a drag yet, and so may still be a selection toggle. */\n toggle?: boolean;\n }\n | { kind: 'opening'; openingId: string; moved: boolean }\n | { kind: 'room'; nodeIds: string[]; startWorld: PlanPoint; orig: Record<string, PlanPoint>; moved: boolean }\n /**\n * The midpoint handle of a selected wall has been pressed. The split is deliberately\n * deferred until the pointer actually moves, so a stray click on the handle leaves the\n * wall exactly as it was instead of silently adding a corner.\n */\n | { kind: 'wallSplit'; wallId: string; at: PlanPoint }\n | { kind: 'rect'; start: PlanPoint; current: PlanPoint }\n | { kind: 'measure'; start: PlanPoint; current: PlanPoint }\n /** Dragging one end of a measurement already taken. */\n | { kind: 'measureEnd'; id: number; end: 'a' | 'b' }\n /** Sliding a whole measurement, both ends together. */\n | { kind: 'measureMove'; id: number; startWorld: PlanPoint; origA: PlanPoint; origB: PlanPoint }\n /** Rubber-band selection over empty canvas. `base` is what was selected before it began. */\n | {\n kind: 'marquee';\n start: PlanPoint;\n current: PlanPoint;\n additive: boolean;\n base: readonly PlanSelection[];\n };\n\nfunction defaultFormatLength(mm: number): string {\n return mm >= 1000 ? `${(mm / 1000).toFixed(2)} m` : `${Math.round(mm)} mm`;\n}\n\nfunction defaultFormatArea(mm2: number): string {\n const m2 = mm2 / 1_000_000;\n return `${m2.toFixed(m2 < 10 ? 1 : 0)} m²`;\n}\n\n/** Radians to a signed degree reading in (-180, 180], which is how a plan angle reads. */\nfunction formatAngle(radians: number): string {\n let deg = (radians * 180) / Math.PI;\n deg = ((deg % 360) + 360) % 360;\n if (deg > 180) deg -= 360;\n // -0 would render as \"-0°\".\n return `${Math.abs(deg) < 0.05 ? 0 : Math.round(deg * 10) / 10}°`;\n}\n\nconst RoomDrawer = forwardRef<RoomDrawerHandle, RoomDrawerProps>(function RoomDrawer(props, ref) {\n const {\n value,\n defaultValue,\n onChange,\n onChangeEnd,\n floorId,\n defaultFloorId,\n onFloorChange,\n showFloorBar = true,\n showUnderlay = true,\n\n tool: toolProp,\n defaultTool = 'select',\n onToolChange,\n tools = ALL_TOOLS,\n\n selection: selectionProp,\n defaultSelection,\n onSelectionChange,\n multiSelect = true,\n onHoverChange,\n\n view: viewProp,\n defaultView,\n onViewChange,\n minZoom = 0.005,\n maxZoom = 2,\n fitOnMount = true,\n\n gridSize = 100,\n showGrid = true,\n snap = true,\n angleLock = false,\n constrainAngle = 45,\n showSnapGuides = true,\n\n wallThickness = DEFAULT_WALL_ATTRS.thickness,\n wallHeight,\n openingDefaults,\n\n showMeasurements = true,\n formatLength = defaultFormatLength,\n formatArea = defaultFormatArea,\n\n showToolbar = true,\n showInspector = true,\n showHint = true,\n showHistoryControls = true,\n showFileControls = true,\n fileName,\n onImport,\n\n historyMode = 'internal',\n historyLimit = 50,\n onHistoryChange,\n\n readOnly = false,\n disabled = false,\n\n canvasHeight = '420px',\n getRoomColor,\n renderRoomLabel,\n\n labels,\n icons,\n 'aria-label': ariaLabel,\n\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('RoomDrawer', props);\n\n const coarse = useCoarsePointer();\n const hitPx = coarse ? HIT_PX_COARSE : HIT_PX;\n\n const rootRef = useRef<HTMLDivElement>(null);\n const canvasWrapRef = useRef<HTMLDivElement>(null);\n const svgRef = useRef<SVGSVGElement>(null);\n const lastEmittedRef = useRef<FloorPlan | null>(null);\n const gestureRef = useRef<Gesture>({ kind: 'none' });\n const pointersRef = useRef<Map<number, PlanPoint>>(new Map());\n const pressRef = useRef<{ client: PlanPoint; moved: boolean } | null>(null);\n const spaceHeldRef = useRef(false);\n const altHeldRef = useRef(false);\n const shiftHeldRef = useRef(false);\n\n // ─── Plan state ───────────────────────────────────────────────────────────────\n\n const initialPlan = useMemo(\n () => defaultValue ?? value ?? emptyFloorPlan(),\n // Only ever read once, to seed the uncontrolled state.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [],\n );\n const history = usePlanHistory(initialPlan, historyLimit);\n const isControlled = value !== undefined;\n const plan = isControlled ? value : history.present;\n\n // Keep the internal history in step with an externally driven value, so undo still\n // works for a controlled consumer that has not implemented its own.\n useEffect(() => {\n if (!isControlled || !value) return;\n if (value === lastEmittedRef.current) {\n history.replace(value);\n return;\n }\n history.commit(value, 'external');\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [value, isControlled]);\n\n const [floorIdState, setFloorIdState] = useState<string | undefined>(defaultFloorId);\n // The floor a stale id points at is gone, so fall back to the lowest rather than\n // rendering nothing: a plan always has at least one floor.\n const activeFloorId = floorId ?? floorIdState ?? plan.floors[0]?.id;\n const floor: PlanFloor = useMemo(\n () => getFloor(plan, activeFloorId) ?? getFloor(plan) ?? emptyFloorPlan().floors[0],\n [plan, activeFloorId],\n );\n const [underlayOn, setUnderlayOn] = useState(showUnderlay);\n useEffect(() => setUnderlayOn(showUnderlay), [showUnderlay]);\n const underlayFloor = useMemo(() => (underlayOn ? floorBelow(plan, floor.id) : null), [underlayOn, plan, floor.id]);\n\n const selectFloor = useCallback(\n (nextId: string) => {\n if (nextId === floor.id) return;\n if (floorId === undefined) setFloorIdState(nextId);\n onFloorChange?.(nextId);\n },\n [floor.id, floorId, onFloorChange],\n );\n\n // Memoised on the floor identity, so a gesture that changed nothing re-derives nothing.\n const derivation = useMemo(() => derivePlan(floor), [floor]);\n const rooms = derivation.rooms;\n\n // ─── Tool / selection / view ──────────────────────────────────────────────────\n\n const [toolState, setToolState] = useState<RoomDrawerTool>(defaultTool);\n const tool = toolProp ?? toolState;\n const setTool = useCallback(\n (next: RoomDrawerTool) => {\n if (toolProp === undefined) setToolState(next);\n onToolChange?.(next);\n },\n [toolProp, onToolChange],\n );\n\n const [selectionState, setSelectionState] = useState<readonly PlanSelection[]>(defaultSelection ?? EMPTY_SELECTION);\n const selection = selectionProp !== undefined ? selectionProp : selectionState;\n /**\n * Mirrors `selection` synchronously, so `handle.select(x)` immediately followed by\n * `handle.deleteSelection()` acts on `x` rather than on whatever was selected before\n * React re-rendered.\n */\n const selectionRef = useRef<readonly PlanSelection[]>(selection);\n selectionRef.current = selection;\n\n const setSelection = useCallback(\n (next: readonly PlanSelection[] | PlanSelection | null) => {\n const list = next === null ? EMPTY_SELECTION : Array.isArray(next) ? next : [next as PlanSelection];\n selectionRef.current = list;\n if (selectionProp === undefined) setSelectionState(list);\n onSelectionChange?.([...list]);\n },\n [selectionProp, onSelectionChange],\n );\n\n /** Adds an entry if absent, removes it if present. */\n const toggleSelection = useCallback(\n (entry: PlanSelection) => {\n const current = selectionRef.current;\n const without = current.filter((e) => !(e.type === entry.type && e.id === entry.id));\n setSelection(without.length === current.length ? [...current, entry] : without);\n },\n [setSelection],\n );\n\n /**\n * Exactly one thing selected. The per-entity chrome (the inspector's fields, the\n * midpoint handle) keys off this rather than off `selection[0]`, so it disappears\n * cleanly once a second thing joins the selection instead of silently acting on\n * whichever entry happened to come first.\n */\n const single = selection.length === 1 ? selection[0] : null;\n\n /**\n * Every node the selection implies, which is what a translation actually moves. A room\n * is its face's corners, a wall is its two ends, a corner is itself. Openings have no\n * corners of their own and so translate with whatever wall they are cut into.\n */\n const nodeIdsForSelection = useCallback(\n (entries: readonly PlanSelection[]): Set<string> => {\n const ids = new Set<string>();\n for (const entry of entries) {\n if (entry.type === 'node') {\n ids.add(entry.id);\n } else if (entry.type === 'wall') {\n const wall = floor.walls[entry.id];\n if (wall) {\n ids.add(wall.a);\n ids.add(wall.b);\n }\n } else if (entry.type === 'room') {\n const room = rooms.find((r) => r.roomId === entry.id);\n for (const id of room?.nodeIds ?? []) ids.add(id);\n }\n }\n return ids;\n },\n [floor.walls, rooms],\n );\n\n const selectedNodeIds = useMemo(() => nodeIdsForSelection(selection), [nodeIdsForSelection, selection]);\n\n const [hovered, setHovered] = useState<PlanSelection | null>(null);\n const [viewState, setViewState] = useState<PlanView>(defaultView ?? { x: 0, y: 0, zoom: 0.05 });\n const view = viewProp ?? viewState;\n const setView = useCallback(\n (next: PlanView) => {\n if (viewProp === undefined) setViewState(next);\n onViewChange?.(next);\n },\n [viewProp, onViewChange],\n );\n\n const [size, setSize] = useState({ width: 800, height: 420 });\n const [wallChain, setWallChain] = useState<PlanPoint[]>([]);\n const [cursor, setCursor] = useState<PlanPoint | null>(null);\n\n /**\n * Latched snapping and angle lock. Seeded from the props, then owned by the toolbar\n * toggles and their hotkeys; changing the prop resets the latch.\n */\n const [snapEnabled, setSnapEnabled] = useState(snap);\n const [angleLockEnabled, setAngleLockEnabled] = useState(angleLock);\n useEffect(() => setSnapEnabled(snap), [snap]);\n useEffect(() => setAngleLockEnabled(angleLock), [angleLock]);\n\n const [gridVisible, setGridVisible] = useState(showGrid);\n useEffect(() => setGridVisible(showGrid), [showGrid]);\n\n /** The most recent snap result, so the overlay can explain what it snapped to. */\n const lastSnapRef = useRef<SnapResult | null>(null);\n const [snapHint, setSnapHint] = useState<SnapResult | null>(null);\n\n /**\n * Ruler measurements. Deliberately component state rather than plan data: a line in\n * the wall graph would close faces, and faces become rooms, so a \"guide\" stored as\n * geometry would fight the whole derived-room model. Nothing here reaches `onChange`,\n * a downloaded file, or the undo stack.\n */\n const [measurements, setMeasurements] = useState<Array<{ id: number; a: PlanPoint; b: PlanPoint }>>([]);\n const measureSeqRef = useRef(0);\n const [hoveredMeasure, setHoveredMeasure] = useState<number | null>(null);\n\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n /**\n * What of the ruler is under a point, nearest first: an end before the line, so the\n * ends stay grabbable where they sit on top of it. Only live while the measure tool is\n * up, so measurements are inert scenery under every other tool.\n */\n const measureHit = useCallback(\n (world: PlanPoint): { id: number; end?: 'a' | 'b' } | null => {\n const tol = worldTolerances(hitPx, view.zoom);\n for (let i = measurements.length - 1; i >= 0; i--) {\n const m = measurements[i];\n if (Math.hypot(world.x - m.a.x, world.y - m.a.y) <= tol.node) return { id: m.id, end: 'a' };\n if (Math.hypot(world.x - m.b.x, world.y - m.b.y) <= tol.node) return { id: m.id, end: 'b' };\n }\n for (let i = measurements.length - 1; i >= 0; i--) {\n const m = measurements[i];\n if (pointToSegment(world, m.a, m.b).dist <= tol.wall) return { id: m.id };\n }\n return null;\n },\n [measurements, hitPx, view.zoom],\n );\n\n /**\n * What to say about the file that was just opened, or `null` when there is nothing to\n * say. An empty array means it failed outright; a filled one means it loaded but had to\n * be repaired. Shown in the hint line, and cleared as soon as the user carries on.\n */\n const [importReport, setImportReport] = useState<string[] | null>(null);\n\n const editable = !readOnly && !disabled;\n\n /** Direction of the last committed chain segment, so the next one can go square to it. */\n const chainReferenceAngle = useMemo(() => {\n if (wallChain.length < 2) return undefined;\n const a = wallChain[wallChain.length - 2];\n const b = wallChain[wallChain.length - 1];\n return Math.atan2(b.y - a.y, b.x - a.x);\n }, [wallChain]);\n\n // ─── Sizing ───────────────────────────────────────────────────────────────────\n\n useEffect(() => {\n const element = canvasWrapRef.current;\n if (!element) return undefined;\n const observer = new ResizeObserver(() => {\n const rect = element.getBoundingClientRect();\n if (rect.width > 0 && rect.height > 0) {\n setSize({ width: Math.round(rect.width), height: Math.round(rect.height) });\n }\n });\n observer.observe(element);\n const rect = element.getBoundingClientRect();\n if (rect.width > 0 && rect.height > 0) {\n setSize({ width: Math.round(rect.width), height: Math.round(rect.height) });\n }\n return () => observer.disconnect();\n }, []);\n\n // ─── Coordinates ──────────────────────────────────────────────────────────────\n\n const toWorld = useCallback(\n (clientX: number, clientY: number): PlanPoint => {\n const rect = canvasWrapRef.current?.getBoundingClientRect() ?? { left: 0, top: 0 };\n return clientToWorld(clientX, clientY, rect, view);\n },\n [view],\n );\n\n const toScreen = useCallback(\n (p: PlanPoint) => ({ x: (p.x - view.x) * view.zoom, y: (p.y - view.y) * view.zoom }),\n [view],\n );\n\n const snapAt = useCallback(\n (\n world: PlanPoint,\n origin?: PlanPoint,\n excludeNodeIds?: ReadonlySet<string>,\n referenceAngle?: number,\n excludeWallIds?: ReadonlySet<string>,\n ) => {\n // Alt INVERTS the current setting rather than only switching snapping off, so it\n // doubles as a momentary \"snap just this once\" when snapping is latched off.\n const useSnap = snapEnabled !== altHeldRef.current;\n // Likewise Shift: momentary lock when the latch is off, momentary release when on.\n const lockAngle = angleLockEnabled !== shiftHeldRef.current;\n const result = resolveSnap(world, {\n floor,\n pxToWorld: view.zoom > 0 ? 1 / view.zoom : 1,\n origin,\n referenceAngle,\n excludeNodeIds,\n excludeWallIds,\n options: {\n enabled: useSnap\n ? [\n SnapKind.Node,\n SnapKind.Intersection,\n SnapKind.Wall,\n SnapKind.Angle,\n SnapKind.Extension,\n SnapKind.Alignment,\n SnapKind.Grid,\n ]\n : [],\n gridSize,\n angleStepDeg: lockAngle ? constrainAngle : 0,\n toleranceScreenPx: coarse ? 16 : 10,\n },\n });\n lastSnapRef.current = result;\n return result;\n },\n [floor, view.zoom, snapEnabled, angleLockEnabled, gridSize, constrainAngle, coarse],\n );\n\n /**\n * The snapping context for dragging a corner, measured once before anything moves.\n *\n * `origin` is the far end of one incident wall, so angle lock has something fixed to\n * measure from. `referenceAngle` is the direction of a wall meeting the graph at that\n * ORIGIN, which makes \"square to the run this corner hangs off\" available. It is taken\n * from there rather than from a second incident wall because neither of the origin\n * wall's endpoints moves during the drag, so the basis stays put; a wall touching the\n * dragged corner would rotate as the corner moves and the lock would chase itself.\n *\n * Every incident wall is excluded from snapping: they move with the corner, so leaving\n * them in means the corner snaps to itself.\n */\n const nodeDragContext = useCallback(\n (nodeId: string): { origin?: PlanPoint; referenceAngle?: number; excludeWallIds: ReadonlySet<string> } => {\n const incident = Object.values(floor.walls).filter((w) => w.a === nodeId || w.b === nodeId);\n // Every wall meeting the corner moves with it, so none of them may be snapped to:\n // leaving them in means the corner snaps to itself.\n const excludeWallIds = new Set(incident.map((w) => w.id));\n\n const node = floor.nodes[nodeId];\n if (!node) return { excludeWallIds };\n\n // Angle lock constrains the DIRECTION OF TRAVEL, measured from where the corner\n // started, which is what Shift-dragging means in every editor and makes all eight\n // directions reachable. Measuring from a neighbouring corner instead (as this once\n // did) only ever puts one ray near the corner you are holding: the one running\n // along the existing wall. Everything else lands metres away, so in practice the\n // lock appeared to work in one direction only.\n const origin = { x: node.x, y: node.y };\n\n // Plus the direction of a wall meeting here, so travel can also be taken parallel\n // or square to it rather than only to the page.\n let referenceAngle: number | undefined;\n const first = incident[0];\n if (first) {\n const farId = first.a === nodeId ? first.b : first.a;\n const far = floor.nodes[farId];\n if (far) referenceAngle = Math.atan2(far.y - node.y, far.x - node.x);\n }\n\n return { origin, referenceAngle, excludeWallIds };\n },\n [floor],\n );\n\n const fitToContent = useCallback(\n (options: { padding?: number } = {}) => {\n const padding = options.padding ?? 24;\n const bounds = floorBounds(floor);\n if (!bounds || bounds.width <= 0 || bounds.height <= 0) {\n setView({ x: -1000, y: -1000, zoom: 0.05 });\n return;\n }\n const zoom = Math.min((size.width - padding * 2) / bounds.width, (size.height - padding * 2) / bounds.height);\n const clamped = Math.max(minZoom, Math.min(maxZoom, zoom));\n setView({\n x: bounds.minX - (size.width / clamped - bounds.width) / 2,\n y: bounds.minY - (size.height / clamped - bounds.height) / 2,\n zoom: clamped,\n });\n },\n [floor, size.width, size.height, minZoom, maxZoom, setView],\n );\n\n // Fit once, as soon as there is something to fit and a measured canvas.\n const didFitRef = useRef(false);\n useEffect(() => {\n if (!fitOnMount || didFitRef.current) return;\n if (Object.keys(floor.nodes).length === 0) return;\n if (size.width <= 1) return;\n didFitRef.current = true;\n fitToContent();\n }, [fitOnMount, floor, size.width, fitToContent]);\n\n // ─── Emitting changes ─────────────────────────────────────────────────────────\n\n const emit = useCallback(\n (\n nextFloor: PlanFloor,\n reason: RoomDrawerChangeReason,\n committed: boolean,\n targetIds: string[] = [],\n /**\n * `false` when the edit is the tail of a gesture that already pushed its own\n * snapshot, so the two do not become two undo steps for one action.\n */\n options: { push?: boolean } = {},\n ) => {\n if (nextFloor === floor) return plan;\n const nextPlan = setFloor(plan, nextFloor);\n lastEmittedRef.current = nextPlan;\n\n // History is bookkeeping, not rendering state, so it runs in BOTH modes. Skipping\n // it while controlled left discrete edits (delete, rename, nudge) with no entry at\n // all, and Undo then jumped back past them to whatever the last drag had stashed.\n if (committed) {\n if (options.push === false) history.replace(nextPlan);\n else history.commit(nextPlan, reason, targetIds);\n } else {\n history.updateGesture(nextPlan);\n }\n\n const meta: RoomDrawerChangeMeta = { reason, committed, targetIds };\n onChange?.(nextPlan, meta);\n if (committed) onChangeEnd?.(nextPlan, meta);\n return nextPlan;\n },\n [floor, plan, history, onChange, onChangeEnd],\n );\n\n /** A discrete edit: names new rooms and pushes exactly one history entry. */\n const commitEdit = useCallback(\n (\n nextFloor: PlanFloor,\n reason: RoomDrawerChangeReason,\n targetIds: string[] = [],\n options: { push?: boolean } = {},\n ) => {\n // Welding here rather than per-gesture: a drag is not the only way to squash a\n // wall to nothing, and a plan with two corners on the same point derives as two\n // rooms and refuses to round-trip through JSON without being \"repaired\".\n const named = commitDerivation(weldShortWalls(nextFloor).floor).floor;\n return emit(named, reason, true, targetIds, options);\n },\n [emit],\n );\n\n useEffect(() => {\n onHistoryChange?.({ canUndo: history.canUndo, canRedo: history.canRedo });\n }, [history.canUndo, history.canRedo, onHistoryChange]);\n\n // ─── Selection helpers ────────────────────────────────────────────────────────\n\n const selectedRoom = useMemo(\n () => (single?.type === 'room' ? (rooms.find((r) => r.roomId === single.id) ?? null) : null),\n [single, rooms],\n );\n const selectedWall = single?.type === 'wall' ? floor.walls[single.id] : undefined;\n const selectedOpening = single?.type === 'opening' ? floor.openings[single.id] : undefined;\n\n /** Midpoint of the selected wall, where its \"add a corner\" handle sits. */\n const selectedWallMidpoint = useMemo(() => {\n if (!selectedWall) return null;\n const a = floor.nodes[selectedWall.a];\n const b = floor.nodes[selectedWall.b];\n if (!a || !b) return null;\n return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };\n }, [selectedWall, floor.nodes]);\n\n /** Splits the selected wall at its midpoint and selects the corner that appears. */\n const addCornerToSelectedWall = useCallback(() => {\n if (!editable || !selectedWall) return;\n const edit = splitWall(floor, selectedWall.id, 0.5);\n if (!edit.result.wallIds) return;\n commitEdit(edit.floor, 'edit-wall', [edit.result.nodeId]);\n setSelection({ type: 'node', id: edit.result.nodeId });\n }, [editable, selectedWall, floor, commitEdit, setSelection]);\n\n /** Removes one entry. Split out so deleting a whole selection can reuse it. */\n const deleteOne = useCallback(\n (\n entry: PlanSelection,\n from: PlanFloor,\n /** Room ids going in the same operation, so they do not count as needing a wall. */\n alsoDeleting: ReadonlySet<string> = new Set(),\n ): { floor: PlanFloor; reselect?: PlanSelection } => {\n if (entry.type === 'wall') return { floor: removeWall(from, entry.id).floor };\n if (entry.type === 'opening') return { floor: removeOpening(from, entry.id).floor };\n\n if (entry.type === 'node') {\n // Deleting a corner between exactly two walls means \"straighten this out\": the\n // two walls rejoin into one. Removing both of them instead would leave a hole\n // where the user only wanted the bend gone. `force` is needed because the corner\n // is normally bent by the time anyone wants rid of it, which the unforced\n // dissolve refuses.\n const rejoined = dissolveNode(from, entry.id, { force: true });\n if (rejoined.result) {\n return { floor: rejoined.floor, reselect: { type: 'wall', id: rejoined.result.wallId } };\n }\n // A loose end or a junction of three or more: there is nothing to rejoin, so the\n // walls meeting there go with it.\n let next = from;\n for (const wall of Object.values(from.walls)) {\n if (wall.a === entry.id || wall.b === entry.id) {\n next = removeWall(next, wall.id, { dissolveCollinear: false }).floor;\n }\n }\n return { floor: next };\n }\n\n // A room: drop its metadata and the walls that ONLY it uses. A wall a neighbouring\n // room still needs survives, which is what \"walls are shared\" has to mean.\n const room = rooms.find((r) => r.roomId === entry.id);\n if (!room) return { floor: from };\n const usedElsewhere = new Set<string>();\n for (const other of rooms) {\n if (other.roomId === entry.id) continue;\n // A neighbour being deleted in the same breath does not need the wall either,\n // otherwise deleting two adjacent rooms together would strand the wall they share.\n if (other.roomId && alsoDeleting.has(other.roomId)) continue;\n for (const id of other.wallIds) usedElsewhere.add(id);\n }\n let next = from;\n for (const wallId of new Set(room.wallIds)) {\n if (usedElsewhere.has(wallId)) continue;\n next = removeWall(next, wallId, { dissolveCollinear: false }).floor;\n }\n const nextRooms = { ...next.rooms };\n delete nextRooms[entry.id];\n return { floor: { ...next, rooms: nextRooms } };\n },\n [rooms],\n );\n\n const deleteSelection = useCallback(() => {\n const entries = selectionRef.current;\n if (!editable || entries.length === 0) return;\n\n // Rooms first, then walls, then corners. A corner deleted early can dissolve a wall\n // that a later entry still refers to, and rooms need their walls intact to work out\n // which of them a neighbour is still using.\n const order: Record<PlanSelection['type'], number> = { room: 0, opening: 1, wall: 2, node: 3 };\n const ordered = [...entries].sort((a, b) => order[a.type] - order[b.type]);\n\n const deletingRooms = new Set(ordered.filter((e) => e.type === 'room').map((e) => e.id));\n let next = floor;\n let reselect: PlanSelection | undefined;\n for (const entry of ordered) {\n const result = deleteOne(entry, next, deletingRooms);\n next = result.floor;\n // Only meaningful when one thing was deleted; a bulk delete just clears.\n if (ordered.length === 1) reselect = result.reselect;\n }\n\n if (next === floor) return;\n commitEdit(\n next,\n 'delete',\n ordered.map((e) => e.id),\n );\n setSelection(reselect ?? null);\n }, [editable, floor, deleteOne, commitEdit, setSelection]);\n\n // ─── Pointer handling ─────────────────────────────────────────────────────────\n\n /**\n * The snap hint belongs to whatever is being drawn, and only `finishGesture` used to\n * clear it — so ending a chain from the keyboard left its guide and marker painted on\n * the canvas with nothing to explain them. Identity is preserved when there is nothing\n * to clear, so the tool-change effect below costs no render.\n */\n const endWallChain = useCallback(() => {\n setWallChain((prev) => (prev.length === 0 ? prev : []));\n setSnapHint(null);\n }, []);\n\n // Switching tool abandons whatever the last one had half-drawn, hint included.\n useEffect(() => {\n endWallChain();\n }, [tool, endWallChain]);\n\n const handlePointerDown = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>) => {\n if (disabled) return;\n rootRef.current?.focus();\n // Whatever the last opened file had to say, the user has read it or does not care.\n setImportReport(null);\n pointersRef.current.set(event.pointerId, { x: event.clientX, y: event.clientY });\n (event.currentTarget as HTMLElement).setPointerCapture?.(event.pointerId);\n\n // Two pointers abort whatever tool gesture was running and become a pinch.\n if (pointersRef.current.size === 2) {\n const [p1, p2] = [...pointersRef.current.values()];\n if (gestureRef.current.kind !== 'none' && gestureRef.current.kind !== 'pan') {\n history.cancelGesture();\n }\n gestureRef.current = {\n kind: 'pinch',\n startDist: Math.hypot(p2.x - p1.x, p2.y - p1.y),\n startView: view,\n startMid: { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 },\n };\n return;\n }\n if (pointersRef.current.size > 2) return;\n\n pressRef.current = { client: { x: event.clientX, y: event.clientY }, moved: false };\n\n // Pan: middle button, or space held.\n if (event.button === 1 || spaceHeldRef.current) {\n gestureRef.current = {\n kind: 'pan',\n startClient: { x: event.clientX, y: event.clientY },\n startView: view,\n };\n return;\n }\n if (event.button !== 0) return;\n\n const world = toWorld(event.clientX, event.clientY);\n\n if (!editable) {\n const hit = hitTest(floor, rooms, world, worldTolerances(hitPx, view.zoom));\n setSelection(hit);\n return;\n }\n\n if (tool === 'rect') {\n const start = snapAt(world).point;\n gestureRef.current = { kind: 'rect', start, current: start };\n history.beginGesture();\n return;\n }\n\n if (tool === 'measure') {\n // No `history.beginGesture()` anywhere in here: measuring changes nothing, so\n // there is nothing to take back and an undo entry would be a lie.\n const grabbed = measureHit(world);\n if (grabbed) {\n const m = measurements.find((x) => x.id === grabbed.id)!;\n gestureRef.current = grabbed.end\n ? { kind: 'measureEnd', id: grabbed.id, end: grabbed.end }\n : { kind: 'measureMove', id: grabbed.id, startWorld: world, origA: m.a, origB: m.b };\n return;\n }\n const start = snapAt(world).point;\n gestureRef.current = { kind: 'measure', start, current: start };\n return;\n }\n\n if (tool === 'wall') {\n const origin = wallChain[wallChain.length - 1];\n const snapped = snapAt(world, origin, undefined, chainReferenceAngle).point;\n if (wallChain.length === 0) {\n setWallChain([snapped]);\n return;\n }\n const from = wallChain[wallChain.length - 1];\n const edit = addWall(\n floor,\n from,\n snapped,\n { thickness: wallThickness, type: WallType.Interior, height: wallHeight },\n worldTolerances(hitPx, view.zoom).node,\n );\n if (edit.result.wallIds.length > 0) commitEdit(edit.floor, 'draw-wall', edit.result.wallIds);\n // Closing the loop on the chain's own start finishes it.\n const closes =\n wallChain.length >= 2 &&\n Math.hypot(snapped.x - wallChain[0].x, snapped.y - wallChain[0].y) < worldTolerances(hitPx, view.zoom).node;\n if (closes) endWallChain();\n else setWallChain((prev) => [...prev, snapped]);\n return;\n }\n\n const openingKind = OPENING_TOOL_KIND[tool];\n if (openingKind) {\n const tolerances = worldTolerances(hitPx, view.zoom);\n const hit = hitTest(floor, rooms, world, tolerances, { accept: ['wall'] });\n if (hit?.type !== 'wall') return;\n const wall = floor.walls[hit.id];\n const a = floor.nodes[wall.a];\n const b = floor.nodes[wall.b];\n const len = Math.hypot(b.x - a.x, b.y - a.y);\n const t = ((world.x - a.x) * (b.x - a.x) + (world.y - a.y) * (b.y - a.y)) / (len * len || 1);\n const defaults = {\n ...BUILT_IN_OPENING_DEFAULTS[openingKind],\n ...openingDefaults?.[openingKind],\n };\n const edit = addOpening(floor, {\n wallId: wall.id,\n kind: openingKind,\n offset: Math.min(Math.max(t, 0), 1) * len,\n width: defaults.width,\n sill: defaults.sill,\n head: defaults.head,\n });\n if (edit.result.openingId) {\n commitEdit(edit.floor, 'add-opening', [edit.result.openingId]);\n setSelection({ type: 'opening', id: edit.result.openingId });\n }\n return;\n }\n\n // Select tool.\n const tolerances = worldTolerances(hitPx, view.zoom);\n\n // The selected wall's midpoint handle sits on top of the wall itself, so it has to\n // be tested before the general hit test or the wall underneath always wins.\n if (selectedWallMidpoint && single?.type === 'wall') {\n const grabbed =\n Math.hypot(world.x - selectedWallMidpoint.x, world.y - selectedWallMidpoint.y) <= tolerances.node;\n if (grabbed) {\n history.beginGesture();\n gestureRef.current = {\n kind: 'wallSplit',\n wallId: single.id,\n at: selectedWallMidpoint,\n };\n return;\n }\n }\n\n const hit = hitTest(floor, rooms, world, tolerances);\n const additive = multiSelect && (event.ctrlKey || event.metaKey);\n\n if (!hit) {\n // Empty canvas. With multi-select on, a drag from here is a marquee; a plain\n // click clears. Ctrl is honoured so a stray miss does not wipe the selection.\n if (!additive) setSelection(null);\n gestureRef.current = multiSelect\n ? { kind: 'marquee', start: world, current: world, additive, base: selectionRef.current }\n : { kind: 'none' };\n return;\n }\n\n if ((event.ctrlKey || event.metaKey) && hit.type === 'wall' && floor.walls[hit.id]) {\n // Ctrl on a wall stays ambiguous until the pointer moves: a click toggles it in\n // or out of the selection, a drag pulls it off its neighbours. The drag\n // threshold decides, which is the same gate that stops a wobbling click from\n // moving what it just picked up, so the toggle waits for pointerup.\n const wall = floor.walls[hit.id];\n if (!additive) setSelection(hit);\n history.beginGesture();\n gestureRef.current = {\n kind: 'wall',\n wallId: hit.id,\n startWorld: world,\n origA: { ...floor.nodes[wall.a] },\n origB: { ...floor.nodes[wall.b] },\n moved: false,\n detach: true,\n toggle: additive,\n };\n return;\n }\n\n if (additive) {\n // Toggling is a selection change on its own: no drag follows, so that a\n // Ctrl-click which happens to wobble does not also move what it just picked up.\n toggleSelection(hit);\n gestureRef.current = { kind: 'none' };\n return;\n }\n\n const alreadySelected = selectionRef.current.some((entry) => entry.type === hit.type && entry.id === hit.id);\n // Pressing something already in a multi-selection moves the whole set. Pressing\n // anything else selects just it, which is what every editor does.\n if (!alreadySelected) setSelection(hit);\n\n history.beginGesture();\n\n if (selectionRef.current.length > 1) {\n const nodeIds = [...nodeIdsForSelection(selectionRef.current)];\n const orig: Record<string, PlanPoint> = {};\n for (const id of nodeIds) {\n const node = floor.nodes[id];\n if (node) orig[id] = { x: node.x, y: node.y };\n }\n gestureRef.current = { kind: 'room', nodeIds, startWorld: world, orig, moved: false };\n return;\n }\n\n if (hit.type === 'node') {\n gestureRef.current = {\n kind: 'node',\n nodeId: hit.id,\n moved: false,\n ...nodeDragContext(hit.id),\n };\n } else if (hit.type === 'wall') {\n const wall = floor.walls[hit.id];\n gestureRef.current = {\n kind: 'wall',\n wallId: hit.id,\n startWorld: world,\n origA: { ...floor.nodes[wall.a] },\n origB: { ...floor.nodes[wall.b] },\n moved: false,\n };\n } else if (hit.type === 'opening') {\n gestureRef.current = { kind: 'opening', openingId: hit.id, moved: false };\n } else {\n const room = rooms.find((r) => r.roomId === hit.id);\n const nodeIds = room ? [...new Set(room.nodeIds)] : [];\n const orig: Record<string, PlanPoint> = {};\n for (const id of nodeIds) {\n const node = floor.nodes[id];\n if (node) orig[id] = { x: node.x, y: node.y };\n }\n gestureRef.current = { kind: 'room', nodeIds, startWorld: world, orig, moved: false };\n }\n },\n [\n disabled,\n editable,\n view,\n tool,\n floor,\n rooms,\n hitPx,\n toWorld,\n snapAt,\n wallChain,\n wallThickness,\n wallHeight,\n openingDefaults,\n commitEdit,\n setSelection,\n selectedWallMidpoint,\n single,\n chainReferenceAngle,\n nodeDragContext,\n nodeIdsForSelection,\n multiSelect,\n toggleSelection,\n history,\n endWallChain,\n measureHit,\n measurements,\n ],\n );\n\n const handlePointerMove = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>) => {\n if (disabled) return;\n if (pointersRef.current.has(event.pointerId)) {\n pointersRef.current.set(event.pointerId, { x: event.clientX, y: event.clientY });\n }\n\n const gesture = gestureRef.current;\n\n if (gesture.kind === 'pinch' && pointersRef.current.size >= 2) {\n const [p1, p2] = [...pointersRef.current.values()];\n const distNow = Math.hypot(p2.x - p1.x, p2.y - p1.y);\n if (gesture.startDist <= 0) return;\n const nextZoom = Math.max(minZoom, Math.min(maxZoom, gesture.startView.zoom * (distNow / gesture.startDist)));\n const rect = canvasWrapRef.current?.getBoundingClientRect() ?? { left: 0, top: 0 };\n const mid = { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 };\n // Keep the world point under the pinch midpoint where it is.\n const worldMid = clientToWorld(gesture.startMid.x, gesture.startMid.y, rect, gesture.startView);\n setView({\n x: worldMid.x - (mid.x - rect.left) / nextZoom,\n y: worldMid.y - (mid.y - rect.top) / nextZoom,\n zoom: nextZoom,\n });\n return;\n }\n\n if (pressRef.current) {\n const dx = event.clientX - pressRef.current.client.x;\n const dy = event.clientY - pressRef.current.client.y;\n if (Math.hypot(dx, dy) > DRAG_THRESHOLD_PX) pressRef.current.moved = true;\n }\n\n if (gesture.kind === 'pan') {\n setView({\n x: gesture.startView.x - (event.clientX - gesture.startClient.x) / gesture.startView.zoom,\n y: gesture.startView.y - (event.clientY - gesture.startClient.y) / gesture.startView.zoom,\n zoom: gesture.startView.zoom,\n });\n return;\n }\n\n const world = toWorld(event.clientX, event.clientY);\n setCursor(world);\n\n if (gesture.kind === 'none') {\n if (!editable) return;\n if (tool === 'wall' && wallChain.length > 0) {\n // Preview where the wall will actually land, not where the pointer is.\n const snapped = snapAt(world, wallChain[wallChain.length - 1], undefined, chainReferenceAngle);\n setCursor(snapped.point);\n setSnapHint(snapped);\n return;\n }\n if (tool === 'select') {\n const hit = hitTest(floor, rooms, world, worldTolerances(hitPx, view.zoom));\n if (!selectionsEqual(hit, hovered)) {\n setHovered(hit);\n onHoverChange?.(hit);\n }\n }\n if (tool === 'measure') setHoveredMeasure(measureHit(world)?.id ?? null);\n return;\n }\n\n if (!pressRef.current?.moved) return;\n\n if (gesture.kind === 'measureEnd') {\n const point = snapAt(world).point;\n setMeasurements((list) => list.map((m) => (m.id === gesture.id ? { ...m, [gesture.end]: point } : m)));\n setCursor(point);\n return;\n }\n\n if (gesture.kind === 'measureMove') {\n const dx = world.x - gesture.startWorld.x;\n const dy = world.y - gesture.startWorld.y;\n setMeasurements((list) =>\n list.map((m) =>\n m.id === gesture.id\n ? {\n ...m,\n a: { x: gesture.origA.x + dx, y: gesture.origA.y + dy },\n b: { x: gesture.origB.x + dx, y: gesture.origB.y + dy },\n }\n : m,\n ),\n );\n return;\n }\n\n if (gesture.kind === 'rect' || gesture.kind === 'measure') {\n gestureRef.current = { ...gesture, current: snapAt(world).point };\n setCursor(snapAt(world).point);\n return;\n }\n\n if (gesture.kind === 'marquee') {\n // Unsnapped: a selection rectangle is not geometry, so pulling it onto the grid\n // would only make it harder to enclose exactly what you meant.\n gestureRef.current = { ...gesture, current: world };\n setCursor(world);\n return;\n }\n\n if (gesture.kind === 'wallSplit') {\n // First real movement: cut the wall in two and hand the drag over to the new\n // corner, so adding a corner and placing it are one continuous gesture.\n const edit = splitWall(floor, gesture.wallId, gesture.at);\n if (!edit.result.wallIds) {\n gestureRef.current = { kind: 'none' };\n return;\n }\n const nodeId = edit.result.nodeId;\n // The two halves of the wall just split both move with the new corner, so they\n // are excluded like any other incident wall. The original wall's direction is\n // the reference, which makes \"square to the wall I am bending\" available.\n const wasWall = floor.walls[gesture.wallId];\n const originNode = wasWall ? edit.floor.nodes[wasWall.a] : undefined;\n const farNode = wasWall ? edit.floor.nodes[wasWall.b] : undefined;\n const context = {\n origin: originNode ? { x: originNode.x, y: originNode.y } : undefined,\n referenceAngle:\n originNode && farNode ? Math.atan2(farNode.y - originNode.y, farNode.x - originNode.x) : undefined,\n excludeWallIds: new Set(edit.result.wallIds),\n };\n const snapped = snapAt(\n world,\n context.origin,\n new Set([nodeId]),\n context.referenceAngle,\n context.excludeWallIds,\n ).point;\n gestureRef.current = { kind: 'node', nodeId, moved: true, ...context };\n setSelection({ type: 'node', id: nodeId });\n setSnapHint(lastSnapRef.current);\n emit(moveNode(edit.floor, nodeId, snapped).floor, 'move-node', false, [nodeId]);\n return;\n }\n\n if (gesture.kind === 'node') {\n const snapped = snapAt(\n world,\n gesture.origin,\n new Set([gesture.nodeId]),\n gesture.referenceAngle,\n gesture.excludeWallIds,\n ).point;\n setSnapHint(lastSnapRef.current);\n gestureRef.current = { ...gesture, moved: true };\n emit(moveNode(floor, gesture.nodeId, snapped).floor, 'move-node', false, [gesture.nodeId]);\n return;\n }\n\n if (gesture.kind === 'wall') {\n // Constrained to the wall's own normal: pushing a wall sideways. Because the\n // endpoints are shared, the neighbouring room follows, which is the point.\n const wall = floor.walls[gesture.wallId];\n if (!wall) return;\n const dx = gesture.origB.x - gesture.origA.x;\n const dy = gesture.origB.y - gesture.origA.y;\n const len = Math.hypot(dx, dy);\n if (len < 1e-6) return;\n const normal = { x: -dy / len, y: dx / len };\n const raw = (world.x - gesture.startWorld.x) * normal.x + (world.y - gesture.startWorld.y) * normal.y;\n const step = snap !== altHeldRef.current && gridSize > 0 ? gridSize : 1;\n const offset = Math.round(raw / step) * step;\n // How far the wall has to move before it is worth detaching: the connectors are\n // exactly this long, so anything shorter is a wall the model will not accept.\n const minDetach = Math.max(step, MIN_WALL_LENGTH);\n\n if (gesture.detach) {\n // Nothing happens until the wall has somewhere to go: detaching at zero would\n // mint two zero-length connectors, which the model forbids.\n if (Math.abs(offset) < minDetach) return;\n const edit = detachWall(floor, gesture.wallId, {\n x: normal.x * offset,\n y: normal.y * offset,\n });\n if (!edit.result) return;\n gestureRef.current = {\n ...gesture,\n detach: false,\n toggle: false,\n detachedSign: Math.sign(offset),\n moved: true,\n };\n setSelection({ type: 'wall', id: gesture.wallId });\n emit(edit.floor, 'move-wall', false, [gesture.wallId]);\n return;\n }\n\n let next = moveNode(floor, wall.a, {\n x: gesture.origA.x + normal.x * offset,\n y: gesture.origA.y + normal.y * offset,\n }).floor;\n next = moveNode(next, wall.b, {\n x: gesture.origB.x + normal.x * offset,\n y: gesture.origB.y + normal.y * offset,\n }).floor;\n\n if (gesture.detachedSign && Math.abs(offset) < MIN_WALL_LENGTH) {\n // Pushed back onto the line it came off. The connectors have no length left,\n // so their ends weld and the rest of the drag is an ordinary wall drag: the\n // corner that survives a weld is the one that was already there, which is\n // exactly the corner the wall was pulled off.\n next = weldShortWalls(next).floor;\n gestureRef.current = { ...gesture, detachedSign: undefined, moved: true };\n } else {\n gestureRef.current = { ...gesture, moved: true };\n }\n emit(next, 'move-wall', false, [gesture.wallId]);\n return;\n }\n\n if (gesture.kind === 'opening') {\n const opening = floor.openings[gesture.openingId];\n if (!opening) return;\n const wall = floor.walls[opening.wallId];\n const a = floor.nodes[wall.a];\n const b = floor.nodes[wall.b];\n const len = Math.hypot(b.x - a.x, b.y - a.y);\n const t = ((world.x - a.x) * (b.x - a.x) + (world.y - a.y) * (b.y - a.y)) / (len * len || 1);\n gestureRef.current = { ...gesture, moved: true };\n emit(\n updateOpening(floor, gesture.openingId, { offset: Math.min(Math.max(t, 0), 1) * len }).floor,\n 'move-opening',\n false,\n [gesture.openingId],\n );\n return;\n }\n\n if (gesture.kind === 'room') {\n const rawDx = world.x - gesture.startWorld.x;\n const rawDy = world.y - gesture.startWorld.y;\n const step = snap !== altHeldRef.current && gridSize > 0 ? gridSize : 1;\n const dx = Math.round(rawDx / step) * step;\n const dy = Math.round(rawDy / step) * step;\n let next = floor;\n for (const id of gesture.nodeIds) {\n const orig = gesture.orig[id];\n if (!orig) continue;\n next = moveNode(next, id, { x: orig.x + dx, y: orig.y + dy }).floor;\n }\n gestureRef.current = { ...gesture, moved: true };\n emit(next, 'move-room', false, gesture.nodeIds);\n }\n },\n [\n disabled,\n editable,\n tool,\n floor,\n rooms,\n hovered,\n hitPx,\n view.zoom,\n minZoom,\n maxZoom,\n gridSize,\n snap,\n toWorld,\n snapAt,\n emit,\n setView,\n setSelection,\n wallChain,\n chainReferenceAngle,\n onHoverChange,\n measureHit,\n ],\n );\n\n const finishGesture = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>, cancelled: boolean) => {\n pointersRef.current.delete(event.pointerId);\n const gesture = gestureRef.current;\n gestureRef.current = { kind: 'none' };\n const moved = pressRef.current?.moved ?? false;\n pressRef.current = null;\n setSnapHint(null);\n\n if (gesture.kind === 'none' || gesture.kind === 'pan' || gesture.kind === 'pinch') return;\n\n if (gesture.kind === 'marquee') {\n // The marquee never touches the plan, so it has no gesture snapshot to unwind.\n if (cancelled || !moved) {\n if (cancelled) setSelection([...gesture.base]);\n return;\n }\n const minX = Math.min(gesture.start.x, gesture.current.x);\n const maxX = Math.max(gesture.start.x, gesture.current.x);\n const minY = Math.min(gesture.start.y, gesture.current.y);\n const maxY = Math.max(gesture.start.y, gesture.current.y);\n\n // Corners only. A room is not a primitive you can enclose, it is whatever its\n // corners form, and selecting the corners is what makes a drag move it. Walls and\n // rooms light up on their own wherever all of their corners are in the set.\n const caught: PlanSelection[] = [];\n for (const node of Object.values(floor.nodes)) {\n if (node.x >= minX && node.x <= maxX && node.y >= minY && node.y <= maxY) {\n caught.push({ type: 'node', id: node.id });\n }\n }\n\n if (gesture.additive) {\n const existing = new Set(gesture.base.map((e) => `${e.type}:${e.id}`));\n setSelection([...gesture.base, ...caught.filter((e) => !existing.has(`${e.type}:${e.id}`))]);\n } else {\n setSelection(caught);\n }\n return;\n }\n\n if (cancelled) {\n const restored = history.cancelGesture();\n if (restored) onChange?.(restored, { reason: 'external', committed: false, targetIds: [] });\n return;\n }\n\n if (gesture.kind === 'measureEnd' || gesture.kind === 'measureMove') return;\n\n if (gesture.kind === 'measure') {\n // A measurement stays on the canvas after the drag, so it can be read while\n // drawing against it. A tap that went nowhere leaves nothing behind.\n const span = Math.hypot(gesture.current.x - gesture.start.x, gesture.current.y - gesture.start.y);\n if (moved && span > EPS_MEASURE) {\n measureSeqRef.current += 1;\n setMeasurements((list) => [...list, { id: measureSeqRef.current, a: gesture.start, b: gesture.current }]);\n }\n return;\n }\n\n if (gesture.kind === 'rect') {\n if (!moved) {\n history.cancelGesture();\n return;\n }\n const edit = addRect(\n floor,\n gesture.start,\n gesture.current,\n { thickness: wallThickness, type: WallType.Interior, height: wallHeight },\n worldTolerances(hitPx, view.zoom).node,\n );\n if (edit.result.wallIds.length === 0) {\n history.cancelGesture();\n return;\n }\n // One entry for the whole gesture, not one per pointermove.\n const pushedRect = history.endGesture();\n commitEdit(edit.floor, 'draw-rect', edit.result.wallIds, { push: !pushedRect });\n return;\n }\n\n if (!moved) {\n history.cancelGesture();\n // A Ctrl press on a wall that never became a detach drag was a selection toggle.\n if (gesture.kind === 'wall' && gesture.toggle) {\n toggleSelection({ type: 'wall', id: gesture.wallId });\n }\n return;\n }\n\n if (gesture.kind === 'node') {\n // Dropping a node onto another welds them.\n const node = floor.nodes[gesture.nodeId];\n if (node) {\n const tolerance = worldTolerances(hitPx, view.zoom).node;\n const target = Object.values(floor.nodes).find(\n (other) => other.id !== gesture.nodeId && Math.hypot(other.x - node.x, other.y - node.y) <= tolerance,\n );\n if (target) {\n const pushedWeld = history.endGesture();\n commitEdit(mergeNodes(floor, target.id, gesture.nodeId).floor, 'move-node', [target.id], {\n push: !pushedWeld,\n });\n setSelection({ type: 'node', id: target.id });\n return;\n }\n }\n }\n\n const pushedDrag = history.endGesture();\n const reason: RoomDrawerChangeReason =\n gesture.kind === 'wall'\n ? 'move-wall'\n : gesture.kind === 'opening'\n ? 'move-opening'\n : gesture.kind === 'room'\n ? 'move-room'\n : 'move-node';\n commitEdit(floor, reason, [], { push: !pushedDrag });\n },\n [floor, history, hitPx, view.zoom, wallThickness, wallHeight, commitEdit, setSelection, toggleSelection, onChange],\n );\n\n // Wheel zoom needs a non-passive listener: React's synthetic wheel handler is passive\n // and cannot preventDefault, so the page would scroll instead.\n useEffect(() => {\n const element = canvasWrapRef.current;\n if (!element || disabled) return undefined;\n function onWheel(e: WheelEvent) {\n e.preventDefault();\n const rect = element!.getBoundingClientRect();\n const factor = Math.exp(-e.deltaY * 0.002);\n setView(\n (() => {\n const current = viewRef.current;\n const nextZoom = Math.max(minZoom, Math.min(maxZoom, current.zoom * factor));\n const world = clientToWorld(e.clientX, e.clientY, rect, current);\n return {\n x: world.x - (e.clientX - rect.left) / nextZoom,\n y: world.y - (e.clientY - rect.top) / nextZoom,\n zoom: nextZoom,\n };\n })(),\n );\n }\n element.addEventListener('wheel', onWheel, { passive: false });\n return () => element.removeEventListener('wheel', onWheel);\n }, [disabled, minZoom, maxZoom, setView]);\n\n const viewRef = useRef(view);\n viewRef.current = view;\n\n // ─── Keyboard ─────────────────────────────────────────────────────────────────\n\n const zoomBy = useCallback(\n (factor: number) => {\n const nextZoom = Math.max(minZoom, Math.min(maxZoom, view.zoom * factor));\n const centreWorld = {\n x: view.x + size.width / 2 / view.zoom,\n y: view.y + size.height / 2 / view.zoom,\n };\n setView({\n x: centreWorld.x - size.width / 2 / nextZoom,\n y: centreWorld.y - size.height / 2 / nextZoom,\n zoom: nextZoom,\n });\n },\n [view, size, minZoom, maxZoom, setView],\n );\n\n const undo = useCallback(() => {\n if (historyMode === 'none') return;\n const target = history.undo();\n if (!target) return;\n lastEmittedRef.current = target;\n onChange?.(target, { reason: 'undo', committed: true, targetIds: [] });\n onChangeEnd?.(target, { reason: 'undo', committed: true, targetIds: [] });\n }, [historyMode, history, onChange, onChangeEnd]);\n\n const redo = useCallback(() => {\n if (historyMode === 'none') return;\n const target = history.redo();\n if (!target) return;\n lastEmittedRef.current = target;\n onChange?.(target, { reason: 'redo', committed: true, targetIds: [] });\n onChangeEnd?.(target, { reason: 'redo', committed: true, targetIds: [] });\n }, [historyMode, history, onChange, onChangeEnd]);\n\n /** Selects every room, or every corner when the plan has no enclosed rooms yet. */\n const selectAll = useCallback(() => {\n const asRooms = rooms.filter((r) => r.roomId).map((r) => ({ type: 'room', id: r.roomId! }) as PlanSelection);\n if (asRooms.length > 0) {\n setSelection(asRooms);\n return;\n }\n setSelection(Object.keys(floor.nodes).map((id) => ({ type: 'node', id }) as PlanSelection));\n }, [rooms, floor.nodes, setSelection]);\n\n const nudge = useCallback(\n (dx: number, dy: number) => {\n if (!editable || selectedNodeIds.size === 0) return;\n const step = gridSize > 0 ? gridSize : 100;\n const amount = shiftHeldRef.current ? step * 10 : step;\n let next = floor;\n for (const id of selectedNodeIds) {\n const node = next.nodes[id];\n if (node) next = moveNode(next, id, { x: node.x + dx * amount, y: node.y + dy * amount }).floor;\n }\n commitEdit(next, 'nudge', [...selectedNodeIds]);\n },\n [editable, selectedNodeIds, floor, gridSize, commitEdit],\n );\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return;\n // The old implementation put this on `window`, so any mounted drawer swallowed\n // Delete and `+` for the whole page. Scoping it to the focused root fixes that.\n const target = event.target as HTMLElement;\n if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {\n if (event.key === 'Escape') (target as HTMLElement).blur();\n return;\n }\n\n if (event.key === 'Shift') shiftHeldRef.current = true;\n if (event.key === 'Alt') altHeldRef.current = true;\n if (event.key === ' ' && !spaceHeldRef.current) {\n spaceHeldRef.current = true;\n event.preventDefault();\n return;\n }\n\n const meta = event.metaKey || event.ctrlKey;\n if (meta && event.key.toLowerCase() === 'z') {\n event.preventDefault();\n if (event.shiftKey) redo();\n else undo();\n return;\n }\n if (meta && event.key.toLowerCase() === 'a') {\n event.preventDefault();\n if (multiSelect) selectAll();\n return;\n }\n if (meta && event.key.toLowerCase() === 'y') {\n event.preventDefault();\n redo();\n return;\n }\n\n if (event.key === 'Escape') {\n if (wallChain.length > 0) endWallChain();\n // Clears the ruler before the selection: with measurements on screen, Escape\n // most likely means \"put that away\".\n else if (measurements.length > 0) setMeasurements([]);\n else setSelection(null);\n return;\n }\n if (event.key === 'Enter' && tool === 'wall') {\n endWallChain();\n return;\n }\n if (event.key === 'Delete' || event.key === 'Backspace') {\n event.preventDefault();\n // With the ruler up, Delete belongs to the ruler and NOTHING else. It takes the\n // measurement under the pointer, and with none there it does nothing: falling\n // through to the selection would let a measuring tool quietly destroy a room that\n // was selected minutes earlier with another tool.\n if (tool === 'measure') {\n if (hoveredMeasure !== null) {\n setMeasurements((list) => list.filter((m) => m.id !== hoveredMeasure));\n setHoveredMeasure(null);\n }\n return;\n }\n deleteSelection();\n return;\n }\n if (event.key === '+' || event.key === '=') {\n zoomBy(1.25);\n return;\n }\n if (event.key === '-') {\n zoomBy(1 / 1.25);\n return;\n }\n if (event.key === '0') {\n fitToContent();\n return;\n }\n if (event.key.startsWith('Arrow')) {\n event.preventDefault();\n const map: Record<string, [number, number]> = {\n ArrowLeft: [-1, 0],\n ArrowRight: [1, 0],\n ArrowUp: [0, -1],\n ArrowDown: [0, 1],\n };\n const delta = map[event.key];\n if (delta) nudge(delta[0], delta[1]);\n return;\n }\n\n const lower = event.key.toLowerCase();\n if (lower === 's') {\n setSnapEnabled((on) => !on);\n return;\n }\n if (lower === 'l') {\n setAngleLockEnabled((on) => !on);\n return;\n }\n if (lower === 'g') {\n setGridVisible((on) => !on);\n return;\n }\n\n const toolKey = TOOL_KEYS[event.key.toLowerCase()];\n if (toolKey && editable && tools.includes(toolKey)) {\n setTool(toolKey);\n endWallChain();\n }\n },\n [\n disabled,\n editable,\n tools,\n tool,\n wallChain,\n measurements.length,\n hoveredMeasure,\n redo,\n undo,\n setSelection,\n endWallChain,\n deleteSelection,\n zoomBy,\n fitToContent,\n nudge,\n setTool,\n multiSelect,\n selectAll,\n ],\n );\n\n // ─── Files ────────────────────────────────────────────────────────────────────\n\n /**\n * Swaps the whole plan out as one history entry. Opening a file and the `setPlan`\n * handle share it, so a file and a programmatic replacement behave identically, and\n * both stay undoable: opening a file over an hour's work is exactly the mistake that\n * most needs taking back.\n */\n const replacePlan = useCallback(\n (next: FloorPlan, reason: RoomDrawerChangeReason, options?: { resetHistory?: boolean }) => {\n lastEmittedRef.current = next;\n if (options?.resetHistory) {\n history.clearHistory();\n history.replace(next);\n } else {\n history.commit(next, reason);\n }\n const meta: RoomDrawerChangeMeta = { reason, committed: true, targetIds: [] };\n onChange?.(next, meta);\n onChangeEnd?.(next, meta);\n },\n [history, onChange, onChangeEnd],\n );\n\n // ─── Floors ───────────────────────────────────────────────────────────────────\n\n /**\n * Floor operations go through `emit` like any other edit, so each is one undo step and\n * reaches a controlled parent through `onChange`. Adding or copying a storey also\n * switches to it: nobody adds a floor in order to keep looking at the one below.\n */\n const addFloorAt = useCallback(\n (options: { label?: string } = {}) => {\n const { plan: next, floor: added } = addFloor(plan, {\n ...options,\n index: floorIndex(plan, floor.id) + 1,\n });\n replacePlan(next, 'add-floor');\n selectFloor(added.id);\n return added.id;\n },\n [plan, floor.id, replacePlan, selectFloor],\n );\n\n const duplicateActiveFloor = useCallback(() => {\n const { plan: next, floor: copy } = duplicateFloor(plan, floor.id);\n if (!copy) return null;\n replacePlan(next, 'duplicate-floor');\n selectFloor(copy.id);\n return copy.id;\n }, [plan, floor.id, replacePlan, selectFloor]);\n\n const removeFloorById = useCallback(\n (id?: string) => {\n const target = id ?? floor.id;\n const next = removeFloor(plan, target);\n if (next === plan) return; // the last floor, which stays\n replacePlan(next, 'remove-floor');\n if (target === floor.id) {\n // Step down to the storey below where there is one, so the view lands somewhere\n // adjacent rather than at the bottom of the building.\n const was = floorIndex(plan, target);\n selectFloor(next.floors[Math.max(0, was - 1)].id);\n }\n },\n [plan, floor.id, replacePlan, selectFloor],\n );\n\n const moveFloorTo = useCallback(\n (id: string, toIndex: number) => {\n const next = moveFloor(plan, id, toIndex);\n if (next !== plan) replacePlan(next, 'move-floor');\n },\n [plan, replacePlan],\n );\n\n const setFloorHeightById = useCallback(\n (id: string, height: number) => {\n const next = setFloorHeight(plan, id, height);\n if (next !== plan) replacePlan(next, 'edit-floor');\n },\n [plan, replacePlan],\n );\n\n const renameFloorById = useCallback(\n (id: string, label: string) => {\n const next = renameFloor(plan, id, label);\n if (next !== plan) replacePlan(next, 'rename-floor');\n },\n [plan, replacePlan],\n );\n\n const downloadPlan = useCallback(\n (name?: string) => {\n const chosen = (name ?? fileName ?? plan.name ?? '').trim() || 'floor-plan';\n const url = URL.createObjectURL(new Blob([planToJson(plan)], { type: 'application/json' }));\n const link = document.createElement('a');\n link.href = url;\n link.download = chosen.endsWith('.json') ? chosen : `${chosen}.json`;\n link.click();\n // Revoking in the same tick cancels the download in some browsers, so let the task\n // queue drain first.\n setTimeout(() => URL.revokeObjectURL(url), 0);\n },\n [fileName, plan],\n );\n\n const loadJson = useCallback(\n (text: string): PlanParseResult => {\n const result = planFromJson(text);\n if (result.plan) replacePlan(result.plan, 'import');\n // Silence is the report for a clean file: the canvas changing says it worked.\n setImportReport(result.plan && result.issues.length === 0 ? null : result.issues);\n onImport?.(result);\n return result;\n },\n [onImport, replacePlan],\n );\n\n const openFile = useCallback(\n (file: File) => {\n file\n .text()\n .then((text) => loadJson(text))\n .catch((error: Error) => {\n const issues = [`Could not read that file: ${error.message}`];\n setImportReport(issues);\n onImport?.({ plan: null, issues });\n });\n },\n [loadJson, onImport],\n );\n\n const clearMeasurements = useCallback(() => setMeasurements([]), []);\n\n const handleKeyUp = useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Shift') shiftHeldRef.current = false;\n if (event.key === 'Alt') altHeldRef.current = false;\n if (event.key === ' ') spaceHeldRef.current = false;\n }, []);\n\n // ─── Imperative handle ────────────────────────────────────────────────────────\n\n useImperativeHandle(\n ref,\n () => ({\n getPlan: () => plan,\n setPlan: (next, options) => replacePlan(next, 'import', options),\n clear: () => replacePlan(emptyFloorPlan(), 'clear'),\n getFloorId: () => floor.id,\n setFloorId: selectFloor,\n addFloor: (options) => addFloorAt(options),\n duplicateFloor: duplicateActiveFloor,\n removeFloor: removeFloorById,\n renameFloor: renameFloorById,\n moveFloor: moveFloorTo,\n setFloorHeight: setFloorHeightById,\n clearMeasurements,\n download: downloadPlan,\n loadJson,\n undo,\n redo,\n canUndo: () => historyMode !== 'none' && history.canUndo,\n canRedo: () => historyMode !== 'none' && history.canRedo,\n clearHistory: history.clearHistory,\n getSelection: () => [...selectionRef.current],\n select: setSelection,\n addToSelection: (next) => {\n const additions = Array.isArray(next) ? next : [next as PlanSelection];\n const existing = new Set(selectionRef.current.map((e) => `${e.type}:${e.id}`));\n setSelection([...selectionRef.current, ...additions.filter((e) => !existing.has(`${e.type}:${e.id}`))]);\n },\n toggleSelection,\n selectAll,\n deleteSelection,\n getView: () => view,\n setView,\n zoomBy,\n fitToContent,\n toWorld,\n toScreen,\n getSvgElement: () => svgRef.current,\n }),\n [\n plan,\n history,\n replacePlan,\n floor,\n selectFloor,\n addFloorAt,\n duplicateActiveFloor,\n removeFloorById,\n renameFloorById,\n moveFloorTo,\n setFloorHeightById,\n clearMeasurements,\n downloadPlan,\n loadJson,\n undo,\n redo,\n historyMode,\n setSelection,\n toggleSelection,\n selectAll,\n deleteSelection,\n view,\n setView,\n zoomBy,\n fitToContent,\n toWorld,\n toScreen,\n ],\n );\n\n // ─── Overlay (screen space) ───────────────────────────────────────────────────\n\n const gesture = gestureRef.current;\n const overlay = useMemo(() => {\n if (!editable) return null;\n const parts: ReactElement[] = [];\n\n // The ruler: measurements already taken, plus the one in flight. Drawn in screen\n // space, so ticks and figures stay the same size at any zoom.\n const rulerLines = [\n ...measurements,\n ...(gesture.kind === 'measure' ? [{ id: -1, a: gesture.start, b: gesture.current }] : []),\n ];\n for (const line of rulerLines) {\n const a = toScreen(line.a);\n const b = toScreen(line.b);\n const length = Math.hypot(line.b.x - line.a.x, line.b.y - line.a.y);\n if (length < EPS_MEASURE) continue;\n const angle = Math.atan2(line.b.y - line.a.y, line.b.x - line.a.x);\n // End ticks, perpendicular to the run, so a measurement reads as a dimension\n // rather than as something that was drawn.\n const nx = -Math.sin(angle) * 5;\n const ny = Math.cos(angle) * 5;\n const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };\n parts.push(\n <g\n key={`measure-${line.id}`}\n className={cx(\n styles.roomDrawerMeasureLine,\n hoveredMeasure === line.id && styles.roomDrawerMeasureLineHovered,\n classNames?.measureLine,\n )}\n >\n <line x1={a.x} y1={a.y} x2={b.x} y2={b.y} />\n <line x1={a.x - nx} y1={a.y - ny} x2={a.x + nx} y2={a.y + ny} />\n <line x1={b.x - nx} y1={b.y - ny} x2={b.x + nx} y2={b.y + ny} />\n </g>,\n );\n // Grab handles, only while the ruler is the active tool: under any other tool a\n // measurement is inert scenery and its handles would compete with the plan's own.\n if (tool === 'measure' && line.id >= 0) {\n for (const [end, p] of [\n ['a', a],\n ['b', b],\n ] as const) {\n parts.push(\n <circle\n key={`measure-grip-${line.id}-${end}`}\n className={styles.roomDrawerMeasureGrip}\n cx={p.x}\n cy={p.y}\n r={4}\n />,\n );\n }\n }\n parts.push(\n <text\n key={`measure-label-${line.id}`}\n className={cx(styles.roomDrawerMeasure, classNames?.measureLine)}\n x={mid.x}\n y={mid.y - 8}\n textAnchor='middle'\n >\n {`${formatLength(length)} · ${formatAngle(angle)}`}\n </text>,\n );\n }\n\n // Snap guides: the construction lines explaining what the pointer locked onto.\n // resolveSnap has always produced these; without drawing them, snapping reads as the\n // app quietly moving the pointer somewhere the user did not ask for.\n if (showSnapGuides && snapHint) {\n snapHint.guides.forEach((guide, i) => {\n const a = toScreen(guide.a);\n const b = toScreen(guide.b);\n parts.push(\n <line\n key={`guide-${i}`}\n className={cx(styles.roomDrawerGuide, guide.kind === SnapKind.Angle && styles.roomDrawerGuideAngle)}\n x1={a.x}\n y1={a.y}\n x2={b.x}\n y2={b.y}\n />,\n );\n });\n\n // A marker on the point itself, so a node or wall snap is visible too, not just\n // the constraint-style snaps that come with a line.\n if (snapHint.kind !== SnapKind.None && snapHint.kind !== SnapKind.Grid) {\n const p = toScreen(snapHint.point);\n parts.push(\n <rect\n key='snap-marker'\n className={styles.roomDrawerSnapMarker}\n x={p.x - 4}\n y={p.y - 4}\n width={8}\n height={8}\n />,\n );\n }\n }\n\n // Rect preview.\n if (gesture.kind === 'rect') {\n const a = toScreen(gesture.start);\n const b = toScreen(gesture.current);\n const left = Math.min(a.x, b.x);\n const top = Math.min(a.y, b.y);\n const right = Math.max(a.x, b.x);\n const bottom = Math.max(a.y, b.y);\n parts.push(\n <rect\n key='rect-preview'\n className={styles.roomDrawerPreview}\n x={left}\n y={top}\n width={right - left}\n height={bottom - top}\n />,\n );\n\n // Both sides and the area, each sitting on what it measures. The figures are the\n // wall CENTRELINES, which is also what the finished room reports its area from, so\n // the number does not change the moment the drag ends.\n const width = Math.abs(gesture.current.x - gesture.start.x);\n const height = Math.abs(gesture.current.y - gesture.start.y);\n if (showMeasurements && (width > 0 || height > 0)) {\n parts.push(\n <text\n key='rect-width'\n className={styles.roomDrawerMeasure}\n x={(left + right) / 2}\n y={top - 8}\n textAnchor='middle'\n >\n {formatLength(width)}\n </text>,\n <text\n key='rect-height'\n className={styles.roomDrawerMeasure}\n x={left - 8}\n y={(top + bottom) / 2}\n textAnchor='end'\n dominantBaseline='central'\n >\n {formatLength(height)}\n </text>,\n );\n if (width > 0 && height > 0) {\n parts.push(\n <text\n key='rect-area'\n className={cx(styles.roomDrawerMeasure, styles.roomDrawerMeasureSecondary)}\n x={(left + right) / 2}\n y={(top + bottom) / 2}\n textAnchor='middle'\n dominantBaseline='central'\n >\n {formatArea(width * height)}\n </text>,\n );\n }\n }\n }\n\n // Wall chain preview.\n if (tool === 'wall' && wallChain.length > 0) {\n const points = [...wallChain, cursor].filter(Boolean) as PlanPoint[];\n const screen = points.map(toScreen);\n parts.push(\n <polyline\n key='chain'\n className={styles.roomDrawerPreview}\n points={screen.map((p) => `${p.x},${p.y}`).join(' ')}\n fill='none'\n />,\n );\n screen.forEach((p, i) => {\n parts.push(<circle key={`chain-dot-${i}`} className={styles.roomDrawerHandle} cx={p.x} cy={p.y} r={4} />);\n });\n if (showMeasurements && cursor && wallChain.length > 0) {\n const from = wallChain[wallChain.length - 1];\n const length = Math.hypot(cursor.x - from.x, cursor.y - from.y);\n const mid = toScreen({ x: (from.x + cursor.x) / 2, y: (from.y + cursor.y) / 2 });\n const locked = snapHint?.kind === SnapKind.Angle;\n parts.push(\n <text\n key='chain-len'\n className={cx(styles.roomDrawerMeasure, locked && styles.roomDrawerMeasureLocked)}\n x={mid.x}\n y={mid.y - 8}\n textAnchor='middle'\n >\n {`${formatLength(length)} · ${formatAngle(\n // Relative to the previous segment when the lock is measuring from it, since\n // \"90° to the last wall\" is the number the user is actually aiming for.\n snapHint?.angleRelativeTo === 'reference' && chainReferenceAngle !== undefined\n ? Math.atan2(cursor.y - from.y, cursor.x - from.x) - chainReferenceAngle\n : Math.atan2(cursor.y - from.y, cursor.x - from.x),\n )}${snapHint?.angleRelativeTo === 'reference' ? ' rel' : ''}`}\n </text>,\n );\n }\n }\n\n // A handle on every corner the selection implies, so a multi-selection shows exactly\n // what a drag would move. Corners picked explicitly are filled; ones that came along\n // because their wall or room was selected are outlined.\n const explicitNodes = new Set(selection.filter((e) => e.type === 'node').map((e) => e.id));\n for (const id of selectedNodeIds) {\n const node = floor.nodes[id];\n if (!node) continue;\n const p = toScreen(node);\n parts.push(\n <circle\n key={`handle-${id}`}\n className={cx(styles.roomDrawerHandle, explicitNodes.has(id) && styles.roomDrawerHandleActive)}\n cx={p.x}\n cy={p.y}\n r={5}\n />,\n );\n }\n\n // Marquee rectangle.\n if (gesture.kind === 'marquee' && pressRef.current?.moved) {\n const a = toScreen(gesture.start);\n const b = toScreen(gesture.current);\n parts.push(\n <rect\n key='marquee'\n className={styles.roomDrawerMarquee}\n x={Math.min(a.x, b.x)}\n y={Math.min(a.y, b.y)}\n width={Math.abs(b.x - a.x)}\n height={Math.abs(b.y - a.y)}\n />,\n );\n }\n\n // The selected wall's \"add a corner\" handle. Hollow and smaller than a real corner,\n // so it reads as something that is not there yet rather than as an existing node.\n if (selectedWallMidpoint && gesture.kind !== 'node') {\n const p = toScreen(selectedWallMidpoint);\n parts.push(<circle key='midpoint' className={styles.roomDrawerMidHandle} cx={p.x} cy={p.y} r={4} />);\n }\n\n // Dragging a whole ROOM is a rigid translation of every node in its face, so its own\n // walls and area are all frozen; the only things changing are how far it has moved\n // and any wall straddling its boundary. Reporting its own dimensions here would put\n // five stuck numbers on screen.\n if (showMeasurements && gesture.kind === 'room' && gesture.moved) {\n const moving = new Set(gesture.nodeIds);\n\n const sampleId = gesture.nodeIds.find((id) => floor.nodes[id] && gesture.orig[id]);\n if (sampleId) {\n const now = floor.nodes[sampleId];\n const was = gesture.orig[sampleId];\n const dx = now.x - was.x;\n const dy = now.y - was.y;\n const room = single?.type === 'room' ? rooms.find((r) => r.roomId === single.id) : undefined;\n const at = toScreen(room ? room.anchor : now);\n parts.push(\n <text\n key='room-offset'\n className={cx(styles.roomDrawerMeasure, styles.roomDrawerMeasureOffset)}\n x={at.x}\n y={at.y - 26}\n textAnchor='middle'\n >\n {`${formatLength(Math.hypot(dx, dy))} · ${formatAngle(Math.atan2(dy, dx))}`}\n </text>,\n );\n }\n\n // A wall with both ends moving travels with the room and keeps its shape; one with\n // neither is untouched. Only the ones bridging the two are being stretched.\n for (const wall of Object.values(floor.walls)) {\n if (moving.has(wall.a) === moving.has(wall.b)) continue;\n const a = floor.nodes[wall.a];\n const b = floor.nodes[wall.b];\n if (!a || !b) continue;\n const tail = moving.has(wall.a) ? a : b;\n const head = moving.has(wall.a) ? b : a;\n const mid = toScreen({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });\n parts.push(\n <text\n key={`room-stretch-${wall.id}`}\n className={cx(styles.roomDrawerMeasure, styles.roomDrawerMeasureSecondary)}\n x={mid.x}\n y={mid.y - 8}\n textAnchor='middle'\n >\n {`${formatLength(Math.hypot(head.x - tail.x, head.y - tail.y))} · ${formatAngle(\n Math.atan2(head.y - tail.y, head.x - tail.x),\n )}`}\n </text>,\n );\n }\n }\n\n // A CORNER reports every wall meeting it, since reshaping it changes all of them. A\n // WALL reports itself plus the walls running off its ends: dragging a wall moves both\n // its endpoints, so its own length is the one number that cannot change. Single\n // selections only; a multi-selection moves rigidly and uses the offset readout above.\n if (showMeasurements && (single?.type === 'node' || single?.type === 'wall')) {\n const measured: Array<{ wall: (typeof floor.walls)[string]; from: string; primary: boolean }> = [];\n const seen = new Set<string>();\n const add = (wallId: string, from: string, primary: boolean) => {\n if (seen.has(wallId)) return;\n const wall = floor.walls[wallId];\n if (!wall) return;\n seen.add(wallId);\n measured.push({ wall, from, primary });\n };\n\n if (single.type === 'node') {\n for (const wall of Object.values(floor.walls)) {\n if (wall.a === single.id || wall.b === single.id) add(wall.id, single.id, true);\n }\n } else {\n const selected = floor.walls[single.id];\n if (selected) {\n add(selected.id, selected.a, true);\n // Everything attached at either end. These are what actually resize as the\n // selected wall is pushed sideways.\n for (const wall of Object.values(floor.walls)) {\n if (wall.id === selected.id) continue;\n for (const end of [selected.a, selected.b]) {\n if (wall.a === end || wall.b === end) add(wall.id, end, false);\n }\n }\n }\n }\n\n const locked = snapHint?.kind === SnapKind.Angle && gesture.kind !== 'none';\n for (const { wall, from, primary } of measured) {\n const a = floor.nodes[wall.a];\n const b = floor.nodes[wall.b];\n if (!a || !b) continue;\n // Measured outward from the shared end, so the angle describes the wall as it\n // leaves that point rather than depending on which end it was stored from.\n const tail = wall.a === from ? a : b;\n const head = wall.a === from ? b : a;\n const mid = toScreen({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });\n parts.push(\n <text\n key={`measure-${wall.id}`}\n className={cx(\n styles.roomDrawerMeasure,\n !primary && styles.roomDrawerMeasureSecondary,\n primary && locked && styles.roomDrawerMeasureLocked,\n )}\n x={mid.x}\n y={mid.y - 8}\n textAnchor='middle'\n >\n {`${formatLength(Math.hypot(head.x - tail.x, head.y - tail.y))} · ${formatAngle(\n Math.atan2(head.y - tail.y, head.x - tail.x),\n )}`}\n </text>,\n );\n }\n }\n\n return <>{parts}</>;\n }, [\n editable,\n gesture,\n tool,\n wallChain,\n cursor,\n selection,\n floor,\n rooms,\n single,\n selectedNodeIds,\n selectedWallMidpoint,\n showMeasurements,\n showSnapGuides,\n snapHint,\n chainReferenceAngle,\n formatLength,\n formatArea,\n measurements,\n hoveredMeasure,\n classNames?.measureLine,\n toScreen,\n ]);\n\n // ─── Chrome ───────────────────────────────────────────────────────────────────\n\n const TOOL_META: Record<RoomDrawerTool, { icon: typeof faSquare; label: string; key: string }> = {\n select: { icon: faArrowPointer, label: labels?.select ?? 'Select and edit', key: 'V' },\n wall: { icon: faMinus, label: labels?.wall ?? 'Draw walls', key: 'W' },\n rect: { icon: faSquare, label: labels?.rect ?? 'Draw a room', key: 'R' },\n door: { icon: faDoorOpen, label: labels?.door ?? 'Add a door', key: 'D' },\n window: { icon: faWindowMaximize, label: labels?.window ?? 'Add a window', key: 'N' },\n archway: { icon: faExpand, label: labels?.archway ?? 'Add an archway', key: 'A' },\n measure: { icon: faRulerCombined, label: labels?.measure ?? 'Measure', key: 'M' },\n };\n\n const hint = (() => {\n if (!editable) return '';\n if (tool === 'wall') {\n return (\n labels?.hintWall ??\n 'Click to place corners · Shift for a fixed angle, L to latch it · Click the first corner or press Enter to close'\n );\n }\n if (tool === 'rect') return labels?.hintRect ?? 'Drag to draw a room';\n if (tool === 'measure') {\n return (\n labels?.hintMeasure ??\n 'Drag to measure · Drag an end or the line to adjust it · Delete removes one, Escape clears them'\n );\n }\n if (OPENING_TOOL_KIND[tool]) return labels?.hintOpening ?? 'Click a wall to cut an opening into it';\n return (\n labels?.hintSelect ??\n (multiSelect\n ? 'Drag a corner to reshape · Drag a wall to push it, and the room next door moves with it · Ctrl-click or drag a box to select several · Delete to remove'\n : 'Drag a corner to reshape · Drag a wall to push it, and the room next door moves with it · Drag the dot on a selected wall to add a corner · Delete to remove')\n );\n })();\n\n /**\n * What the inspector calls the current selection. It is the only thing in the bar for a\n * corner, which has nothing to edit that dragging it does not already do.\n */\n const selectionNoun = (() => {\n if (selection.length === 0) return labels?.selectedFloor ?? 'Floor';\n if (selection.length > 1) {\n return (labels?.selectedCount ?? '{count} selected').replace('{count}', String(selection.length));\n }\n if (selectedOpening) return labels?.selectedOpening ?? OPENING_KIND_NOUN[selectedOpening.kind];\n if (selectedWall) return labels?.selectedWall ?? 'Wall';\n if (single?.type === 'room') return labels?.selectedRoom ?? 'Room';\n return labels?.selectedCorner ?? 'Corner';\n })();\n\n const visibleTools = editable ? tools : [];\n\n return (\n <div\n {...rest}\n ref={rootRef}\n className={cx(styles.roomDrawer, disabled && styles.roomDrawerDisabled, className, classNames?.root)}\n style={{ ...style, ...slotStyles?.root }}\n role='application'\n aria-label={ariaLabel ?? 'Floor plan editor'}\n tabIndex={disabled ? -1 : 0}\n onKeyDown={handleKeyDown}\n onKeyUp={handleKeyUp}\n >\n {showToolbar && (\n <div\n className={cx(styles.roomDrawerToolbar, classNames?.toolbar)}\n style={slotStyles?.toolbar}\n role='toolbar'\n aria-label={labels?.tools ?? 'Drawing tools'}\n >\n {visibleTools.map((t) => {\n const meta = TOOL_META[t];\n return (\n <button\n key={t}\n type='button'\n className={cx(\n styles.roomDrawerToolButton,\n t === tool && styles.roomDrawerToolButtonActive,\n classNames?.toolButton,\n )}\n style={slotStyles?.toolButton}\n title={`${meta.label} (${meta.key})`}\n aria-label={meta.label}\n aria-pressed={t === tool}\n onClick={() => {\n setTool(t);\n endWallChain();\n }}\n >\n {renderIcon(icons?.[t] ?? meta.icon, {\n className: cx(styles.roomDrawerToolIcon, classNames?.toolIcon),\n style: slotStyles?.toolIcon,\n })}\n </button>\n );\n })}\n\n {visibleTools.length > 0 && <span className={styles.roomDrawerToolbarSeparator} />}\n\n {editable && (\n <>\n {(\n [\n {\n on: snapEnabled,\n toggle: () => setSnapEnabled((v) => !v),\n icon: icons?.snap ?? faMagnet,\n label: labels?.snap ?? 'Snap to grid and geometry',\n key: 'S',\n },\n {\n on: angleLockEnabled,\n toggle: () => setAngleLockEnabled((v) => !v),\n icon: icons?.angleLock ?? faDraftingCompass,\n label: labels?.angleLock ?? `Lock to ${constrainAngle}° angles`,\n key: 'L',\n },\n {\n on: gridVisible,\n toggle: () => setGridVisible((v) => !v),\n icon: icons?.grid ?? faBorderAll,\n label: labels?.grid ?? 'Show grid',\n key: 'G',\n },\n // Only worth offering when there is a storey below to trace.\n ...(floorBelow(plan, floor.id)\n ? [\n {\n on: underlayOn,\n toggle: () => setUnderlayOn((v) => !v),\n icon: icons?.underlay ?? faLayerGroup,\n label: labels?.underlay ?? 'Show the floor below',\n key: 'B',\n },\n ]\n : []),\n ] as const\n ).map((toggle) => (\n <button\n key={toggle.key}\n type='button'\n className={cx(\n styles.roomDrawerToolButton,\n toggle.on && styles.roomDrawerToolButtonOn,\n classNames?.toolButton,\n )}\n style={slotStyles?.toolButton}\n title={`${toggle.label} (${toggle.key})`}\n aria-label={toggle.label}\n aria-pressed={toggle.on}\n onClick={toggle.toggle}\n >\n {renderIcon(toggle.icon, {\n className: cx(styles.roomDrawerToolIcon, classNames?.toolIcon),\n style: slotStyles?.toolIcon,\n })}\n </button>\n ))}\n <span className={styles.roomDrawerToolbarSeparator} />\n </>\n )}\n\n {showHistoryControls && historyMode !== 'none' && editable && (\n <>\n <button\n type='button'\n className={cx(styles.roomDrawerViewButton, classNames?.viewButton)}\n style={slotStyles?.viewButton}\n title={labels?.undo ?? 'Undo'}\n aria-label={labels?.undo ?? 'Undo'}\n disabled={!history.canUndo}\n onClick={undo}\n >\n {renderIcon(icons?.undo ?? faArrowRotateLeft)}\n </button>\n <button\n type='button'\n className={cx(styles.roomDrawerViewButton, classNames?.viewButton)}\n style={slotStyles?.viewButton}\n title={labels?.redo ?? 'Redo'}\n aria-label={labels?.redo ?? 'Redo'}\n disabled={!history.canRedo}\n onClick={redo}\n >\n {renderIcon(icons?.redo ?? faArrowRotateRight)}\n </button>\n <span className={styles.roomDrawerToolbarSeparator} />\n </>\n )}\n\n {showFileControls && (\n <>\n <button\n type='button'\n className={cx(styles.roomDrawerViewButton, classNames?.viewButton)}\n style={slotStyles?.viewButton}\n title={labels?.download ?? 'Download plan'}\n aria-label={labels?.download ?? 'Download plan'}\n onClick={() => downloadPlan()}\n >\n {renderIcon(icons?.download ?? faDownload)}\n </button>\n {editable && (\n <button\n type='button'\n className={cx(styles.roomDrawerViewButton, classNames?.viewButton)}\n style={slotStyles?.viewButton}\n title={labels?.upload ?? 'Open a plan file'}\n aria-label={labels?.upload ?? 'Open a plan file'}\n onClick={() => fileInputRef.current?.click()}\n >\n {renderIcon(icons?.upload ?? faUpload)}\n </button>\n )}\n <span className={styles.roomDrawerToolbarSeparator} />\n </>\n )}\n\n <div className={cx(styles.roomDrawerViewControls, classNames?.viewControls)} style={slotStyles?.viewControls}>\n <button\n type='button'\n className={cx(styles.roomDrawerViewButton, classNames?.viewButton)}\n style={slotStyles?.viewButton}\n title={labels?.zoomOut ?? 'Zoom out'}\n aria-label={labels?.zoomOut ?? 'Zoom out'}\n onClick={() => zoomBy(1 / 1.25)}\n >\n {renderIcon(icons?.zoomOut ?? faMagnifyingGlassMinus)}\n </button>\n <button\n type='button'\n className={cx(styles.roomDrawerZoomLabel, classNames?.zoomLabel)}\n style={slotStyles?.zoomLabel}\n title={labels?.fit ?? 'Fit to content'}\n onClick={() => fitToContent()}\n >\n {Math.round(view.zoom * 1000) / 10}%\n </button>\n <button\n type='button'\n className={cx(styles.roomDrawerViewButton, classNames?.viewButton)}\n style={slotStyles?.viewButton}\n title={labels?.zoomIn ?? 'Zoom in'}\n aria-label={labels?.zoomIn ?? 'Zoom in'}\n onClick={() => zoomBy(1.25)}\n >\n {renderIcon(icons?.zoomIn ?? faMagnifyingGlassPlus)}\n </button>\n </div>\n </div>\n )}\n\n {showFloorBar && plan.floors.length > 0 && (\n <div\n className={cx(styles.roomDrawerFloorBar, classNames?.floorBar)}\n style={slotStyles?.floorBar}\n role='tablist'\n aria-label={labels?.selectedFloor ?? 'Floor'}\n >\n {/* Top storey first, so the strip reads the way a building does in section\n rather than the way the array happens to be ordered. */}\n {[...plan.floors].reverse().map((f) => (\n <button\n key={f.id}\n type='button'\n role='tab'\n className={cx(\n styles.roomDrawerFloorTab,\n f.id === floor.id && styles.roomDrawerFloorTabActive,\n classNames?.floorTab,\n )}\n style={slotStyles?.floorTab}\n aria-selected={f.id === floor.id}\n onClick={() => selectFloor(f.id)}\n >\n {f.label}\n </button>\n ))}\n {editable && (\n <button\n type='button'\n className={cx(styles.roomDrawerFloorTab, styles.roomDrawerFloorTabAdd)}\n title={labels?.addFloor ?? 'Add a floor above'}\n aria-label={labels?.addFloor ?? 'Add a floor above'}\n onClick={() => addFloorAt()}\n >\n {renderIcon(icons?.addFloor ?? faPlus)}\n </button>\n )}\n </div>\n )}\n\n <div\n ref={canvasWrapRef}\n className={cx(\n styles.roomDrawerCanvasWrapper,\n editable && tool !== 'select' && styles.roomDrawerCanvasWrapperDrawing,\n classNames?.canvasWrapper,\n )}\n style={{ height: canvasHeight, ...slotStyles?.canvasWrapper }}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={(e) => finishGesture(e, false)}\n onPointerCancel={(e) => finishGesture(e, true)}\n >\n <PlanCanvas\n ref={svgRef}\n floor={floor}\n underlay={underlayFloor}\n rooms={rooms}\n view={view}\n size={size}\n selection={selection}\n hovered={hovered}\n interactiveRooms={false}\n showGrid={gridVisible}\n gridSize={gridSize}\n showRoomLabels\n formatArea={formatArea}\n getRoomColor={getRoomColor}\n renderRoomLabel={renderRoomLabel}\n overlay={overlay}\n classNames={classNames}\n styles={slotStyles}\n />\n </div>\n\n {showInspector && editable && (\n <div className={cx(styles.roomDrawerInspector, classNames?.inspector)} style={slotStyles?.inspector}>\n <span\n className={cx(\n styles.roomDrawerInspectorKind,\n selection.length === 0 && styles.roomDrawerInspectorKindEmpty,\n classNames?.inspectorLabel,\n )}\n >\n {selectionNoun}\n </span>\n\n {selection.length === 0 && (\n <>\n <Input\n className={cx(styles.roomDrawerInspectorName, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n label={labels?.floorName ?? 'Floor name'}\n value={floor.label}\n onChange={(next) => renameFloorById(floor.id, next)}\n />\n <NumberInput\n className={cx(styles.roomDrawerInspectorField, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n label={labels?.floorHeight ?? 'Height (mm)'}\n title={labels?.storeyHeightHint ?? 'Storey height. The floors above move to sit on top of it.'}\n value={floor.height}\n decimalScale={0}\n min={MIN_FLOOR_HEIGHT}\n step={100}\n showArrows\n onChange={(next) => {\n if (next === undefined) return;\n setFloorHeightById(floor.id, next);\n }}\n />\n {/* The strip lists storeys top first, so \"up\" is a higher index. Only\n offered when there is somewhere to go. */}\n {plan.floors.length > 1 && (\n <>\n <button\n type='button'\n className={cx(styles.roomDrawerInspectorAction, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n title={labels?.moveFloorUp ?? 'Move this floor up'}\n aria-label={labels?.moveFloorUp ?? 'Move this floor up'}\n disabled={floorIndex(plan, floor.id) >= plan.floors.length - 1}\n onClick={() => moveFloorTo(floor.id, floorIndex(plan, floor.id) + 1)}\n >\n {renderIcon(icons?.moveFloorUp ?? faAngleUp)}\n </button>\n <button\n type='button'\n className={cx(styles.roomDrawerInspectorAction, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n title={labels?.moveFloorDown ?? 'Move this floor down'}\n aria-label={labels?.moveFloorDown ?? 'Move this floor down'}\n disabled={floorIndex(plan, floor.id) <= 0}\n onClick={() => moveFloorTo(floor.id, floorIndex(plan, floor.id) - 1)}\n >\n {renderIcon(icons?.moveFloorDown ?? faAngleDown)}\n </button>\n </>\n )}\n <button\n type='button'\n className={cx(styles.roomDrawerInspectorAction, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n title={labels?.duplicateFloor ?? 'Copy this floor to a new one above'}\n onClick={() => duplicateActiveFloor()}\n >\n {renderIcon(icons?.duplicateFloor ?? faClone)}\n </button>\n {plan.floors.length > 1 && (\n <ActionIcon\n className={styles.roomDrawerInspectorDelete}\n icon={icons?.delete ?? faTrash}\n title={labels?.deleteFloor ?? 'Delete this floor'}\n onClick={() => removeFloorById()}\n />\n )}\n </>\n )}\n\n {selectedRoom?.meta && (\n <>\n <Input\n className={cx(styles.roomDrawerInspectorName, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n label={labels?.roomName ?? 'Room name'}\n value={selectedRoom.meta.name}\n onChange={(next) => {\n const room = selectedRoom.meta!;\n commitEdit({ ...floor, rooms: { ...floor.rooms, [room.id]: { ...room, name: next } } }, 'edit-room', [\n room.id,\n ]);\n }}\n />\n <span className={cx(styles.roomDrawerInspectorLabel, classNames?.inspectorLabel)}>\n {formatArea(selectedRoom.area)}\n </span>\n </>\n )}\n\n {selectedWall && (\n <button\n type='button'\n className={cx(styles.roomDrawerInspectorAction, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n onClick={addCornerToSelectedWall}\n title={labels?.addCorner ?? 'Add a corner to this wall'}\n >\n {labels?.addCorner ?? 'Add corner'}\n </button>\n )}\n\n {selectedWall && (\n <NumberInput\n className={cx(styles.roomDrawerInspectorField, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n label={labels?.thickness ?? 'Thickness (mm)'}\n value={selectedWall.thickness}\n decimalScale={0}\n min={10}\n showArrows\n step={10}\n onChange={(next) => {\n if (next === undefined) return;\n commitEdit(\n {\n ...floor,\n walls: { ...floor.walls, [selectedWall.id]: { ...selectedWall, thickness: next } },\n },\n 'edit-wall',\n [selectedWall.id],\n );\n }}\n />\n )}\n\n {selectedOpening && (\n <>\n <NumberInput\n className={cx(styles.roomDrawerInspectorField, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n label={labels?.width ?? 'Width (mm)'}\n value={selectedOpening.width}\n decimalScale={0}\n min={100}\n step={50}\n showArrows\n onChange={(next) => {\n if (next === undefined) return;\n commitEdit(updateOpening(floor, selectedOpening.id, { width: next }).floor, 'edit-opening', [\n selectedOpening.id,\n ]);\n }}\n />\n <NumberInput\n className={cx(styles.roomDrawerInspectorField, classNames?.inspectorField)}\n style={slotStyles?.inspectorField}\n label={labels?.sill ?? 'Sill (mm)'}\n title={\n labels?.sillHeightHint ??\n 'How far above the floor the opening starts. Stored for a 3D view; it does not change the plan.'\n }\n showArrows\n value={selectedOpening.sill}\n decimalScale={0}\n min={0}\n step={50}\n onChange={(next) => {\n if (next === undefined) return;\n commitEdit(updateOpening(floor, selectedOpening.id, { sill: next }).floor, 'edit-opening', [\n selectedOpening.id,\n ]);\n }}\n />\n </>\n )}\n\n {selection.length > 0 && (\n <ActionIcon\n className={styles.roomDrawerInspectorDelete}\n icon={icons?.delete ?? faTrash}\n title={labels?.deleteSelection ?? 'Delete selection'}\n onClick={deleteSelection}\n />\n )}\n </div>\n )}\n\n {showFileControls && editable && (\n <input\n ref={fileInputRef}\n type='file'\n accept='application/json,.json'\n className={styles.roomDrawerFileInput}\n tabIndex={-1}\n aria-hidden='true'\n onChange={(event) => {\n const file = event.target.files?.[0];\n // Cleared so picking the same file twice running still fires a change.\n event.target.value = '';\n if (file) openFile(file);\n }}\n />\n )}\n\n {showHint && (importReport !== null || hint) && (\n <div\n className={cx(styles.roomDrawerHint, importReport !== null && styles.roomDrawerHintReport, classNames?.hint)}\n style={slotStyles?.hint}\n role={importReport !== null ? 'status' : undefined}\n >\n {importReport === null\n ? hint\n : importReport.length === 0\n ? (labels?.importFailed ?? 'That file could not be read as a floor plan')\n : importReport.join(' · ')}\n </div>\n )}\n </div>\n );\n});\n\nexport default RoomDrawer;\n"],"mappings":"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"}
|