{"version":3,"file":"roomDrawer.mjs","names":[],"sources":["../../../../package/common/roomDrawer/roomDrawer.tsx"],"sourcesContent":["import {\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, useLabels } 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  RoomDrawerLabels,\n  RoomDrawerProps,\n  RoomDrawerTool,\n} from './roomDrawer.types';\nimport styles from './roomDrawer.module.pcss';\nimport { ThemeColor } from '../themeProvider/color.types';\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\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\nfunction defaultFormatAngle(degrees: number): string {\n  return `${Math.round(degrees * 10) / 10}°`;\n}\n\nfunction defaultFormatZoom(zoom: number): string {\n  return `${Math.round(zoom * 1000) / 10}%`;\n}\n\n/** Radians to a signed degree reading in (-180, 180], which is how a plan angle reads. */\nfunction toDegrees(radians: number): number {\n  const deg = ((((radians * 180) / Math.PI) % 360) + 360) % 360;\n  return deg > 180 ? deg - 360 : deg;\n}\n\nconst DEFAULT_LABELS: Required<RoomDrawerLabels> = {\n  tools: 'Drawing tools',\n  storeyHeightHint: 'Storey height. The floors above move to sit on top of it.',\n  sillHeightHint: 'How far above the floor the opening starts. Stored for a 3D view; it does not change the plan.',\n  select: 'Select and edit',\n  wall: 'Draw walls',\n  rect: 'Draw a room',\n  measure: 'Measure',\n  door: 'Add a door',\n  window: 'Add a window',\n  archway: 'Add an archway',\n  undo: 'Undo',\n  redo: 'Redo',\n  download: 'Download plan',\n  upload: 'Open a plan file',\n  underlay: 'Show the floor below',\n  addFloor: 'Add a floor above',\n  floorName: 'Floor name',\n  duplicateFloor: 'Copy this floor to a new one above',\n  deleteFloor: 'Delete this floor',\n  floorHeight: 'Height (mm)',\n  moveFloorUp: 'Move this floor up',\n  moveFloorDown: 'Move this floor down',\n  newRoomName: (n) => `Room ${n}`,\n  newFloorName: (n) => `Floor ${n}`,\n  copiedFloorName: (name) => `${name} copy`,\n  initialFloorName: 'Ground floor',\n  selectedFloor: 'Floor',\n  importFailed: 'That file could not be read as a floor plan',\n  snap: 'Snap to grid and geometry',\n  angleLock: (angle) => `Lock to ${angle}° angles`,\n  grid: 'Show grid',\n  zoomIn: 'Zoom in',\n  zoomOut: 'Zoom out',\n  fit: 'Fit to content',\n  roomName: 'Room name',\n  addCorner: 'Add corner',\n  thickness: 'Thickness (mm)',\n  width: 'Width (mm)',\n  sill: 'Sill (mm)',\n  deleteSelection: 'Delete selection',\n  selectedCount: (count) => `${count} selected`,\n  selectedRoom: 'Room',\n  selectedWall: 'Wall',\n  selectedCorner: 'Corner',\n  selectedDoor: 'Door',\n  selectedWindow: 'Window',\n  selectedArchway: 'Archway',\n  selectedGap: 'Gap',\n  hintSelect:\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  hintSelectSingle:\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  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  hintRect: 'Drag to draw a room',\n  hintMeasure: 'Drag to measure · Drag an end or the line to adjust it · Delete removes one, Escape clears them',\n  hintOpening: 'Click a wall to cut an opening into it',\n  relativeAngle: (angle) => `${angle} rel`,\n};\n\nfunction RoomDrawer(props: RoomDrawerProps) {\n  const {\n    ref,\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    formatAngle = defaultFormatAngle,\n    formatZoom = defaultFormatZoom,\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  const text = useLabels('RoomDrawer', labels, DEFAULT_LABELS);\n  // The hooks below list these rather than `text`, which is a new object every render.\n  const { newRoomName, newFloorName, copiedFloorName, initialFloorName, relativeAngle } = text;\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({ floorLabel: initialFloorName }),\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  // `measured` holds the fit back until the canvas has been measured: the fit runs in the same\n  // effect flush as the first measurement, and would otherwise fit the plan to this guess.\n  const [size, setSize] = useState({ width: 800, height: 420, measured: false });\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), measured: true });\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), measured: true });\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.measured) return;\n    didFitRef.current = true;\n    fitToContent();\n  }, [fitOnMount, floor, size.measured, 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, { nameFor: newRoomName }).floor;\n      return emit(named, reason, true, targetIds, options);\n    },\n    [emit, newRoomName],\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        nameFor: newFloorName,\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, newFloorName],\n  );\n\n  const duplicateActiveFloor = useCallback(() => {\n    const { plan: next, floor: copy } = duplicateFloor(plan, floor.id, { label: copiedFloorName(floor.label) });\n    if (!copy) return null;\n    replacePlan(next, 'duplicate-floor');\n    selectFloor(copy.id);\n    return copy.id;\n  }, [plan, floor.id, floor.label, replacePlan, selectFloor, copiedFloorName]);\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({ floorLabel: initialFloorName }), '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      initialFloorName,\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(toDegrees(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)} · ${\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                ? relativeAngle(\n                    formatAngle(toDegrees(Math.atan2(cursor.y - from.y, cursor.x - from.x) - chainReferenceAngle)),\n                  )\n                : formatAngle(toDegrees(Math.atan2(cursor.y - from.y, cursor.x - from.x)))\n            }`}\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(toDegrees(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(toDegrees(Math.atan2(head.y - tail.y, head.x - tail.x)))}`}\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(toDegrees(Math.atan2(head.y - tail.y, head.x - tail.x)))}`}\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    formatAngle,\n    relativeAngle,\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: text.select, key: 'V' },\n    wall: { icon: faMinus, label: text.wall, key: 'W' },\n    rect: { icon: faSquare, label: text.rect, key: 'R' },\n    door: { icon: faDoorOpen, label: text.door, key: 'D' },\n    window: { icon: faWindowMaximize, label: text.window, key: 'N' },\n    archway: { icon: faExpand, label: text.archway, key: 'A' },\n    measure: { icon: faRulerCombined, label: text.measure, key: 'M' },\n  };\n\n  const hint = (() => {\n    if (!editable) return '';\n    if (tool === 'wall') return text.hintWall;\n    if (tool === 'rect') return text.hintRect;\n    if (tool === 'measure') return text.hintMeasure;\n    if (OPENING_TOOL_KIND[tool]) return text.hintOpening;\n    return multiSelect ? text.hintSelect : text.hintSelectSingle;\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 text.selectedFloor;\n    if (selection.length > 1) return text.selectedCount(selection.length);\n    if (selectedOpening) {\n      const nouns: Record<OpeningKind, string> = {\n        [OpeningKind.Door]: text.selectedDoor,\n        [OpeningKind.Window]: text.selectedWindow,\n        [OpeningKind.Archway]: text.selectedArchway,\n        [OpeningKind.Gap]: text.selectedGap,\n      };\n      return nouns[selectedOpening.kind];\n    }\n    if (selectedWall) return text.selectedWall;\n    if (single?.type === 'room') return text.selectedRoom;\n    return text.selectedCorner;\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={text.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: text.snap,\n                    key: 'S',\n                  },\n                  {\n                    on: angleLockEnabled,\n                    toggle: () => setAngleLockEnabled((v) => !v),\n                    icon: icons?.angleLock ?? faDraftingCompass,\n                    label: text.angleLock(constrainAngle),\n                    key: 'L',\n                  },\n                  {\n                    on: gridVisible,\n                    toggle: () => setGridVisible((v) => !v),\n                    icon: icons?.grid ?? faBorderAll,\n                    label: text.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: text.underlay,\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={text.undo}\n                aria-label={text.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={text.redo}\n                aria-label={text.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={text.download}\n                aria-label={text.download}\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={text.upload}\n                  aria-label={text.upload}\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={text.zoomOut}\n              aria-label={text.zoomOut}\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={text.fit}\n              onClick={() => fitToContent()}\n            >\n              {formatZoom(view.zoom)}\n            </button>\n            <button\n              type='button'\n              className={cx(styles.roomDrawerViewButton, classNames?.viewButton)}\n              style={slotStyles?.viewButton}\n              title={text.zoomIn}\n              aria-label={text.zoomIn}\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={text.selectedFloor}\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={text.addFloor}\n              aria-label={text.addFloor}\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        // The middle button pans the plan. Left alone, the browser starts its autoscroll on the\n        // same press and scrolls the page away underneath the pan.\n        onMouseDown={(e) => {\n          if (!disabled && e.button === 1) e.preventDefault();\n        }}\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={text.floorName}\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={text.floorHeight}\n                title={text.storeyHeightHint}\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={text.moveFloorUp}\n                    aria-label={text.moveFloorUp}\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={text.moveFloorDown}\n                    aria-label={text.moveFloorDown}\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={text.duplicateFloor}\n                onClick={() => duplicateActiveFloor()}\n              >\n                {renderIcon(icons?.duplicateFloor ?? faClone)}\n              </button>\n              {plan.floors.length > 1 && (\n                <ActionIcon\n                  color={ThemeColor.Error}\n                  className={styles.roomDrawerInspectorDelete}\n                  icon={icons?.delete ?? faTrash}\n                  title={text.deleteFloor}\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={text.roomName}\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            >\n              {text.addCorner}\n            </button>\n          )}\n\n          {selectedWall && (\n            <NumberInput\n              className={cx(styles.roomDrawerInspectorField, classNames?.inspectorField)}\n              style={slotStyles?.inspectorField}\n              label={text.thickness}\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={text.width}\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={text.sill}\n                title={text.sillHeightHint}\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              color={ThemeColor.Error}\n              className={styles.roomDrawerInspectorDelete}\n              icon={icons?.delete ?? faTrash}\n              title={text.deleteSelection}\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 ? hint : importReport.length === 0 ? text.importFailed : importReport.join(' · ')}\n        </div>\n      )}\n    </div>\n  );\n}\n\nexport default RoomDrawer;\n"],"mappings":"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"}