@pascal-app/editor 1.0.5 → 1.0.7

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 (317) hide show
  1. package/package.json +20 -17
  2. package/src/components/editor/action-menu-button.tsx +4 -1
  3. package/src/components/editor/alignment-3d-guide-layer.tsx +102 -11
  4. package/src/components/editor/bake-thumbnail.tsx +3 -2
  5. package/src/components/editor/custom-camera-controls.tsx +20 -8
  6. package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
  7. package/src/components/editor/first-person-controls.tsx +72 -61
  8. package/src/components/editor/floating-action-menu.tsx +27 -9
  9. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  10. package/src/components/editor/floorplan-panel.test.ts +220 -0
  11. package/src/components/editor/floorplan-panel.tsx +285 -132
  12. package/src/components/editor/footprint-height-handle.tsx +1 -1
  13. package/src/components/editor/grid.tsx +24 -15
  14. package/src/components/editor/group-actions.ts +19 -1
  15. package/src/components/editor/group-floating-action-menu.tsx +1 -1
  16. package/src/components/editor/group-move-3d.ts +27 -2
  17. package/src/components/editor/group-selection-box-3d.tsx +4 -1
  18. package/src/components/editor/group-transform-shared.ts +20 -0
  19. package/src/components/editor/index.tsx +130 -20
  20. package/src/components/editor/inspector-extension-actions.tsx +36 -0
  21. package/src/components/editor/localized-html.tsx +22 -0
  22. package/src/components/editor/node-action-menu.tsx +46 -12
  23. package/src/components/editor/node-arrow-handles.tsx +1 -2
  24. package/src/components/editor/open-wall-ends-3d-layer.tsx +1 -1
  25. package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
  26. package/src/components/editor/registry-action-contributions.test.tsx +67 -6
  27. package/src/components/editor/registry-action-contributions.tsx +115 -15
  28. package/src/components/editor/riser-diagram-panel.tsx +59 -9
  29. package/src/components/editor/room-controls.tsx +1 -1
  30. package/src/components/editor/room-handles-2d.tsx +156 -6
  31. package/src/components/editor/room-handles.tsx +241 -51
  32. package/src/components/editor/room-highlight.tsx +2 -0
  33. package/src/components/editor/room-labels.tsx +29 -15
  34. package/src/components/editor/selection-manager.tsx +15 -102
  35. package/src/components/editor/separator-end-grip.tsx +74 -0
  36. package/src/components/editor/site-edge-labels.tsx +1 -1
  37. package/src/components/editor/thumbnail-generator.test.ts +15 -6
  38. package/src/components/editor/thumbnail-generator.tsx +26 -1
  39. package/src/components/editor/use-floorplan-background-placement.ts +55 -52
  40. package/src/components/editor/views/view-bar.tsx +111 -26
  41. package/src/components/editor/wall-measurement-label.tsx +1 -1
  42. package/src/components/editor/wall-move-side-handles.tsx +5 -1
  43. package/src/components/editor/wall-pull-grip.tsx +193 -0
  44. package/src/components/editor/wall-snap-beacon-layer.tsx +81 -1
  45. package/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts +76 -0
  46. package/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx +186 -0
  47. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +63 -3
  48. package/src/components/editor-2d/floorplan-group-move.tsx +31 -3
  49. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
  50. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +22 -13
  51. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +11 -3
  52. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +7 -1
  53. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +96 -4
  54. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +52 -12
  55. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +12 -1
  56. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +197 -37
  57. package/src/components/procedural-items/inspector.tsx +59 -12
  58. package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +2 -0
  59. package/src/components/systems/ceiling/ceiling-system.test.ts +141 -0
  60. package/src/components/systems/ceiling/ceiling-system.tsx +53 -18
  61. package/src/components/tools/distribution/distribution-run-tool.tsx +50 -26
  62. package/src/components/tools/item/placement-history.test.tsx +1 -1
  63. package/src/components/tools/item/placement-strategies.test.ts +64 -1
  64. package/src/components/tools/item/placement-strategies.ts +5 -9
  65. package/src/components/tools/item/use-placement-coordinator.tsx +30 -34
  66. package/src/components/tools/registry/item-surface-move.test.ts +11 -0
  67. package/src/components/tools/select/select-candidates.test.ts +45 -0
  68. package/src/components/tools/select/select-candidates.ts +13 -2
  69. package/src/components/tools/shared/cursor-sphere.tsx +10 -3
  70. package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
  71. package/src/components/tools/shared/placement-box.tsx +1 -1
  72. package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
  73. package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
  74. package/src/components/tools/wall/wall-drafting.test.ts +299 -23
  75. package/src/components/tools/wall/wall-drafting.ts +132 -24
  76. package/src/components/tools/wall/wall-pull-snap.test.ts +274 -0
  77. package/src/components/tools/wall/wall-pull-snap.ts +563 -0
  78. package/src/components/tools/wall/wall-snap-geometry.test.ts +113 -1
  79. package/src/components/tools/wall/wall-snap-geometry.ts +137 -0
  80. package/src/components/tools/zone/zone-tool.tsx +3 -3
  81. package/src/components/ui/action-menu/action-button.tsx +2 -0
  82. package/src/components/ui/action-menu/camera-actions.tsx +8 -7
  83. package/src/components/ui/action-menu/control-modes.tsx +9 -5
  84. package/src/components/ui/action-menu/follow-pascal-pill.tsx +3 -1
  85. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +7 -1
  86. package/src/components/ui/action-menu/furnish-tools.tsx +7 -6
  87. package/src/components/ui/action-menu/index.tsx +1 -0
  88. package/src/components/ui/action-menu/measurement-control.tsx +35 -29
  89. package/src/components/ui/action-menu/point-ask-button.tsx +4 -3
  90. package/src/components/ui/action-menu/structure-tools.tsx +25 -23
  91. package/src/components/ui/action-menu/view-toggles.test.ts +18 -0
  92. package/src/components/ui/action-menu/view-toggles.tsx +256 -51
  93. package/src/components/ui/build-panel/build-panel.tsx +163 -25
  94. package/src/components/ui/command-palette/editor-commands.tsx +38 -11
  95. package/src/components/ui/controls/finish-swatch.tsx +45 -19
  96. package/src/components/ui/controls/material-picker.tsx +24 -17
  97. package/src/components/ui/controls/metric-control.test.ts +52 -0
  98. package/src/components/ui/controls/metric-control.tsx +62 -24
  99. package/src/components/ui/controls/segmented-control.tsx +5 -2
  100. package/src/components/ui/controls/slider-control.test.tsx +23 -0
  101. package/src/components/ui/controls/slider-control.tsx +30 -14
  102. package/src/components/ui/floating-level-selector.tsx +100 -48
  103. package/src/components/ui/helpers/building-helper.tsx +5 -3
  104. package/src/components/ui/helpers/contextual-helper-panel.tsx +97 -37
  105. package/src/components/ui/helpers/helper-manager.test.tsx +53 -3
  106. package/src/components/ui/helpers/helper-manager.tsx +82 -57
  107. package/src/components/ui/helpers/item-helper.tsx +6 -4
  108. package/src/components/ui/helpers/registered-tool-helper.tsx +3 -1
  109. package/src/components/ui/level-duplicate-dialog.tsx +32 -23
  110. package/src/components/ui/level-wall-copy-prompt.tsx +124 -0
  111. package/src/components/ui/panels/collections/collections-popover.tsx +11 -11
  112. package/src/components/ui/panels/deck-boards-fields.tsx +136 -0
  113. package/src/components/ui/panels/floor-foundation-panel.tsx +113 -38
  114. package/src/components/ui/panels/floor-opening-panel.tsx +73 -25
  115. package/src/components/ui/panels/inspector-extensions.tsx +66 -0
  116. package/src/components/ui/panels/mobile-panel-sheet.tsx +3 -1
  117. package/src/components/ui/panels/mobile-selection-bar.tsx +8 -6
  118. package/src/components/ui/panels/multi-field-value.test.ts +27 -0
  119. package/src/components/ui/panels/multi-field-value.ts +20 -1
  120. package/src/components/ui/panels/multi-height-mode.tsx +8 -7
  121. package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
  122. package/src/components/ui/panels/multi-selection-panel.tsx +75 -35
  123. package/src/components/ui/panels/node-display.ts +35 -24
  124. package/src/components/ui/panels/panel-manager.tsx +5 -3
  125. package/src/components/ui/panels/panel-wrapper.tsx +22 -109
  126. package/src/components/ui/panels/parametric-field-control.tsx +13 -10
  127. package/src/components/ui/panels/parametric-inspector.tsx +18 -12
  128. package/src/components/ui/panels/reference-panel.tsx +51 -35
  129. package/src/components/ui/panels/room-construction-rows.tsx +464 -65
  130. package/src/components/ui/panels/room-name-options.test.ts +36 -3
  131. package/src/components/ui/panels/room-name-options.ts +110 -11
  132. package/src/components/ui/panels/room-openings-field.tsx +21 -12
  133. package/src/components/ui/panels/room-panel.tsx +206 -99
  134. package/src/components/ui/panels/shape-choice.tsx +7 -5
  135. package/src/components/ui/panels/wall-region-list.tsx +7 -7
  136. package/src/components/ui/primitives/shortcut-token.tsx +10 -6
  137. package/src/components/ui/sidebar/icon-rail.tsx +11 -9
  138. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +10 -10
  139. package/src/components/ui/sidebar/panels/items-panel/index.tsx +10 -9
  140. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +3 -0
  141. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +102 -32
  142. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +4 -3
  143. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -2
  144. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +4 -2
  145. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +7 -5
  146. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +4 -2
  147. package/src/components/ui/sidebar/panels/site-panel/default-names.ts +75 -0
  148. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +4 -2
  149. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +4 -2
  150. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +4 -2
  151. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +4 -2
  152. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +4 -2
  153. package/src/components/ui/sidebar/panels/site-panel/index.tsx +117 -64
  154. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +36 -3
  155. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +4 -2
  156. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -4
  157. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +8 -2
  158. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +8 -5
  159. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +4 -2
  160. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +8 -3
  161. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +4 -2
  162. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +4 -2
  163. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +8 -7
  164. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +7 -6
  165. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +16 -1
  166. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +15 -8
  167. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +4 -2
  168. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +4 -2
  169. package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +3 -1
  170. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +18 -8
  171. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +25 -14
  172. package/src/components/ui/sidebar/tab-bar.tsx +4 -1
  173. package/src/components/ui/sidebar/use-plugin-panels.tsx +19 -10
  174. package/src/components/ui/snap-target-badge.tsx +7 -6
  175. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +721 -0
  176. package/src/components/ui/wall-type/wall-type-menu.tsx +167 -0
  177. package/src/components/viewer/floorplan-compass-button.tsx +12 -3
  178. package/src/components/viewer/floorplan-preview.tsx +27 -19
  179. package/src/components/viewer/render-settings-menu.tsx +294 -0
  180. package/src/components/viewer/sky-control.tsx +710 -0
  181. package/src/components/viewer/use-viewer-ui-sounds.test.ts +15 -0
  182. package/src/components/viewer/use-viewer-ui-sounds.ts +46 -0
  183. package/src/components/viewer/viewer-controls-bar.tsx +278 -260
  184. package/src/components/viewer/viewer-hover-label.tsx +12 -4
  185. package/src/components/viewer/viewer-rooms.tsx +61 -4
  186. package/src/components/viewer/viewer-scene-header.tsx +28 -17
  187. package/src/components/viewer/viewer-stage-switcher.tsx +4 -2
  188. package/src/components/viewer-overlay.tsx +8 -0
  189. package/src/components/walkthrough-hud.tsx +75 -2
  190. package/src/hooks/use-keyboard.test.ts +85 -4
  191. package/src/hooks/use-keyboard.ts +65 -7
  192. package/src/hooks/use-selected-room.ts +12 -1
  193. package/src/i18n/floor-footprint-name.ts +30 -0
  194. package/src/i18n/level-name.ts +39 -0
  195. package/src/i18n/locales/en/messages.po +15117 -0
  196. package/src/i18n/locales/en/messages.ts +1 -0
  197. package/src/i18n/locales/en-XA/messages.ts +1 -0
  198. package/src/i18n/locales/es/messages.po +37501 -0
  199. package/src/i18n/locales/es/messages.ts +1 -0
  200. package/src/i18n/locales/fr/messages.po +37501 -0
  201. package/src/i18n/locales/fr/messages.ts +1 -0
  202. package/src/i18n/locales/ja/messages.po +37501 -0
  203. package/src/i18n/locales/ja/messages.ts +1 -0
  204. package/src/i18n/locales/zh-CN/messages.po +37501 -0
  205. package/src/i18n/locales/zh-CN/messages.ts +1 -0
  206. package/src/i18n/locales.test.ts +63 -0
  207. package/src/i18n/locales.ts +32 -0
  208. package/src/i18n/message-slot.tsx +25 -0
  209. package/src/i18n/node-messages.ts +169 -0
  210. package/src/i18n/pseudo-locale.ts +17 -0
  211. package/src/i18n/published-entry.test.ts +17 -0
  212. package/src/i18n/scene-theme-name.ts +16 -0
  213. package/src/index.tsx +156 -4
  214. package/src/lib/add-storey.test.ts +3 -3
  215. package/src/lib/contextual-help.test.ts +20 -1
  216. package/src/lib/contextual-help.ts +95 -19
  217. package/src/lib/continuation.ts +38 -11
  218. package/src/lib/floor-footprints.test.ts +2 -1
  219. package/src/lib/floor-foundation-support.test.ts +7 -3
  220. package/src/lib/floorplan/dimension-drive.test.ts +36 -2
  221. package/src/lib/floorplan/dimension-drive.ts +68 -3
  222. package/src/lib/floorplan/floorplan-export.test.ts +16 -5
  223. package/src/lib/floorplan/floorplan-export.tsx +13 -16
  224. package/src/lib/floorplan/floorplan-extension.test.ts +11 -12
  225. package/src/lib/floorplan/floorplan-extension.ts +37 -18
  226. package/src/lib/floorplan/floorplan-mode.test.ts +13 -6
  227. package/src/lib/floorplan/floorplan-mode.ts +14 -12
  228. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +1 -1
  229. package/src/lib/floorplan/floorplan-readonly.ts +3 -3
  230. package/src/lib/floorplan/schedules.ts +10 -6
  231. package/src/lib/floorplan/site-plan/site-annotations.test.ts +46 -1
  232. package/src/lib/floorplan/site-plan/site-annotations.ts +3 -0
  233. package/src/lib/floorplan/site-plan/site-parts.ts +6 -3
  234. package/src/lib/geometry-script/author.test.ts +114 -0
  235. package/src/lib/geometry-script/author.ts +55 -23
  236. package/src/lib/geometry-script/client.ts +3 -0
  237. package/src/lib/geometry-script/finish-scripts.ts +7 -7
  238. package/src/lib/glb-export.test.ts +327 -0
  239. package/src/lib/glb-export.ts +70 -2
  240. package/src/lib/grid-frame.test.ts +56 -3
  241. package/src/lib/grid-frame.ts +28 -1
  242. package/src/lib/history.test.ts +2 -1
  243. package/src/lib/hud-title.ts +48 -24
  244. package/src/lib/ifc-export.test.ts +41 -1
  245. package/src/lib/inspector-card-mode.test.ts +3 -31
  246. package/src/lib/inspector-card-mode.ts +5 -10
  247. package/src/lib/inspector-expanded.test.ts +13 -0
  248. package/src/lib/inspector-expanded.ts +13 -1
  249. package/src/lib/keyboard-pan.test.ts +7 -0
  250. package/src/lib/keyboard-pan.ts +9 -2
  251. package/src/lib/layer-thickness.test.ts +22 -0
  252. package/src/lib/layer-thickness.ts +79 -0
  253. package/src/lib/lot/drop-in.test.ts +59 -1
  254. package/src/lib/lot/drop-in.ts +42 -50
  255. package/src/lib/material-swatch-style.ts +24 -0
  256. package/src/lib/measurement-parser.ts +7 -3
  257. package/src/lib/measurements.test.ts +17 -0
  258. package/src/lib/measurements.ts +20 -5
  259. package/src/lib/mezzanine-selection.test.ts +3 -1
  260. package/src/lib/orbit-near-plane.test.ts +12 -0
  261. package/src/lib/orbit-near-plane.ts +13 -0
  262. package/src/lib/paint-regions.test.ts +8 -5
  263. package/src/lib/paint-scope.test.ts +53 -0
  264. package/src/lib/paint-scope.ts +18 -4
  265. package/src/lib/panel-rows.ts +17 -8
  266. package/src/lib/reference-scale.ts +83 -0
  267. package/src/lib/room-built-on.test.ts +26 -12
  268. package/src/lib/room-built-on.ts +20 -2
  269. package/src/lib/room-construction.test.ts +28 -1
  270. package/src/lib/room-construction.ts +14 -0
  271. package/src/lib/room-controls-phase6.test.ts +140 -0
  272. package/src/lib/room-handle-drag.ts +557 -42
  273. package/src/lib/room-name-catalog.test.ts +8 -0
  274. package/src/lib/room-name-catalog.ts +8 -1
  275. package/src/lib/room-name-messages.test.ts +132 -0
  276. package/src/lib/room-name-messages.ts +147 -0
  277. package/src/lib/room-name-storage.test.ts +334 -0
  278. package/src/lib/room-push-dimensions.test.ts +37 -0
  279. package/src/lib/room-push-dimensions.ts +226 -2
  280. package/src/lib/room-selection.test.ts +23 -6
  281. package/src/lib/scene-clipboard.test.ts +70 -49
  282. package/src/lib/scene-clipboard.ts +9 -4
  283. package/src/lib/selection-cycle.ts +63 -0
  284. package/src/lib/separator-edit.test.ts +431 -0
  285. package/src/lib/separator-edit.ts +179 -0
  286. package/src/lib/separator-lines.ts +38 -0
  287. package/src/lib/sfx-bus.ts +27 -1
  288. package/src/lib/sfx-player.ts +16 -0
  289. package/src/lib/tool-cursor-icon.ts +5 -1
  290. package/src/lib/tool-shortcuts.ts +130 -0
  291. package/src/lib/viewer-selection.test.ts +28 -24
  292. package/src/lib/viewer-selection.ts +20 -5
  293. package/src/lib/wall-draw-variant.test.ts +7 -6
  294. package/src/lib/wall-draw-variant.ts +107 -6
  295. package/src/lib/wall-nudge.test.ts +44 -0
  296. package/src/lib/wall-nudge.ts +131 -0
  297. package/src/lib/wall-parallel-snap.test.ts +83 -0
  298. package/src/lib/wall-parallel-snap.ts +176 -0
  299. package/src/lib/wall-placement-pointer.test.ts +23 -0
  300. package/src/lib/wall-placement-pointer.ts +18 -0
  301. package/src/lib/wall-pull.test.ts +245 -0
  302. package/src/lib/wall-pull.ts +209 -0
  303. package/src/lib/wall-push-line-up.test.ts +132 -0
  304. package/src/lib/wall-region-list.ts +14 -5
  305. package/src/lib/wall-run-snap.test.ts +188 -0
  306. package/src/lib/wall-run-snap.ts +83 -0
  307. package/src/lib/wall-type.test.ts +125 -0
  308. package/src/lib/wall-type.ts +119 -0
  309. package/src/lib/window-interaction.test.ts +42 -0
  310. package/src/lib/window-interaction.ts +47 -5
  311. package/src/lib/world-grid-snap.test.ts +139 -2
  312. package/src/lib/world-grid-snap.ts +102 -105
  313. package/src/store/use-alignment-guides.ts +27 -1
  314. package/src/store/use-editor.tsx +48 -0
  315. package/src/store/use-floorplan-annotation-visibility.test.ts +64 -0
  316. package/src/store/use-floorplan-annotation-visibility.ts +47 -22
  317. package/src/store/use-wall-snap-indicator.ts +15 -2
@@ -13,33 +13,92 @@ import { useFloorplanRender } from './floorplan-render-context'
13
13
  *
14
14
  * endpoint (corner) → square midpoint → triangle
15
15
  * intersection → ✕ cross wall body (edge) → circle
16
+ * perpendicular → ⊥ corner projection guide → diamond
17
+ * grid length → small +
16
18
  *
17
19
  * Indigo to match the 3D beacon, except the corner (endpoint) square which is
18
- * green to match the alignment guides. Sizes are pixel-budgeted via
19
- * `unitsPerPixel` so the marker stays a
20
+ * green to match the alignment guides. A point that carries a `label` (a wall
21
+ * pulled from a wall end) also names what it caught in a pill beside the
22
+ * glyph. Sizes are pixel-budgeted via `unitsPerPixel` so the marker stays a
20
23
  * constant size on screen at any zoom. Mounted inside the `data-floorplan-scene`
21
24
  * group so coordinates are world meters (XZ) 1:1, like the alignment guides.
22
25
  */
23
26
  const COLOR = '#6366f1' // indigo-500 — matches the 3D beacon
24
27
  const ENDPOINT_COLOR = '#22c55e' // green-500 — corner (endpoint) snap accent
25
28
 
26
- export const FloorplanSnapBeaconLayer = memo(function FloorplanSnapBeaconLayer() {
29
+ export const FloorplanSnapBeaconLayer = memo(function FloorplanSnapBeaconLayer({
30
+ unitsPerPixel,
31
+ }: {
32
+ /** The plan's live scale, which keeps a labelled marker screen-sized. */
33
+ unitsPerPixel?: number
34
+ }) {
27
35
  const point = useWallSnapIndicator((s) => s.point)
28
36
  const ctx = useFloorplanRender()
29
37
 
30
38
  if (!point) return null
31
39
 
32
- const upp = ctx?.unitsPerPixel ?? 0.01
40
+ // A labelled point (a wall pulled from a wall end) is sized on screen.
41
+ const upp = (point.label ? unitsPerPixel : undefined) ?? ctx?.unitsPerPixel ?? 0.01
33
42
  const m = 6 * upp // base half-size of the glyph in world meters
34
43
  const stroke = 1.5 * upp
35
44
 
36
45
  return (
37
46
  <g pointerEvents="none">
38
47
  <SnapMarker color={COLOR} kind={point.kind} m={m} stroke={stroke} x={point.x} z={point.z} />
48
+ {point.label ? (
49
+ <SnapLabel
50
+ color={point.kind === 'endpoint' ? ENDPOINT_COLOR : COLOR}
51
+ sceneRotationDeg={ctx?.sceneRotationDeg ?? 0}
52
+ text={point.label}
53
+ upp={upp}
54
+ x={point.x}
55
+ z={point.z}
56
+ />
57
+ ) : null}
39
58
  </g>
40
59
  )
41
60
  })
42
61
 
62
+ /** What the snap caught, in a pill down and to the right of the glyph, kept upright. */
63
+ function SnapLabel({
64
+ color,
65
+ sceneRotationDeg,
66
+ text,
67
+ upp,
68
+ x,
69
+ z,
70
+ }: {
71
+ color: string
72
+ sceneRotationDeg: number
73
+ text: string
74
+ upp: number
75
+ x: number
76
+ z: number
77
+ }) {
78
+ const fontSize = 10.5 * upp
79
+ const padX = 5 * upp
80
+ const width = text.length * fontSize * 0.56 + padX * 2
81
+ const height = fontSize + 6 * upp
82
+ return (
83
+ <g
84
+ transform={`translate(${x} ${z}) rotate(${-sceneRotationDeg}) translate(${12 * upp} ${16 * upp})`}
85
+ >
86
+ <rect fill={color} height={height} rx={3 * upp} width={width} x={0} y={-height / 2} />
87
+ <text
88
+ dominantBaseline="middle"
89
+ fill="#ffffff"
90
+ fontFamily="-apple-system, system-ui, sans-serif"
91
+ fontSize={fontSize}
92
+ fontWeight={600}
93
+ x={padX}
94
+ y={0}
95
+ >
96
+ {text}
97
+ </text>
98
+ </g>
99
+ )
100
+ }
101
+
43
102
  function SnapMarker({
44
103
  color,
45
104
  kind,
@@ -72,6 +131,39 @@ function SnapMarker({
72
131
  </g>
73
132
  )
74
133
  }
134
+ if (kind === 'perpendicular') {
135
+ // ⊥ — a base line with an upright from its middle, and the square corner.
136
+ const c = m * 1.3
137
+ return (
138
+ <g fill="none" stroke={color} strokeLinecap="round" strokeWidth={stroke * 1.6}>
139
+ <line x1={x - c} x2={x + c} y1={z} y2={z} />
140
+ <line x1={x} x2={x} y1={z} y2={z - c * 1.4} />
141
+ <polyline
142
+ points={`${x + c * 0.5},${z} ${x + c * 0.5},${z - c * 0.5} ${x},${z - c * 0.5}`}
143
+ />
144
+ </g>
145
+ )
146
+ }
147
+ if (kind === 'alignment') {
148
+ const c = m * 1.2
149
+ return (
150
+ <polygon
151
+ fill="#ffffff"
152
+ points={`${x},${z - c} ${x + c},${z} ${x},${z + c} ${x - c},${z}`}
153
+ stroke={color}
154
+ strokeWidth={stroke * 1.4}
155
+ />
156
+ )
157
+ }
158
+ if (kind === 'grid') {
159
+ const c = m * 0.9
160
+ return (
161
+ <g stroke={color} strokeLinecap="round" strokeWidth={stroke * 1.4}>
162
+ <line x1={x - c} x2={x + c} y1={z} y2={z} />
163
+ <line x1={x} x2={x} y1={z - c} y2={z + c} />
164
+ </g>
165
+ )
166
+ }
75
167
  // 'wall' (edge / along-wall) → circle
76
168
  return <circle cx={x} cy={z} fill={color} r={m} />
77
169
  }
@@ -1,7 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import type { AnyNode, AnyNodeId } from '@pascal-app/core'
4
- import { useScene } from '@pascal-app/core'
4
+ import { useLocale, useScene } from '@pascal-app/core'
5
5
  import { useViewer } from '@pascal-app/viewer'
6
6
  import { useCallback, useEffect, useRef, useState } from 'react'
7
7
  import { createPortal } from 'react-dom'
@@ -11,6 +11,7 @@ import {
11
11
  resolveDimensionDrive,
12
12
  } from '../../../lib/floorplan/dimension-drive'
13
13
  import { formatScheduleLength } from '../../../lib/floorplan/schedules'
14
+ import { commitRoomPush } from '../../../lib/room-handle-drag'
14
15
  import useEditor from '../../../store/use-editor'
15
16
 
16
17
  /**
@@ -20,7 +21,8 @@ import useEditor from '../../../store/use-editor'
20
21
  * plate, pre-filled with the current text. Enter commits, Esc cancels.
21
22
  * Committing DRIVES geometry: the anchor the dimension runs toward moves
22
23
  * along the dimension direction by the delta, and connected wall junctions
23
- * follow (`planDimensionDrive`). When nothing bindable is under the far
24
+ * follow (`planDimensionDrive`); a dimension across a wall slides the whole
25
+ * wall, as its push arrow does (`commitRoomPush`). When nothing bindable is under the far
24
26
  * anchor the value is written as `textOverride` on a construction-dimension
25
27
  * node instead, and the label renders an "override" badge.
26
28
  *
@@ -60,6 +62,32 @@ export function useDimensionEditAllowed(): boolean {
60
62
  return useEditor((state) => isDimensionEditAllowed(state, { readOnly }))
61
63
  }
62
64
 
65
+ /**
66
+ * The typeable dimension value under a press, with the node typing it drives:
67
+ * the pressed element's or, when the plan's dashed group box lies over it, the
68
+ * one beneath (a multi-selection's walls keep their dimensions inside it).
69
+ */
70
+ export function typeableDimensionAt(
71
+ target: EventTarget | null,
72
+ clientX: number,
73
+ clientY: number,
74
+ ): { label: Element; ownerNodeId: AnyNodeId } | null {
75
+ const resolve = (element: Element | null | undefined) => {
76
+ const hit = element?.closest?.('[data-floorplan-dimension-hit]')
77
+ const label = hit?.closest('[data-floorplan-annotation-label]')
78
+ const ownerNodeId = hit?.closest('[data-node-id]')?.getAttribute('data-node-id')
79
+ return label && ownerNodeId ? { label, ownerNodeId: ownerNodeId as AnyNodeId } : null
80
+ }
81
+ const element = target as Element | null
82
+ const direct = resolve(element)
83
+ if (direct || !element?.closest?.('[data-group-selection-box]')) return direct
84
+ for (const below of document.elementsFromPoint(clientX, clientY)) {
85
+ const found = resolve(below)
86
+ if (found) return found
87
+ }
88
+ return null
89
+ }
90
+
63
91
  type EditTarget = {
64
92
  ownerNodeId: AnyNodeId
65
93
  text: string
@@ -78,6 +106,7 @@ export function FloorplanDimensionEditOverlay(): React.ReactElement | null {
78
106
  const [error, setError] = useState<string | null>(null)
79
107
  const inputRef = useRef<HTMLInputElement | null>(null)
80
108
  const unit = useViewer((state) => state.unit)
109
+ const locale = useLocale()
81
110
 
82
111
  useEffect(() => {
83
112
  let down: { x: number; y: number } | null = null
@@ -92,13 +121,9 @@ export function FloorplanDimensionEditOverlay(): React.ReactElement | null {
92
121
  return
93
122
  }
94
123
  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
124
+ const found = typeableDimensionAt(event.target, event.clientX, event.clientY)
125
+ if (!found) return
126
+ const { label, ownerNodeId } = found
102
127
 
103
128
  const value = number(label.getAttribute('data-floorplan-dimension-value'))
104
129
  const witnessStart = point(
@@ -116,7 +141,7 @@ export function FloorplanDimensionEditOverlay(): React.ReactElement | null {
116
141
  const box = label.getBoundingClientRect()
117
142
  const text = label.getAttribute('data-floorplan-dimension-text') ?? ''
118
143
  setTarget({
119
- ownerNodeId: ownerNodeId as AnyNodeId,
144
+ ownerNodeId,
120
145
  text,
121
146
  value,
122
147
  witnessStart,
@@ -154,7 +179,7 @@ export function FloorplanDimensionEditOverlay(): React.ReactElement | null {
154
179
 
155
180
  const commit = useCallback(() => {
156
181
  if (!target) return
157
- const nextLength = parseDimensionInput(draft, unit)
182
+ const nextLength = parseDimensionInput(draft, unit, locale)
158
183
  if (nextLength === null) {
159
184
  setError(`Cannot read "${draft.trim()}"`)
160
185
  return
@@ -166,7 +191,7 @@ export function FloorplanDimensionEditOverlay(): React.ReactElement | null {
166
191
  }
167
192
  setTarget(null)
168
193
  setError(null)
169
- }, [draft, target, unit])
194
+ }, [draft, locale, target, unit])
170
195
 
171
196
  if (!target) return null
172
197
  if (typeof document === 'undefined') return null
@@ -246,6 +271,21 @@ export function commitDimensionValue(args: {
246
271
  return { ok: true }
247
272
  }
248
273
 
274
+ if (resolution.target.kind === 'wall-offset') {
275
+ // The wall's own push: the whole wall slides along its normal, connected
276
+ // walls follow through the junction planner, rooms and floors re-form —
277
+ // one undo step, exactly as dragging its side arrow that far.
278
+ const delta = args.nextLength - args.target.value
279
+ if (Math.abs(delta) < 1e-9) return { ok: true }
280
+ return commitRoomPush(
281
+ { wallId: resolution.target.wallId, t0: 0, t1: 1 },
282
+ resolution.target.direction,
283
+ delta,
284
+ )
285
+ ? { ok: true }
286
+ : { ok: false, reason: "Can't move this wall there" }
287
+ }
288
+
249
289
  const plan = planDimensionDrive({
250
290
  nodes,
251
291
  target: resolution.target,
@@ -24,6 +24,7 @@ import {
24
24
  collectFloorplanLinkedLevelNodes,
25
25
  computeAffectedSiblingIds,
26
26
  floorplanAffordanceReshapeScope,
27
+ floorplanEntryStripsHandles,
27
28
  floorplanEntryYieldsToTool,
28
29
  floorplanHandleDoubleClickAffordance,
29
30
  InteractiveGeometry,
@@ -162,7 +163,6 @@ describe('site-scoped floorplan discovery', () => {
162
163
  siblingEpoch: 0,
163
164
  unit: 'metric' as const,
164
165
  metricNotation: 'meters' as const,
165
- wallDimensionReference: 'finished-faces' as const,
166
166
  visibilityRootId: site.id,
167
167
  }
168
168
  const committedSnapshot = structuredClone(overlay)
@@ -793,6 +793,17 @@ describe('floorplan entry routing while a tool is active', () => {
793
793
  expect(floorplanEntryYieldsToTool({ mode: 'select', openingPlacement: false })).toBe(false)
794
794
  expect(floorplanEntryYieldsToTool({ mode: 'delete', openingPlacement: false })).toBe(false)
795
795
  })
796
+
797
+ // A roof plane selected before arming the roof tool kept its live corner and edge handles: a
798
+ // click near its eave resized it instead of drawing.
799
+ test('a selected node shows no edit handles while a build tool is armed', () => {
800
+ const entry = { multiSelected: false, ceilingWithoutSession: false }
801
+ expect(floorplanEntryStripsHandles({ ...entry, mode: 'select' })).toBe(false)
802
+ expect(floorplanEntryStripsHandles({ ...entry, mode: 'build' })).toBe(true)
803
+ expect(floorplanEntryStripsHandles({ ...entry, mode: 'select', multiSelected: true })).toBe(
804
+ true,
805
+ )
806
+ })
796
807
  })
797
808
 
798
809
  describe('isFloorplanHierarchyVisible', () => {