@pascal-app/editor 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (624) hide show
  1. package/README.md +106 -0
  2. package/package.json +42 -38
  3. package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
  4. package/src/components/editor/action-menu-button.tsx +83 -0
  5. package/src/components/editor/bake-thumbnail.tsx +307 -5
  6. package/src/components/editor/capture-level-picker.tsx +72 -0
  7. package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
  8. package/src/components/editor/construction-reveal-audio.tsx +40 -0
  9. package/src/components/editor/custom-camera-controls.tsx +275 -57
  10. package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
  11. package/src/components/editor/duplicate-node.ts +244 -0
  12. package/src/components/editor/editor-layout-mobile.tsx +37 -26
  13. package/src/components/editor/editor-layout-v2.tsx +132 -59
  14. package/src/components/editor/export-manager.tsx +47 -26
  15. package/src/components/editor/first-person/build-collider-world.test.ts +320 -0
  16. package/src/components/editor/first-person/build-collider-world.ts +28 -7
  17. package/src/components/editor/first-person/ground-holes.test.ts +128 -0
  18. package/src/components/editor/first-person/ground-holes.ts +130 -0
  19. package/src/components/editor/first-person/mechanism-targets.test.ts +162 -0
  20. package/src/components/editor/first-person/mechanism-targets.ts +88 -0
  21. package/src/components/editor/first-person-controls.tsx +220 -9
  22. package/src/components/editor/floating-action-menu.tsx +63 -207
  23. package/src/components/editor/floor-aware-camera-controls.test.ts +466 -0
  24. package/src/components/editor/floor-aware-camera-controls.ts +324 -0
  25. package/src/components/editor/floor-openings-overlay.tsx +243 -0
  26. package/src/components/editor/floor-region-controls.tsx +846 -0
  27. package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
  28. package/src/components/editor/floor-surface.test.ts +165 -0
  29. package/src/components/editor/floor-surface.ts +182 -0
  30. package/src/components/editor/floorplan-background-selection.test.ts +11 -2
  31. package/src/components/editor/floorplan-background-selection.ts +1 -3
  32. package/src/components/editor/floorplan-mode-coordinator.tsx +2 -2
  33. package/src/components/editor/floorplan-panel.tsx +414 -363
  34. package/src/components/editor/follow-pascal.test.tsx +912 -0
  35. package/src/components/editor/follow-pascal.tsx +354 -0
  36. package/src/components/editor/footprint-height-handle.tsx +204 -0
  37. package/src/components/editor/group-actions.ts +56 -2
  38. package/src/components/editor/group-move-3d.ts +2 -0
  39. package/src/components/editor/group-rotate-handle.tsx +2 -0
  40. package/src/components/editor/group-transform-shared.test.ts +460 -0
  41. package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
  42. package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
  43. package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
  44. package/src/components/editor/handles/use-handle-drag.ts +269 -197
  45. package/src/components/editor/index.tsx +409 -197
  46. package/src/components/editor/join-walls-pill.tsx +44 -0
  47. package/src/components/editor/node-action-menu.tsx +68 -70
  48. package/src/components/editor/node-arrow-handles.tsx +25 -6
  49. package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
  50. package/src/components/editor/open-wall-ends-hint.tsx +89 -0
  51. package/src/components/editor/quick-measurement-hud.tsx +3 -2
  52. package/src/components/editor/registry-action-contributions.test.tsx +63 -0
  53. package/src/components/editor/registry-action-contributions.tsx +80 -1
  54. package/src/components/editor/riser-diagram-panel.tsx +14 -7
  55. package/src/components/editor/room-controls.tsx +847 -0
  56. package/src/components/editor/room-floor-highlight.tsx +128 -0
  57. package/src/components/editor/room-handles-2d.tsx +354 -0
  58. package/src/components/editor/room-handles.tsx +675 -0
  59. package/src/components/editor/room-highlight.tsx +233 -0
  60. package/src/components/editor/room-labels.test.ts +850 -0
  61. package/src/components/editor/room-labels.tsx +1890 -0
  62. package/src/components/editor/room-transform-ghost.tsx +256 -0
  63. package/src/components/editor/room-webgpu-materials.test.ts +27 -0
  64. package/src/components/editor/selection-manager.tsx +474 -419
  65. package/src/components/editor/slab-hole-highlights.tsx +5 -2
  66. package/src/components/editor/snapshot-capture-overlay.tsx +7 -2
  67. package/src/components/editor/snapshot-capture.test.ts +119 -60
  68. package/src/components/editor/snapshot-capture.ts +36 -5
  69. package/src/components/editor/thumbnail-generator.test.ts +654 -0
  70. package/src/components/editor/thumbnail-generator.tsx +1103 -223
  71. package/src/components/editor/use-floorplan-background-placement.ts +34 -12
  72. package/src/components/editor/vector-edge-extractor.tsx +121 -0
  73. package/src/components/editor/views/host-view.tsx +61 -0
  74. package/src/components/editor/views/plugins-view.tsx +663 -0
  75. package/src/components/editor/views/scene-region.tsx +59 -0
  76. package/src/components/editor/views/use-editor-views.ts +57 -0
  77. package/src/components/editor/views/view-bar.test.tsx +54 -0
  78. package/src/components/editor/views/view-bar.tsx +343 -0
  79. package/src/components/editor/wall-justification.test.ts +140 -0
  80. package/src/components/editor/wall-move-side-handles.tsx +43 -142
  81. package/src/components/editor/wall-push-arrow.tsx +92 -0
  82. package/src/components/editor/wall-region-handles.tsx +509 -0
  83. package/src/components/editor/wall-snap-beacon-layer.tsx +8 -4
  84. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
  85. package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
  86. package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
  87. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
  88. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +2 -2
  89. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
  90. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +116 -106
  91. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
  92. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
  93. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
  94. package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
  95. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
  96. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
  97. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +319 -233
  98. package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
  99. package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
  100. package/src/components/editor-2d/svg-paths.ts +12 -15
  101. package/src/components/point-ask/bubble.tsx +538 -0
  102. package/src/components/point-ask/hover-chip.tsx +91 -0
  103. package/src/components/point-ask/mode-pill.tsx +92 -0
  104. package/src/components/point-ask/optional-motion.ts +22 -0
  105. package/src/components/point-ask/outline-corners.tsx +93 -0
  106. package/src/components/point-ask/pins.tsx +711 -0
  107. package/src/components/point-ask/point-ask-layer.tsx +83 -0
  108. package/src/components/point-ask/point-ask-styles.ts +105 -0
  109. package/src/components/point-ask/region-marquee-styles.ts +15 -0
  110. package/src/components/point-ask/region-marquee.tsx +64 -0
  111. package/src/components/procedural-items/inspector.tsx +40 -15
  112. package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
  113. package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
  114. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
  115. package/src/components/systems/roof/roof-edit-system.tsx +1 -0
  116. package/src/components/systems/zone/zone-system.test.ts +47 -0
  117. package/src/components/systems/zone/zone-system.tsx +29 -68
  118. package/src/components/tools/distribution/connection-compatibility.test.ts +34 -0
  119. package/src/components/tools/distribution/connection-compatibility.ts +43 -0
  120. package/src/components/tools/distribution/distribution-public.ts +9 -0
  121. package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
  122. package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
  123. package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
  124. package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
  125. package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
  126. package/src/components/tools/distribution/draw-alignment.ts +35 -0
  127. package/src/components/tools/distribution/ports.test.ts +56 -0
  128. package/src/components/tools/distribution/ports.ts +378 -0
  129. package/src/components/tools/distribution/run-cursor.test.ts +135 -0
  130. package/src/components/tools/distribution/run-cursor.ts +36 -0
  131. package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
  132. package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
  133. package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
  134. package/src/components/tools/distribution/run-port-snap.ts +25 -0
  135. package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
  136. package/src/components/tools/distribution/run-screen-direction.ts +30 -0
  137. package/src/components/tools/elevator/elevator-tool.tsx +3 -6
  138. package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
  139. package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
  140. package/src/components/tools/item/placement-history.test.tsx +512 -0
  141. package/src/components/tools/item/placement-strategies.test.ts +46 -1
  142. package/src/components/tools/item/placement-strategies.ts +11 -10
  143. package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
  144. package/src/components/tools/item/use-draft-node.test.tsx +432 -1
  145. package/src/components/tools/item/use-draft-node.ts +166 -95
  146. package/src/components/tools/item/use-placement-coordinator.tsx +148 -95
  147. package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
  148. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
  149. package/src/components/tools/registry/item-surface-move.test.ts +62 -63
  150. package/src/components/tools/registry/item-surface-move.ts +2 -0
  151. package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
  152. package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
  153. package/src/components/tools/registry/move-registry-node-tool.tsx +66 -30
  154. package/src/components/tools/select/box-select-tool.tsx +2 -12
  155. package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
  156. package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
  157. package/src/components/tools/select/select-candidates.test.ts +37 -4
  158. package/src/components/tools/select/select-candidates.ts +15 -11
  159. package/src/components/tools/shared/cursor-sphere.tsx +36 -12
  160. package/src/components/tools/site/site-boundary-editor.tsx +1 -2
  161. package/src/components/tools/tool-manager.tsx +59 -24
  162. package/src/components/tools/wall/wall-drafting.test.ts +175 -47
  163. package/src/components/tools/zone/zone-tool.tsx +4 -12
  164. package/src/components/ui/action-menu/action-button.tsx +9 -1
  165. package/src/components/ui/action-menu/camera-actions.tsx +9 -4
  166. package/src/components/ui/action-menu/control-modes.tsx +15 -42
  167. package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
  168. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
  169. package/src/components/ui/action-menu/index.tsx +56 -33
  170. package/src/components/ui/action-menu/measurement-control.tsx +14 -19
  171. package/src/components/ui/action-menu/placement.ts +20 -0
  172. package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
  173. package/src/components/ui/action-menu/view-toggles.tsx +47 -42
  174. package/src/components/ui/build-panel/build-panel.tsx +242 -0
  175. package/src/components/ui/command-palette/editor-commands.tsx +7 -42
  176. package/src/components/ui/command-palette/index.tsx +50 -39
  177. package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
  178. package/src/components/ui/controls/action-button.tsx +5 -2
  179. package/src/components/ui/controls/finish-swatch.tsx +110 -0
  180. package/src/components/ui/controls/material-paint-panel.tsx +97 -26
  181. package/src/components/ui/controls/material-picker.tsx +4 -1
  182. package/src/components/ui/controls/metric-control.tsx +1 -1
  183. package/src/components/ui/controls/scene-material-list.tsx +4 -32
  184. package/src/components/ui/controls/segmented-control.tsx +21 -8
  185. package/src/components/ui/controls/select-control.tsx +65 -0
  186. package/src/components/ui/controls/slider-control.tsx +1 -1
  187. package/src/components/ui/controls/toggle-control.tsx +3 -3
  188. package/src/components/ui/controls/tool-options-panel.tsx +1 -1
  189. package/src/components/ui/floating-level-selector.tsx +159 -59
  190. package/src/components/ui/helpers/building-helper.tsx +4 -1
  191. package/src/components/ui/helpers/contextual-helper-panel.tsx +181 -72
  192. package/src/components/ui/helpers/helper-manager.test.tsx +121 -0
  193. package/src/components/ui/helpers/helper-manager.tsx +331 -14
  194. package/src/components/ui/helpers/item-helper.tsx +17 -1
  195. package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
  196. package/src/components/ui/icon-ref.tsx +45 -1
  197. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  198. package/src/components/ui/node-icon-image.tsx +16 -0
  199. package/src/components/ui/panels/collections/collections-popover.tsx +28 -22
  200. package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
  201. package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
  202. package/src/components/ui/panels/mobile-panel-sheet.tsx +4 -4
  203. package/src/components/ui/panels/mobile-selection-bar.tsx +8 -5
  204. package/src/components/ui/panels/multi-field-value.test.ts +26 -3
  205. package/src/components/ui/panels/multi-field-value.ts +6 -3
  206. package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
  207. package/src/components/ui/panels/multi-selection-panel.tsx +161 -5
  208. package/src/components/ui/panels/node-display.ts +8 -3
  209. package/src/components/ui/panels/panel-manager.tsx +103 -68
  210. package/src/components/ui/panels/panel-wrapper.tsx +130 -62
  211. package/src/components/ui/panels/parametric-field-control.tsx +3 -3
  212. package/src/components/ui/panels/parametric-inspector.tsx +7 -7
  213. package/src/components/ui/panels/reference-panel.tsx +2 -3
  214. package/src/components/ui/panels/room-construction-rows.tsx +774 -0
  215. package/src/components/ui/panels/room-name-options.test.ts +55 -0
  216. package/src/components/ui/panels/room-name-options.ts +47 -0
  217. package/src/components/ui/panels/room-openings-field.tsx +110 -0
  218. package/src/components/ui/panels/room-panel.tsx +790 -0
  219. package/src/components/ui/panels/shape-choice.tsx +58 -0
  220. package/src/components/ui/panels/wall-region-list.tsx +109 -0
  221. package/src/components/ui/primitives/badge.tsx +8 -0
  222. package/src/components/ui/primitives/color-dot.tsx +36 -24
  223. package/src/components/ui/primitives/sidebar.tsx +3 -1
  224. package/src/components/ui/right-stack.test.ts +29 -0
  225. package/src/components/ui/right-stack.tsx +164 -0
  226. package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
  227. package/src/components/ui/sidebar/icon-rail.tsx +8 -3
  228. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  229. package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
  230. package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
  231. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
  232. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
  233. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
  234. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +138 -27
  235. package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
  236. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
  237. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
  238. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
  239. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
  240. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
  241. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
  242. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
  243. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
  244. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
  245. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
  246. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
  247. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
  248. package/src/components/ui/sidebar/panels/site-panel/index.tsx +181 -223
  249. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
  250. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
  251. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
  252. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
  253. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
  254. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
  255. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
  256. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
  257. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
  258. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
  259. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +13 -37
  260. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
  261. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
  262. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
  263. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
  264. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +2 -4
  265. package/src/components/ui/sidebar/tab-bar.tsx +14 -4
  266. package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
  267. package/src/components/viewer/floorplan-compass-button.tsx +9 -1
  268. package/src/components/viewer/floorplan-preview.tsx +5 -6
  269. package/src/components/viewer/viewer-controls-bar.tsx +18 -6
  270. package/src/components/viewer/viewer-hover-label.tsx +34 -0
  271. package/src/components/viewer/viewer-rooms.tsx +191 -0
  272. package/src/components/viewer/viewer-scene-header.tsx +181 -145
  273. package/src/components/viewer/viewer-selection-manager.tsx +124 -0
  274. package/src/components/viewer/viewer-stage.tsx +2 -2
  275. package/src/components/viewer-zone-system.tsx +3 -8
  276. package/src/hooks/use-auto-save.integration.test.tsx +215 -43
  277. package/src/hooks/use-auto-save.ts +60 -9
  278. package/src/hooks/use-ceiling-events.test.ts +123 -118
  279. package/src/hooks/use-grid-events.test.ts +105 -105
  280. package/src/hooks/use-keyboard.test.ts +243 -19
  281. package/src/hooks/use-keyboard.ts +118 -83
  282. package/src/hooks/use-open-wall-ends.test.ts +99 -0
  283. package/src/hooks/use-open-wall-ends.ts +238 -0
  284. package/src/hooks/use-placement-notice.test.ts +71 -0
  285. package/src/hooks/use-placement-notice.ts +73 -0
  286. package/src/hooks/use-selected-room.ts +143 -0
  287. package/src/index.tsx +312 -58
  288. package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
  289. package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
  290. package/src/lib/add-storey.test.ts +177 -0
  291. package/src/lib/add-storey.ts +59 -0
  292. package/src/lib/below-grade.test.tsx +171 -0
  293. package/src/lib/below-grade.ts +133 -0
  294. package/src/lib/camera-facing.test.ts +24 -0
  295. package/src/lib/camera-facing.ts +24 -0
  296. package/src/lib/ceiling-edit-session.test.ts +321 -0
  297. package/src/lib/ceiling-edit-session.ts +206 -0
  298. package/src/lib/ceiling-region-session.test.ts +179 -0
  299. package/src/lib/continuation.test.ts +19 -1
  300. package/src/lib/continuation.ts +25 -4
  301. package/src/lib/direct-manipulation.ts +2 -0
  302. package/src/lib/door-interaction.ts +14 -0
  303. package/src/lib/download-project-file.ts +17 -0
  304. package/src/lib/editor-level-display.test.ts +96 -0
  305. package/src/lib/editor-level-display.ts +55 -0
  306. package/src/lib/editor-views.ts +178 -0
  307. package/src/lib/export-geometry-wait.test.ts +90 -0
  308. package/src/lib/export-texture-utils.ts +2 -4
  309. package/src/lib/floor-edit-session.ts +153 -0
  310. package/src/lib/floor-footprints.test.ts +563 -0
  311. package/src/lib/floor-footprints.ts +395 -0
  312. package/src/lib/floor-foundation-support.test.ts +284 -0
  313. package/src/lib/floor-opening-draft.ts +218 -0
  314. package/src/lib/floor-openings.test.ts +216 -0
  315. package/src/lib/floor-region-geometry.ts +191 -0
  316. package/src/lib/floor-region-session.test.ts +485 -0
  317. package/src/lib/floor-region-session.ts +589 -0
  318. package/src/lib/floor-region-snap.ts +101 -0
  319. package/src/lib/floor-step-hover.test.ts +32 -0
  320. package/src/lib/floorplan/annotation-visibility.ts +17 -0
  321. package/src/lib/floorplan/dimension-drive.test.ts +360 -0
  322. package/src/lib/floorplan/dimension-drive.ts +479 -0
  323. package/src/lib/floorplan/floorplan-export.test.ts +12 -8
  324. package/src/lib/floorplan/floorplan-export.tsx +32 -8
  325. package/src/lib/floorplan/floorplan-extension.ts +32 -0
  326. package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
  327. package/src/lib/floorplan/floorplan-mode.ts +2 -0
  328. package/src/lib/floorplan/floorplan-pdfkit-document.ts +363 -27
  329. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +177 -8
  330. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +60 -25
  331. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
  332. package/src/lib/floorplan/floorplan-readonly.ts +6 -0
  333. package/src/lib/floorplan/geometry.ts +13 -6
  334. package/src/lib/floorplan/index.ts +0 -9
  335. package/src/lib/floorplan/items.ts +5 -2
  336. package/src/lib/floorplan/marks.test.ts +213 -0
  337. package/src/lib/floorplan/marks.ts +238 -0
  338. package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
  339. package/src/lib/floorplan/open-wall-ends.ts +162 -0
  340. package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
  341. package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
  342. package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
  343. package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
  344. package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
  345. package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
  346. package/src/lib/floorplan/pdfkit-standard-fonts.d.ts +5 -0
  347. package/src/lib/floorplan/schedules.test.ts +189 -0
  348. package/src/lib/floorplan/schedules.ts +339 -0
  349. package/src/lib/floorplan/sheet-export.test.ts +124 -0
  350. package/src/lib/floorplan/sheet-export.ts +318 -0
  351. package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +768 -0
  352. package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
  353. package/src/lib/floorplan/site-plan/contributors.ts +97 -0
  354. package/src/lib/floorplan/site-plan/coverage.ts +230 -0
  355. package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
  356. package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
  357. package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
  358. package/src/lib/floorplan/site-plan/front-edge.ts +157 -0
  359. package/src/lib/floorplan/site-plan/geometry.test.ts +269 -0
  360. package/src/lib/floorplan/site-plan/geometry.ts +142 -0
  361. package/src/lib/floorplan/site-plan/index.ts +55 -0
  362. package/src/lib/floorplan/site-plan/site-annotations.test.ts +483 -0
  363. package/src/lib/floorplan/site-plan/site-annotations.ts +1084 -0
  364. package/src/lib/floorplan/site-plan/site-parts.ts +287 -0
  365. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
  366. package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
  367. package/src/lib/floorplan/site-plan/street-name.ts +302 -0
  368. package/src/lib/floorplan/site-plan/yards.test.ts +70 -0
  369. package/src/lib/floorplan/types.ts +1 -27
  370. package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
  371. package/src/lib/follow-pascal-exterior.test.ts +171 -0
  372. package/src/lib/follow-pascal-exterior.ts +39 -0
  373. package/src/lib/follow-pascal-presentation.test.ts +203 -0
  374. package/src/lib/follow-pascal-presentation.ts +129 -0
  375. package/src/lib/follow-pascal-sight.ts +84 -0
  376. package/src/lib/follow-pascal-targets.test.ts +406 -0
  377. package/src/lib/follow-pascal-targets.ts +198 -0
  378. package/src/lib/follow-pascal.test.ts +350 -0
  379. package/src/lib/follow-pascal.ts +284 -0
  380. package/src/lib/fresh-planar-placement.test.ts +148 -0
  381. package/src/lib/fresh-planar-placement.ts +114 -37
  382. package/src/lib/geometry-script/author.ts +157 -0
  383. package/src/lib/geometry-script/client.ts +159 -0
  384. package/src/lib/geometry-script/finish-scripts.ts +127 -0
  385. package/src/lib/geometry-script/protocol.ts +13 -0
  386. package/src/lib/gesture-lifecycle.test.ts +595 -0
  387. package/src/lib/gesture-lifecycle.ts +290 -0
  388. package/src/lib/glb-export-zone-holes.test.ts +41 -0
  389. package/src/lib/glb-export.test.ts +636 -126
  390. package/src/lib/glb-export.ts +241 -120
  391. package/src/lib/history.test.ts +18 -7
  392. package/src/lib/history.ts +7 -0
  393. package/src/lib/hud-title.ts +109 -0
  394. package/src/lib/hydration-gated-storage.ts +49 -0
  395. package/src/lib/ifc-export.test.ts +174 -0
  396. package/src/lib/ifc-export.ts +201 -0
  397. package/src/lib/ifc-parts.ts +15 -0
  398. package/src/lib/inspector-expanded.test.ts +44 -0
  399. package/src/lib/inspector-expanded.ts +36 -0
  400. package/src/lib/interaction/scope.ts +22 -0
  401. package/src/lib/keyboard-pan.test.ts +129 -7
  402. package/src/lib/keyboard-pan.ts +161 -12
  403. package/src/lib/level-selection.test.ts +57 -0
  404. package/src/lib/level-selection.ts +16 -1
  405. package/src/lib/live-gesture-round-trip.test.ts +360 -0
  406. package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
  407. package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
  408. package/src/lib/lot/clean-ring.test.ts +187 -0
  409. package/src/lib/lot/clean-ring.ts +217 -0
  410. package/src/lib/lot/dossier.test.ts +202 -0
  411. package/src/lib/lot/dossier.ts +498 -0
  412. package/src/lib/lot/drop-in.test.ts +326 -0
  413. package/src/lib/lot/drop-in.ts +392 -0
  414. package/src/lib/lot/index.ts +43 -0
  415. package/src/lib/lot/lot-patch.test.ts +340 -0
  416. package/src/lib/lot/lot-patch.ts +394 -0
  417. package/src/lib/lot/parcel-provider.ts +43 -0
  418. package/src/lib/lot/terrain.test.ts +161 -0
  419. package/src/lib/lot/terrain.ts +266 -0
  420. package/src/lib/material-catalog-model.ts +3 -1
  421. package/src/lib/material-paint-panel-model.ts +9 -5
  422. package/src/lib/material-paint.ts +4 -0
  423. package/src/lib/measurement-kind.test.ts +6 -8
  424. package/src/lib/measurements.test.ts +23 -0
  425. package/src/lib/mezzanine-actions.test.ts +342 -0
  426. package/src/lib/mezzanine-draft.test.ts +501 -0
  427. package/src/lib/mezzanine-draft.ts +278 -0
  428. package/src/lib/mezzanine-messages.ts +26 -0
  429. package/src/lib/mezzanine-selection.test.ts +207 -0
  430. package/src/lib/mezzanine-stairs.ts +46 -0
  431. package/src/lib/model-export.ts +3 -1
  432. package/src/lib/node-action-movement.ts +17 -6
  433. package/src/lib/node-display-label.ts +37 -0
  434. package/src/lib/orbit-floor-clearance.test.ts +201 -0
  435. package/src/lib/orbit-floor-clearance.ts +154 -0
  436. package/src/lib/paint-commit-route.test.ts +37 -0
  437. package/src/lib/paint-commit-route.ts +17 -0
  438. package/src/lib/paint-eyedropper.test.tsx +165 -0
  439. package/src/lib/paint-eyedropper.ts +99 -0
  440. package/src/lib/paint-pass-through.test.ts +122 -0
  441. package/src/lib/paint-pass-through.ts +78 -0
  442. package/src/lib/paint-region-hover.ts +25 -0
  443. package/src/lib/paint-region-mode.ts +154 -0
  444. package/src/lib/paint-regions.test.ts +166 -0
  445. package/src/lib/paint-regions.ts +192 -0
  446. package/src/lib/paint-scope.test.ts +317 -35
  447. package/src/lib/paint-scope.ts +267 -25
  448. package/src/lib/panel-rows.ts +21 -0
  449. package/src/lib/parametric-node-update.ts +16 -7
  450. package/src/lib/plate-paint-affected.test.ts +300 -0
  451. package/src/lib/plate-paint-affected.ts +177 -0
  452. package/src/lib/plugin-chat.ts +65 -0
  453. package/src/lib/plugin-panels.test.ts +25 -0
  454. package/src/lib/plugin-panels.ts +94 -0
  455. package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
  456. package/src/lib/point-ask/__fixtures__/house.ts +81 -0
  457. package/src/lib/point-ask/ask-created.test.ts +195 -0
  458. package/src/lib/point-ask/ask-flow.test.ts +380 -0
  459. package/src/lib/point-ask/build-context.test.ts +173 -0
  460. package/src/lib/point-ask/build-context.ts +94 -0
  461. package/src/lib/point-ask/capture-crop.ts +129 -0
  462. package/src/lib/point-ask/choreography.ts +69 -0
  463. package/src/lib/point-ask/compare.test.ts +145 -0
  464. package/src/lib/point-ask/compare.ts +73 -0
  465. package/src/lib/point-ask/describe-target.test.ts +165 -0
  466. package/src/lib/point-ask/describe-target.ts +234 -0
  467. package/src/lib/point-ask/dictation.ts +79 -0
  468. package/src/lib/point-ask/edge-marker.test.ts +61 -0
  469. package/src/lib/point-ask/edge-marker.ts +27 -0
  470. package/src/lib/point-ask/frame-loop.ts +25 -0
  471. package/src/lib/point-ask/history-marks.test.ts +94 -0
  472. package/src/lib/point-ask/history-marks.ts +39 -0
  473. package/src/lib/point-ask/host-api.ts +32 -0
  474. package/src/lib/point-ask/outline-box.test.ts +30 -0
  475. package/src/lib/point-ask/outline-box.ts +11 -0
  476. package/src/lib/point-ask/overlay-metrics.ts +6 -0
  477. package/src/lib/point-ask/pick.test.ts +220 -0
  478. package/src/lib/point-ask/pick.ts +121 -0
  479. package/src/lib/point-ask/pin-machine.test.ts +494 -0
  480. package/src/lib/point-ask/pin-machine.ts +217 -0
  481. package/src/lib/point-ask/placement.test.ts +203 -0
  482. package/src/lib/point-ask/placement.ts +141 -0
  483. package/src/lib/point-ask/pointer-tracker.ts +34 -0
  484. package/src/lib/point-ask/projector.ts +83 -0
  485. package/src/lib/point-ask/region.test.ts +157 -0
  486. package/src/lib/point-ask/region.ts +136 -0
  487. package/src/lib/point-ask/retry.test.ts +217 -0
  488. package/src/lib/point-ask/retry.ts +37 -0
  489. package/src/lib/point-ask/scene-commit.test.ts +312 -0
  490. package/src/lib/point-ask/scene-commit.ts +32 -0
  491. package/src/lib/point-ask/session.ts +1391 -0
  492. package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
  493. package/src/lib/point-ask/spring-keyframes.ts +38 -0
  494. package/src/lib/point-ask/spring.test.ts +63 -0
  495. package/src/lib/point-ask/spring.ts +107 -0
  496. package/src/lib/point-ask/suggestions.test.ts +80 -0
  497. package/src/lib/point-ask/suggestions.ts +125 -0
  498. package/src/lib/point-ask/target-scope.test.ts +622 -0
  499. package/src/lib/point-ask/target-scope.ts +338 -0
  500. package/src/lib/point-ask/types.ts +48 -0
  501. package/src/lib/portable-export.test.ts +34 -1
  502. package/src/lib/portable-export.ts +119 -53
  503. package/src/lib/print-feature-thickness.ts +10 -1
  504. package/src/lib/print-roof-solids.test.ts +39 -1
  505. package/src/lib/print-roof-solids.ts +43 -10
  506. package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
  507. package/src/lib/print-shell-compiler.ts +1 -0
  508. package/src/lib/registered-draft-snap.ts +12 -0
  509. package/src/lib/roof-duplication.ts +10 -2
  510. package/src/lib/room-assembly-overlay.test.ts +275 -0
  511. package/src/lib/room-assembly-overlay.ts +265 -0
  512. package/src/lib/room-built-on.test.ts +379 -0
  513. package/src/lib/room-built-on.ts +138 -0
  514. package/src/lib/room-construction-commands.ts +712 -0
  515. package/src/lib/room-construction.test.ts +746 -0
  516. package/src/lib/room-construction.ts +385 -0
  517. package/src/lib/room-controls-phase6.test.ts +630 -0
  518. package/src/lib/room-divide-session.ts +523 -0
  519. package/src/lib/room-handle-drag.ts +1068 -0
  520. package/src/lib/room-name-catalog.test.ts +90 -0
  521. package/src/lib/room-name-catalog.ts +168 -0
  522. package/src/lib/room-push-dimensions.test.ts +311 -0
  523. package/src/lib/room-push-dimensions.ts +251 -0
  524. package/src/lib/room-selection-commands.ts +62 -0
  525. package/src/lib/room-selection.test.ts +1017 -0
  526. package/src/lib/room-selection.ts +417 -0
  527. package/src/lib/room-structure-commands.ts +94 -0
  528. package/src/lib/room-structure.test.ts +921 -0
  529. package/src/lib/room-transform-session.ts +570 -0
  530. package/src/lib/room-zone-routing.test.ts +294 -0
  531. package/src/lib/room-zone-routing.ts +197 -0
  532. package/src/lib/scene-clipboard.test.ts +610 -1
  533. package/src/lib/scene-clipboard.ts +269 -58
  534. package/src/lib/scene-material-refs.test.ts +79 -0
  535. package/src/lib/scene-material-refs.ts +79 -0
  536. package/src/lib/scene.test.ts +24 -36
  537. package/src/lib/scene.ts +33 -57
  538. package/src/lib/selection-routing.test.ts +129 -45
  539. package/src/lib/selection-routing.ts +54 -75
  540. package/src/lib/session-writes.test.ts +120 -0
  541. package/src/lib/session-writes.ts +205 -0
  542. package/src/lib/sfx-bus.ts +13 -0
  543. package/src/lib/sfx-player.test.ts +70 -9
  544. package/src/lib/sfx-player.ts +6 -0
  545. package/src/lib/sidebar-panel.ts +62 -0
  546. package/src/lib/site-boundary.test.ts +4 -6
  547. package/src/lib/site-boundary.ts +1 -1
  548. package/src/lib/snapping-mode.test.ts +4 -0
  549. package/src/lib/snapping-mode.ts +8 -1
  550. package/src/lib/stair-levels.test.ts +25 -0
  551. package/src/lib/stair-levels.ts +22 -3
  552. package/src/lib/terrace-draft.test.ts +145 -0
  553. package/src/lib/terrace-draft.ts +214 -0
  554. package/src/lib/tool-cursor-icon.test.ts +16 -0
  555. package/src/lib/tool-cursor-icon.ts +11 -0
  556. package/src/lib/typing-target.ts +9 -0
  557. package/src/lib/units.ts +77 -39
  558. package/src/lib/vector-edges.test.ts +58 -0
  559. package/src/lib/vector-edges.ts +400 -0
  560. package/src/lib/viewer-selection.test.ts +577 -0
  561. package/src/lib/viewer-selection.ts +580 -0
  562. package/src/lib/wall-draw-variant.test.ts +71 -0
  563. package/src/lib/wall-draw-variant.ts +91 -0
  564. package/src/lib/wall-paint-region-session.ts +246 -0
  565. package/src/lib/wall-paint-region.test.ts +373 -0
  566. package/src/lib/wall-placement-pointer.test.ts +42 -0
  567. package/src/lib/wall-placement-pointer.ts +57 -0
  568. package/src/lib/wall-polygon-draft.test.ts +262 -0
  569. package/src/lib/wall-polygon-draft.ts +286 -0
  570. package/src/lib/wall-push.test.ts +453 -0
  571. package/src/lib/wall-region-face.ts +352 -0
  572. package/src/lib/wall-region-handles.test.ts +304 -0
  573. package/src/lib/wall-region-handles.ts +368 -0
  574. package/src/lib/wall-region-list.test.ts +119 -0
  575. package/src/lib/wall-region-list.ts +136 -0
  576. package/src/lib/wall-region-snap.ts +110 -0
  577. package/src/lib/window-changes.ts +35 -0
  578. package/src/lib/window-interaction.ts +14 -0
  579. package/src/lib/zone-content.ts +4 -1
  580. package/src/lib/zone-name.test.ts +17 -0
  581. package/src/lib/zone-name.ts +17 -0
  582. package/src/store/opens-in-select-mode.test.ts +223 -0
  583. package/src/store/paint-mode.test.ts +188 -0
  584. package/src/store/terrain-sculpt-mode.test.ts +19 -21
  585. package/src/store/tool-mode.test.ts +137 -6
  586. package/src/store/use-audio.test.ts +146 -0
  587. package/src/store/use-audio.tsx +127 -3
  588. package/src/store/use-construction-reveal.test.ts +173 -0
  589. package/src/store/use-construction-reveal.ts +145 -0
  590. package/src/store/use-corner-snap-hint.test.ts +24 -0
  591. package/src/store/use-corner-snap-hint.ts +47 -0
  592. package/src/store/use-delete-confirmation.ts +28 -1
  593. package/src/store/use-drawing-view.test.ts +24 -3
  594. package/src/store/use-drawing-view.ts +29 -1
  595. package/src/store/use-editor.tsx +432 -321
  596. package/src/store/use-floorplan-draft-preview.ts +13 -0
  597. package/src/store/use-floorplan-marquee.ts +12 -2
  598. package/src/store/use-follow-pascal.test.ts +62 -0
  599. package/src/store/use-follow-pascal.ts +53 -0
  600. package/src/store/use-point-ask.ts +247 -0
  601. package/src/store/use-quick-measurement-hud.ts +3 -3
  602. package/src/store/use-stair-build-preview.ts +33 -5
  603. package/src/store/use-wall-move-ghosts.ts +4 -2
  604. package/src/store/use-wall-region-handles-selection.test.ts +222 -0
  605. package/src/store/use-wall-region-handles-selection.ts +139 -0
  606. package/src/store/view-layout.test.ts +201 -0
  607. package/src/store/view-layout.ts +199 -0
  608. package/src/test-utils/immediate-animation-frames.ts +16 -0
  609. package/src/test-utils/live-gesture-round-trip.ts +224 -0
  610. package/src/test-utils/render-current-markup.tsx +16 -0
  611. package/src/test-utils/selection-harness.ts +82 -0
  612. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
  613. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
  614. package/src/components/tools/fence/fence-drafting.test.ts +0 -100
  615. package/src/components/tools/fence/fence-drafting.ts +0 -266
  616. package/src/components/tools/stair/stair-click-guard.test.ts +0 -58
  617. package/src/components/tools/stair/stair-click-guard.ts +0 -72
  618. package/src/components/tools/stair/stair-defaults.ts +0 -18
  619. package/src/components/tools/stair/stair-tool.tsx +0 -634
  620. package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
  621. package/src/components/viewer/viewer-units-panel.tsx +0 -116
  622. package/src/lib/floorplan/stairs.ts +0 -489
  623. package/src/lib/level-duplication.test.ts +0 -144
  624. package/src/lib/level-duplication.ts +0 -195
@@ -0,0 +1,278 @@
1
+ 'use client'
2
+
3
+ import type { AnyNode, AnyNodeId } from '@pascal-app/core'
4
+ import { useScene } from '@pascal-app/core'
5
+ import { useViewer } from '@pascal-app/viewer'
6
+ import { useCallback, useEffect, useRef, useState } from 'react'
7
+ import { createPortal } from 'react-dom'
8
+ import {
9
+ parseDimensionInput,
10
+ planDimensionDrive,
11
+ resolveDimensionDrive,
12
+ } from '../../../lib/floorplan/dimension-drive'
13
+ import { formatScheduleLength } from '../../../lib/floorplan/schedules'
14
+ import useEditor from '../../../store/use-editor'
15
+
16
+ /**
17
+ * Click-to-type dimensions — WS3.
18
+ *
19
+ * Single-clicking a dimension label opens an inline input over the label
20
+ * plate, pre-filled with the current text. Enter commits, Esc cancels.
21
+ * Committing DRIVES geometry: the anchor the dimension runs toward moves
22
+ * along the dimension direction by the delta, and connected wall junctions
23
+ * follow (`planDimensionDrive`). When nothing bindable is under the far
24
+ * anchor the value is written as `textOverride` on a construction-dimension
25
+ * node instead, and the label renders an "override" badge.
26
+ *
27
+ * The label is found by DOM delegation on the attributes
28
+ * `floorplan-dimension-renderer.tsx` emits, so this works identically for
29
+ * `dimension` and `dimension-string` geometry, for automatic wall dimensions,
30
+ * contextual dimensions and manual construction-dimension nodes — and on
31
+ * sheets, which mount the same renderer.
32
+ */
33
+
34
+ type EditGateEditorState = Pick<
35
+ ReturnType<typeof useEditor.getState>,
36
+ 'workspaceMode' | 'mode' | 'isPreviewMode' | 'isCaptureMode' | 'isFirstPersonMode'
37
+ >
38
+
39
+ /**
40
+ * Typing a dimension moves geometry, so it is only offered on an editable
41
+ * scene in idle select mode — never in studio, sheets, preview, capture or
42
+ * read-only (which is how version preview locks the graph).
43
+ */
44
+ export function isDimensionEditAllowed(
45
+ editor: EditGateEditorState,
46
+ scene: { readOnly: boolean },
47
+ ): boolean {
48
+ return (
49
+ editor.workspaceMode === 'edit' &&
50
+ editor.mode === 'select' &&
51
+ !editor.isPreviewMode &&
52
+ !editor.isCaptureMode &&
53
+ !editor.isFirstPersonMode &&
54
+ !scene.readOnly
55
+ )
56
+ }
57
+
58
+ export function useDimensionEditAllowed(): boolean {
59
+ const readOnly = useScene((state) => state.readOnly)
60
+ return useEditor((state) => isDimensionEditAllowed(state, { readOnly }))
61
+ }
62
+
63
+ type EditTarget = {
64
+ ownerNodeId: AnyNodeId
65
+ text: string
66
+ value: number
67
+ witnessStart: [number, number]
68
+ witnessEnd: [number, number]
69
+ rect: { left: number; top: number; width: number; height: number }
70
+ }
71
+
72
+ const MIN_INPUT_WIDTH_PX = 76
73
+ const CLICK_SLOP_PX = 4
74
+
75
+ export function FloorplanDimensionEditOverlay(): React.ReactElement | null {
76
+ const [target, setTarget] = useState<EditTarget | null>(null)
77
+ const [draft, setDraft] = useState('')
78
+ const [error, setError] = useState<string | null>(null)
79
+ const inputRef = useRef<HTMLInputElement | null>(null)
80
+ const unit = useViewer((state) => state.unit)
81
+
82
+ useEffect(() => {
83
+ let down: { x: number; y: number } | null = null
84
+ const onPointerDown = (event: PointerEvent) => {
85
+ down = { x: event.clientX, y: event.clientY }
86
+ }
87
+ const onClick = (event: MouseEvent) => {
88
+ if (event.button !== 0 || event.shiftKey || event.metaKey || event.ctrlKey || event.altKey) {
89
+ return
90
+ }
91
+ if (down && Math.hypot(event.clientX - down.x, event.clientY - down.y) > CLICK_SLOP_PX) {
92
+ return
93
+ }
94
+ if (!isDimensionEditAllowed(useEditor.getState(), useScene.getState())) return
95
+ const origin = event.target as Element | null
96
+ const hit = origin?.closest?.('[data-floorplan-dimension-hit]')
97
+ if (!hit) return
98
+ const label = hit.closest('[data-floorplan-annotation-label]')
99
+ const owner = hit.closest('[data-node-id]')
100
+ const ownerNodeId = owner?.getAttribute('data-node-id')
101
+ if (!label || !ownerNodeId) return
102
+
103
+ const value = number(label.getAttribute('data-floorplan-dimension-value'))
104
+ const witnessStart = point(
105
+ label.getAttribute('data-floorplan-dimension-witness-start-x'),
106
+ label.getAttribute('data-floorplan-dimension-witness-start-y'),
107
+ )
108
+ const witnessEnd = point(
109
+ label.getAttribute('data-floorplan-dimension-witness-end-x'),
110
+ label.getAttribute('data-floorplan-dimension-witness-end-y'),
111
+ )
112
+ if (value === null || !witnessStart || !witnessEnd) return
113
+
114
+ event.preventDefault()
115
+ event.stopPropagation()
116
+ const box = label.getBoundingClientRect()
117
+ const text = label.getAttribute('data-floorplan-dimension-text') ?? ''
118
+ setTarget({
119
+ ownerNodeId: ownerNodeId as AnyNodeId,
120
+ text,
121
+ value,
122
+ witnessStart,
123
+ witnessEnd,
124
+ rect: {
125
+ left: box.left + box.width / 2,
126
+ top: box.top + box.height / 2,
127
+ width: Math.max(MIN_INPUT_WIDTH_PX, box.width + 24),
128
+ height: Math.max(22, box.height + 6),
129
+ },
130
+ })
131
+ setDraft(text)
132
+ setError(null)
133
+ }
134
+
135
+ // Document, not window: drag and marquee handlers swallow the click that
136
+ // ends them with a window capture listener registered after this one, and
137
+ // only a listener further down the capture path is skipped by it.
138
+ document.addEventListener('pointerdown', onPointerDown, true)
139
+ document.addEventListener('click', onClick, true)
140
+ return () => {
141
+ document.removeEventListener('pointerdown', onPointerDown, true)
142
+ document.removeEventListener('click', onClick, true)
143
+ }
144
+ }, [])
145
+
146
+ useEffect(() => {
147
+ if (!target) return
148
+ const frame = requestAnimationFrame(() => {
149
+ inputRef.current?.focus()
150
+ inputRef.current?.select()
151
+ })
152
+ return () => cancelAnimationFrame(frame)
153
+ }, [target])
154
+
155
+ const commit = useCallback(() => {
156
+ if (!target) return
157
+ const nextLength = parseDimensionInput(draft, unit)
158
+ if (nextLength === null) {
159
+ setError(`Cannot read "${draft.trim()}"`)
160
+ return
161
+ }
162
+ const outcome = commitDimensionValue({ target, nextLength, unit })
163
+ if (!outcome.ok) {
164
+ setError(outcome.reason)
165
+ return
166
+ }
167
+ setTarget(null)
168
+ setError(null)
169
+ }, [draft, target, unit])
170
+
171
+ if (!target) return null
172
+ if (typeof document === 'undefined') return null
173
+
174
+ return createPortal(
175
+ <div
176
+ className="pointer-events-none fixed inset-0 z-[70]"
177
+ data-floorplan-dimension-edit-overlay=""
178
+ >
179
+ <div
180
+ className="pointer-events-auto absolute -translate-x-1/2 -translate-y-1/2"
181
+ style={{ left: target.rect.left, top: target.rect.top, width: target.rect.width }}
182
+ >
183
+ <input
184
+ className="w-full rounded-sm border border-primary bg-card px-1 py-0.5 text-center font-mono text-foreground text-xs shadow-sm outline-none"
185
+ onBlur={() => setTarget(null)}
186
+ onChange={(event) => {
187
+ setDraft(event.target.value)
188
+ setError(null)
189
+ }}
190
+ onKeyDown={(event) => {
191
+ if (event.key === 'Enter') {
192
+ event.preventDefault()
193
+ commit()
194
+ } else if (event.key === 'Escape') {
195
+ event.preventDefault()
196
+ setTarget(null)
197
+ setError(null)
198
+ }
199
+ event.stopPropagation()
200
+ }}
201
+ ref={inputRef}
202
+ spellCheck={false}
203
+ value={draft}
204
+ />
205
+ {error ? (
206
+ <div className="mt-0.5 whitespace-nowrap rounded-sm bg-card px-1 text-[10px] text-destructive shadow-sm">
207
+ {error}
208
+ </div>
209
+ ) : null}
210
+ </div>
211
+ </div>,
212
+ document.body,
213
+ )
214
+ }
215
+
216
+ type CommitOutcome = { ok: true } | { ok: false; reason: string }
217
+
218
+ /**
219
+ * Apply a typed dimension value. Exported for tests — it is the seam between
220
+ * the pure drive math and the scene store.
221
+ */
222
+ export function commitDimensionValue(args: {
223
+ target: Pick<EditTarget, 'ownerNodeId' | 'witnessStart' | 'witnessEnd' | 'value'>
224
+ nextLength: number
225
+ unit?: 'metric' | 'imperial'
226
+ }): CommitOutcome {
227
+ const store = useScene.getState()
228
+ const nodes = store.nodes as Readonly<Record<string, AnyNode>>
229
+ const resolution = resolveDimensionDrive({
230
+ nodes,
231
+ ownerNodeId: args.target.ownerNodeId,
232
+ start: args.target.witnessStart,
233
+ end: args.target.witnessEnd,
234
+ })
235
+
236
+ if (!resolution.drivable) {
237
+ // Fallback: annotate rather than move. Only a construction-dimension
238
+ // node can carry an override — anything else has nowhere to put it.
239
+ const owner = nodes[args.target.ownerNodeId]
240
+ if (owner?.type !== 'construction-dimension') {
241
+ return { ok: false, reason: `Not drivable — ${resolution.reason}` }
242
+ }
243
+ store.updateNode(args.target.ownerNodeId, {
244
+ textOverride: formatScheduleLength(args.nextLength, args.unit ?? 'imperial'),
245
+ } as Partial<AnyNode>)
246
+ return { ok: true }
247
+ }
248
+
249
+ const plan = planDimensionDrive({
250
+ nodes,
251
+ target: resolution.target,
252
+ currentLength: args.target.value,
253
+ nextLength: args.nextLength,
254
+ })
255
+ if (!plan) return { ok: false, reason: 'Value would collapse the geometry' }
256
+ if (plan.updates.length === 0) return { ok: true }
257
+
258
+ const changes = plan.updates.map((update) => ({
259
+ id: update.id,
260
+ data: update.data as Partial<AnyNode>,
261
+ }))
262
+ if (store.applyNodeChanges) store.applyNodeChanges({ update: changes })
263
+ else for (const change of changes) store.updateNode(change.id, change.data)
264
+ for (const change of changes) store.markDirty(change.id)
265
+ return { ok: true }
266
+ }
267
+
268
+ function number(value: string | null): number | null {
269
+ if (value === null) return null
270
+ const parsed = Number.parseFloat(value)
271
+ return Number.isFinite(parsed) ? parsed : null
272
+ }
273
+
274
+ function point(x: string | null, y: string | null): [number, number] | null {
275
+ const parsedX = number(x)
276
+ const parsedY = number(y)
277
+ return parsedX === null || parsedY === null ? null : [parsedX, parsedY]
278
+ }
@@ -1,4 +1,5 @@
1
1
  import type { FloorplanGeometry, FloorplanPoint } from '@pascal-app/core'
2
+ import { useDimensionEditAllowed } from './floorplan-dimension-edit-overlay'
2
3
  import { resolveFloorplanLabelAngle } from './floorplan-label-angle'
3
4
 
4
5
  const EXTENSION_START_GAP = 0.075
@@ -21,6 +22,22 @@ const SQRT_ONE_HALF = Math.SQRT1_2
21
22
 
22
23
  type FloorplanDimensionRenderMode = 'screen' | 'pdf'
23
24
 
25
+ /**
26
+ * Click-to-type dimensions (WS3).
27
+ *
28
+ * On screen the label plate carries an invisible hit rect plus the data the
29
+ * editing layer needs to drive geometry from a typed value. The layer picks
30
+ * it up by DOM delegation (`FloorplanDimensionEditOverlay` below), so every
31
+ * producer of `dimension` / `dimension-string` geometry — wall automatic
32
+ * dimensions, contextual dimensions, manual construction-dimension nodes —
33
+ * becomes editable without each one wiring up a callback.
34
+ *
35
+ * Witness points are the measured feature points in level-plan metres; the
36
+ * value is the length the label actually reads (dimension-line points when
37
+ * the producer supplied them, witness points otherwise).
38
+ */
39
+ export const FLOORPLAN_DIMENSION_HIT_ATTRIBUTE = 'data-floorplan-dimension-hit'
40
+
24
41
  type DimensionGeometry = Extract<FloorplanGeometry, { kind: 'dimension' }>
25
42
  type DimensionStringGeometry = Extract<FloorplanGeometry, { kind: 'dimension-string' }>
26
43
  type DimensionTerminator = NonNullable<DimensionGeometry['terminator']>
@@ -289,10 +306,20 @@ export function FloorplanDimensionRenderer({
289
306
  data-floorplan-dimension-start-y={layout.dimensionStart[1]}
290
307
  data-floorplan-dimension-end-x={layout.dimensionEnd[0]}
291
308
  data-floorplan-dimension-end-y={layout.dimensionEnd[1]}
309
+ data-floorplan-dimension-text={geometry.text}
310
+ data-floorplan-dimension-value={dimensionLabelValue(layout)}
311
+ data-floorplan-dimension-witness-start-x={geometry.start[0]}
312
+ data-floorplan-dimension-witness-start-y={geometry.start[1]}
313
+ data-floorplan-dimension-witness-end-x={geometry.end[0]}
314
+ data-floorplan-dimension-witness-end-y={geometry.end[1]}
292
315
  transform={labelTransform}
293
316
  >
294
317
  <DimensionLabel
295
318
  fontSize={labelFontSize}
319
+ // `overridden` is declared on the geometry union in
320
+ // packages/core/src/registry/types.ts; the cast keeps this file
321
+ // compiling against a core dist built before that field landed.
322
+ overridden={(geometry as { overridden?: boolean }).overridden === true}
296
323
  renderMode={renderMode}
297
324
  stroke={stroke}
298
325
  text={geometry.text}
@@ -464,6 +491,12 @@ export function FloorplanDimensionStringRenderer({
464
491
  data-floorplan-dimension-start-y={layout.dimensionStart[1]}
465
492
  data-floorplan-dimension-end-x={layout.dimensionEnd[0]}
466
493
  data-floorplan-dimension-end-y={layout.dimensionEnd[1]}
494
+ data-floorplan-dimension-text={segment.text}
495
+ data-floorplan-dimension-value={dimensionLabelValue(layout)}
496
+ data-floorplan-dimension-witness-start-x={segment.start[0]}
497
+ data-floorplan-dimension-witness-start-y={segment.start[1]}
498
+ data-floorplan-dimension-witness-end-x={segment.end[0]}
499
+ data-floorplan-dimension-witness-end-y={segment.end[1]}
467
500
  transform={labelTransform}
468
501
  >
469
502
  <DimensionLabel
@@ -481,6 +514,14 @@ export function FloorplanDimensionStringRenderer({
481
514
  )
482
515
  }
483
516
 
517
+ /** The length the label reads — the dimension-line span, offset cancels out. */
518
+ function dimensionLabelValue(layout: ArchitecturalDimensionLayout): number {
519
+ return Math.hypot(
520
+ layout.dimensionEnd[0] - layout.dimensionStart[0],
521
+ layout.dimensionEnd[1] - layout.dimensionStart[1],
522
+ )
523
+ }
524
+
484
525
  function rotateVector(vector: FloorplanPoint, radians: number): FloorplanPoint {
485
526
  const cosine = Math.cos(radians)
486
527
  const sine = Math.sin(radians)
@@ -493,17 +534,21 @@ function DimensionLabel({
493
534
  fontSize,
494
535
  stroke,
495
536
  renderMode,
537
+ overridden = false,
496
538
  }: {
497
539
  text: string
498
540
  y: number
499
541
  fontSize: number
500
542
  stroke: string
501
543
  renderMode: FloorplanDimensionRenderMode
544
+ /** Draws the "override" badge when the value could not drive geometry. */
545
+ overridden?: boolean
502
546
  }) {
503
547
  const width = Math.max(fontSize, text.length * fontSize * LABEL_CHARACTER_WIDTH_RATIO)
504
548
  const plateWidth = width + fontSize * 0.5
505
549
  const plateHeight = fontSize * 1.2
506
550
  const plateY = y - fontSize * 0.82
551
+ const editable = useDimensionEditAllowed()
507
552
 
508
553
  return (
509
554
  <>
@@ -535,6 +580,38 @@ function DimensionLabel({
535
580
  >
536
581
  {text}
537
582
  </text>
583
+ {overridden && renderMode !== 'pdf' ? (
584
+ <text
585
+ fill={stroke}
586
+ fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace"
587
+ fontSize={fontSize * 0.62}
588
+ opacity={0.75}
589
+ paintOrder="stroke"
590
+ stroke="#ffffff"
591
+ strokeLinejoin="round"
592
+ strokeWidth={2}
593
+ textAnchor="middle"
594
+ vectorEffect="non-scaling-stroke"
595
+ x={0}
596
+ y={y + fontSize * 0.86}
597
+ >
598
+ override
599
+ </text>
600
+ ) : null}
601
+ {renderMode === 'pdf' || !editable ? null : (
602
+ // Invisible click target on the plate. `pointer-events` is inherited
603
+ // from the dimension group (`none`), so it is re-enabled here only.
604
+ <rect
605
+ {...{ [FLOORPLAN_DIMENSION_HIT_ATTRIBUTE]: '' }}
606
+ cursor="text"
607
+ fill="transparent"
608
+ height={plateHeight}
609
+ pointerEvents="all"
610
+ width={plateWidth}
611
+ x={-plateWidth / 2}
612
+ y={plateY}
613
+ />
614
+ )}
538
615
  </>
539
616
  )
540
617
  }
@@ -1,16 +1,16 @@
1
1
  'use client'
2
2
 
3
- import type { WallPlanPoint } from '@pascal-app/core'
3
+ /** Length plate + angle arcs for a drafted segment, in plan space. */
4
+ type PlanPoint = readonly [number, number]
4
5
 
5
- /** Length plate + angle arcs for a drafted wall segment, in plan space. */
6
- export type DraftWallMeasurement = {
6
+ export type DraftMeasurement = {
7
7
  lengthLabel: string
8
- midpoint: WallPlanPoint
9
- direction: WallPlanPoint
8
+ midpoint: PlanPoint
9
+ direction: PlanPoint
10
10
  angleLabels: {
11
11
  id: string
12
12
  label: string
13
- center: WallPlanPoint
13
+ center: PlanPoint
14
14
  radius: number
15
15
  startAngle: number
16
16
  endAngle: number
@@ -18,7 +18,7 @@ export type DraftWallMeasurement = {
18
18
  }[]
19
19
  }
20
20
 
21
- export function FloorplanDraftWallMeasurement({
21
+ export function FloorplanDraftMeasurement({
22
22
  measurement,
23
23
  measurementStroke,
24
24
  labelBackground,
@@ -26,7 +26,7 @@ export function FloorplanDraftWallMeasurement({
26
26
  sceneRotationDeg,
27
27
  unitsPerPixel,
28
28
  }: {
29
- measurement: DraftWallMeasurement
29
+ measurement: DraftMeasurement
30
30
  measurementStroke: string
31
31
  labelBackground: string
32
32
  labelText: string
@@ -172,7 +172,10 @@ function documentTextFontSize(
172
172
  }
173
173
 
174
174
  function documentTextSizePt(geometry: Extract<FloorplanGeometry, { kind: 'text' }>): number {
175
- switch (readFloorplanGeometryMetadata(geometry).annotationRole) {
175
+ const metadata = readFloorplanGeometryMetadata(geometry)
176
+ // an explicit paper size wins over the role's (a sheet's room tag sets its own)
177
+ if (typeof metadata.textSizePt === 'number' && metadata.textSizePt > 0) return metadata.textSizePt
178
+ switch (metadata.annotationRole) {
176
179
  case 'room-label':
177
180
  if (geometry.fontSize >= 0.18) return DOCUMENT_ROOM_NAME_TEXT_SIZE_PT
178
181
  if (geometry.fontSize >= 0.145) return DOCUMENT_ROOM_NUMBER_TEXT_SIZE_PT
@@ -15,6 +15,7 @@ import {
15
15
  FLOORPLAN_NODE_EXTENSION_KEY,
16
16
  floorplanGeometryMetadata,
17
17
  } from '../../../lib/floorplan/floorplan-extension'
18
+ import type { SessionWrites } from '../../../lib/session-writes'
18
19
  import {
19
20
  buildFloorplanEntryGeometry,
20
21
  cancelFloorplanAffordanceDrag,
@@ -30,6 +31,7 @@ import {
30
31
  isFloorplanOpeningPlacementState,
31
32
  resolveFloorplanHandleUnitsPerPixel,
32
33
  siteToFloorplanTransform,
34
+ splitFloorplanHandles,
33
35
  splitFloorplanOverlay,
34
36
  subscribeFloorplanAffordanceToolCancel,
35
37
  } from './floorplan-registry-layer'
@@ -385,7 +387,13 @@ describe('floorplan affordance cancellation', () => {
385
387
  canCommit: () => true,
386
388
  commit,
387
389
  }
388
- const snapshots = [{ id: 'wall_a' as AnyNodeId, data: { width: 1 } }]
390
+ const revert = mock(() => {})
391
+ const writes = {
392
+ record: (run: () => unknown) => run(),
393
+ revert,
394
+ changes: () => ({ create: [], update: [], delete: [] }),
395
+ size: 0,
396
+ } as unknown as SessionWrites
389
397
  const drag = {
390
398
  pointerId: 7,
391
399
  captureTarget: {
@@ -394,12 +402,11 @@ describe('floorplan affordance cancellation', () => {
394
402
  } as unknown as Element,
395
403
  handleId: 'wall_a:endpoint',
396
404
  session,
397
- snapshots,
405
+ writes,
398
406
  historyPaused: true,
399
407
  lastPlanPoint: [0, 0] as [number, number],
400
408
  }
401
409
  const dragRef = { current: drag }
402
- const restoreSnapshots = mock(() => {})
403
410
  const resumeHistory = mock(() => {})
404
411
  const clearPreview = mock(() => {})
405
412
  const clearSnapFeedback = mock(() => {})
@@ -410,7 +417,6 @@ describe('floorplan affordance cancellation', () => {
410
417
  const unsubscribe = subscribeFloorplanAffordanceToolCancel(
411
418
  () =>
412
419
  cancelFloorplanAffordanceDrag(dragRef, {
413
- restoreSnapshots,
414
420
  resumeHistory,
415
421
  clearPreview,
416
422
  clearSnapFeedback,
@@ -429,7 +435,7 @@ describe('floorplan affordance cancellation', () => {
429
435
 
430
436
  expect(dragRef.current).toBeNull()
431
437
  expect(releasePointerCapture).toHaveBeenCalledWith(7)
432
- expect(restoreSnapshots).toHaveBeenCalledWith(snapshots)
438
+ expect(revert).toHaveBeenCalledTimes(1)
433
439
  expect(resumeHistory).toHaveBeenCalledTimes(1)
434
440
  expect(clearPreview).toHaveBeenCalledTimes(2)
435
441
  expect(clearPreview).toHaveBeenNthCalledWith(1, 'wall_a')
@@ -450,7 +456,6 @@ describe('floorplan opening placement interaction routing', () => {
450
456
  test('passes entries through only while an opening tool or moving opening is active', () => {
451
457
  expect(
452
458
  isFloorplanOpeningPlacementState({
453
- phase: 'structure',
454
459
  mode: 'build',
455
460
  tool: 'window',
456
461
  movingNodeHasWallOpeningPlacement: false,
@@ -458,7 +463,6 @@ describe('floorplan opening placement interaction routing', () => {
458
463
  ).toBe(true)
459
464
  expect(
460
465
  isFloorplanOpeningPlacementState({
461
- phase: 'structure',
462
466
  mode: 'select',
463
467
  tool: null,
464
468
  movingNodeHasWallOpeningPlacement: true,
@@ -466,7 +470,6 @@ describe('floorplan opening placement interaction routing', () => {
466
470
  ).toBe(true)
467
471
  expect(
468
472
  isFloorplanOpeningPlacementState({
469
- phase: 'structure',
470
473
  mode: 'select',
471
474
  tool: null,
472
475
  movingNodeHasWallOpeningPlacement: false,
@@ -829,3 +832,30 @@ describe('isFloorplanHierarchyVisible', () => {
829
832
  expect(visibleUnder(nodes, 'level_a', 'wall_a')).toBe(true)
830
833
  })
831
834
  })
835
+
836
+ describe('the handles pass', () => {
837
+ test('grabbable handles leave the overlay for a pass of their own, under the same transform', () => {
838
+ const label = { kind: 'text', x: 0, y: 0, text: 'Wall 1' } as FloorplanGeometry
839
+ const corner = {
840
+ kind: 'endpoint-handle',
841
+ point: [1, 0],
842
+ state: 'idle',
843
+ affordance: 'move-endpoint',
844
+ payload: { wallId: 'wall_a', endpoint: 'end' },
845
+ } as FloorplanGeometry
846
+ const tree = {
847
+ kind: 'group',
848
+ transform: 'rotate(30)',
849
+ children: [label, { kind: 'group', children: [corner] }],
850
+ } as FloorplanGeometry
851
+ expect(splitFloorplanHandles(tree)).toEqual({
852
+ rest: { kind: 'group', transform: 'rotate(30)', children: [label] },
853
+ handles: {
854
+ kind: 'group',
855
+ transform: 'rotate(30)',
856
+ children: [{ kind: 'group', children: [corner] }],
857
+ },
858
+ } as never)
859
+ expect(splitFloorplanHandles(label)).toEqual({ rest: label, handles: null })
860
+ })
861
+ })