@pascal-app/editor 1.0.7 → 1.1.1

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 (336) hide show
  1. package/package.json +9 -15
  2. package/src/components/editor/action-menu-button.tsx +1 -1
  3. package/src/components/editor/alignment-3d-guide-layer.tsx +1 -1
  4. package/src/components/editor/custom-camera-controls.tsx +101 -45
  5. package/src/components/editor/duplicate-node.ts +2 -1
  6. package/src/components/editor/editor-layout-v2.tsx +1 -1
  7. package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
  8. package/src/components/editor/first-person-controls.tsx +20 -1983
  9. package/src/components/editor/fit-scene-framing.test.ts +318 -0
  10. package/src/components/editor/fit-scene-framing.ts +142 -0
  11. package/src/components/editor/floating-action-menu.tsx +5 -3
  12. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  13. package/src/components/editor/floor-openings-overlay.tsx +1 -1
  14. package/src/components/editor/floor-region-controls.tsx +8 -4
  15. package/src/components/editor/floorplan-navigation-presentation.test.ts +0 -40
  16. package/src/components/editor/floorplan-navigation-presentation.ts +0 -66
  17. package/src/components/editor/floorplan-panel.tsx +199 -30
  18. package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
  19. package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
  20. package/src/components/editor/follow-pascal.test.tsx +1 -2
  21. package/src/components/editor/follow-pascal.tsx +1 -1
  22. package/src/components/editor/footprint-height-handle.tsx +2 -2
  23. package/src/components/editor/group-actions.ts +1 -2
  24. package/src/components/editor/group-floating-action-menu.tsx +1 -1
  25. package/src/components/editor/group-move-3d.ts +1 -2
  26. package/src/components/editor/group-rotate-handle.tsx +1 -2
  27. package/src/components/editor/group-transform-shared.ts +3 -1
  28. package/src/components/editor/handles/use-handle-drag.ts +1 -2
  29. package/src/components/editor/index.tsx +43 -13
  30. package/src/components/editor/inspector-extension-actions.tsx +1 -1
  31. package/src/components/editor/measurement-pill.tsx +1 -1
  32. package/src/components/editor/node-arrow-handles.tsx +2 -2
  33. package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
  34. package/src/components/editor/open-wall-ends-hint.tsx +1 -1
  35. package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
  36. package/src/components/editor/quick-measurement-card.tsx +1 -1
  37. package/src/components/editor/room-controls.tsx +10 -6
  38. package/src/components/editor/room-handles-2d.tsx +3 -3
  39. package/src/components/editor/room-handles.tsx +4 -5
  40. package/src/components/editor/room-highlight.tsx +6 -105
  41. package/src/components/editor/room-labels.test.ts +8 -822
  42. package/src/components/editor/room-labels.tsx +41 -1670
  43. package/src/components/editor/room-webgpu-materials.test.ts +1 -1
  44. package/src/components/editor/selection-manager.tsx +5 -3
  45. package/src/components/editor/site-edge-labels.tsx +7 -3
  46. package/src/components/editor/snapshot-capture-overlay.tsx +2 -5
  47. package/src/components/editor/use-floorplan-background-placement.ts +2 -1
  48. package/src/components/editor/views/plugins-view.tsx +1 -1
  49. package/src/components/editor/views/view-bar.tsx +9 -9
  50. package/src/components/editor/wall-measurement-label.tsx +31 -15
  51. package/src/components/editor/wall-move-side-handles.tsx +11 -7
  52. package/src/components/editor/wall-pull-grip.tsx +2 -2
  53. package/src/components/editor/wall-region-handles.tsx +2 -2
  54. package/src/components/editor/wall-snap-beacon-layer.tsx +53 -32
  55. package/src/components/editor-2d/floorplan-group-move.tsx +3 -4
  56. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +13 -13
  57. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +2 -3
  58. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
  59. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +23 -6
  60. package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +0 -37
  61. package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +0 -30
  62. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +13 -3
  63. package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
  64. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +14 -3
  65. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
  66. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
  67. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +30 -10
  68. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +39 -35
  69. package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
  70. package/src/components/systems/roof/roof-edit-system.tsx +33 -21
  71. package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
  72. package/src/components/tools/distribution/distribution-run-tool.tsx +151 -33
  73. package/src/components/tools/item/move-tool.tsx +1 -6
  74. package/src/components/tools/item/placement-history.test.tsx +1 -1
  75. package/src/components/tools/item/use-placement-coordinator.tsx +3 -4
  76. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +7 -3
  77. package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
  78. package/src/components/tools/registry/move-registry-node-tool.tsx +39 -16
  79. package/src/components/tools/shared/cursor-sphere.tsx +1 -1
  80. package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
  81. package/src/components/tools/shared/placement-box.tsx +2 -6
  82. package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
  83. package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
  84. package/src/components/tools/shared/segment-angle.ts +0 -4
  85. package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
  86. package/src/components/tools/tool-manager.tsx +1 -24
  87. package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
  88. package/src/components/tools/wall/wall-typed-entry.ts +70 -0
  89. package/src/components/ui/action-menu/camera-actions.tsx +2 -2
  90. package/src/components/ui/action-menu/control-modes.tsx +1 -1
  91. package/src/components/ui/action-menu/follow-pascal-pill.tsx +1 -1
  92. package/src/components/ui/action-menu/index.tsx +3 -3
  93. package/src/components/ui/action-menu/measurement-control.tsx +3 -3
  94. package/src/components/ui/action-menu/placement.ts +0 -18
  95. package/src/components/ui/action-menu/point-ask-button.tsx +1 -1
  96. package/src/components/ui/action-menu/view-toggles.tsx +5 -5
  97. package/src/components/ui/controls/finish-swatch.tsx +1 -1
  98. package/src/components/ui/controls/material-paint-panel.tsx +2 -2
  99. package/src/components/ui/controls/scene-material-list.tsx +2 -2
  100. package/src/components/ui/controls/select-control.tsx +1 -1
  101. package/src/components/ui/controls/slider-control.tsx +18 -6
  102. package/src/components/ui/controls/terrain-sculpt-panel.tsx +1 -1
  103. package/src/components/ui/controls/tool-options-panel.tsx +74 -44
  104. package/src/components/ui/floating-level-selector.tsx +3 -3
  105. package/src/components/ui/helpers/contextual-helper-panel.tsx +1 -1
  106. package/src/components/ui/helpers/helper-manager.test.tsx +54 -1
  107. package/src/components/ui/helpers/helper-manager.tsx +55 -1
  108. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  109. package/src/components/ui/panels/collections/collections-popover.tsx +2 -2
  110. package/src/components/ui/panels/floor-foundation-panel.tsx +3 -3
  111. package/src/components/ui/panels/floor-opening-panel.tsx +5 -5
  112. package/src/components/ui/panels/mobile-selection-bar.tsx +28 -24
  113. package/src/components/ui/panels/multi-height-mode.tsx +3 -3
  114. package/src/components/ui/panels/multi-selection-panel.tsx +1 -1
  115. package/src/components/ui/panels/node-display.ts +1 -1
  116. package/src/components/ui/panels/panel-manager.tsx +5 -3
  117. package/src/components/ui/panels/panel-wrapper.tsx +42 -19
  118. package/src/components/ui/panels/parametric-inspector.tsx +1 -1
  119. package/src/components/ui/panels/room-construction-rows.tsx +7 -7
  120. package/src/components/ui/panels/room-name-options.ts +1 -5
  121. package/src/components/ui/panels/room-openings-field.tsx +1 -1
  122. package/src/components/ui/panels/room-panel.tsx +7 -9
  123. package/src/components/ui/primitives/color-dot.tsx +2 -2
  124. package/src/components/ui/primitives/opacity-control.tsx +2 -2
  125. package/src/components/ui/primitives/sidebar.tsx +3 -3
  126. package/src/components/ui/right-stack.test.ts +13 -6
  127. package/src/components/ui/right-stack.tsx +22 -11
  128. package/src/components/ui/sidebar/icon-rail.tsx +1 -1
  129. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  130. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
  131. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +1 -1
  132. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +2 -2
  133. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +1 -1
  134. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +2 -6
  135. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
  136. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +1 -1
  137. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +1 -1
  138. package/src/components/ui/sidebar/panels/site-panel/index.tsx +3 -10
  139. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +1 -1
  140. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
  141. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +46 -7
  142. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +1 -1
  143. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +1 -1
  144. package/src/components/ui/sidebar/panels/site-panel/tree-node-drag.tsx +14 -5
  145. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -2
  146. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +1 -1
  147. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +3 -3
  148. package/src/components/ui/sidebar/tab-bar.tsx +1 -1
  149. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +2 -2
  150. package/src/components/ui/wall-type/wall-type-menu.tsx +1 -1
  151. package/src/components/viewer-overlay.tsx +1 -2
  152. package/src/hooks/use-auto-frame.ts +1 -2
  153. package/src/hooks/use-keyboard.ts +1 -2
  154. package/src/hooks/use-selected-room.ts +5 -7
  155. package/src/i18n/floor-footprint-name.ts +1 -1
  156. package/src/i18n/locales/en/messages.po +1036 -306
  157. package/src/i18n/locales/en/messages.ts +1 -1
  158. package/src/i18n/locales/en-XA/messages.ts +1 -1
  159. package/src/i18n/locales/es/messages.po +2267 -439
  160. package/src/i18n/locales/fr/messages.po +2267 -439
  161. package/src/i18n/locales/ja/messages.po +2267 -439
  162. package/src/i18n/locales/zh-CN/messages.po +2267 -439
  163. package/src/i18n/locales.test.ts +1 -1
  164. package/src/index.tsx +183 -178
  165. package/src/lib/below-grade.test.tsx +4 -4
  166. package/src/lib/camera-facing.test.ts +1 -1
  167. package/src/lib/camera-facing.ts +2 -3
  168. package/src/lib/contextual-help.ts +6 -0
  169. package/src/lib/editor-level-display.ts +1 -1
  170. package/src/lib/elevation-guides.ts +21 -6
  171. package/src/lib/elevator-support.ts +1 -72
  172. package/src/lib/floor-footprints.test.ts +3 -3
  173. package/src/lib/floor-footprints.ts +1 -9
  174. package/src/lib/floor-foundation-support.test.ts +8 -3
  175. package/src/lib/floor-opening-draft.ts +2 -2
  176. package/src/lib/floor-region-session.ts +1 -1
  177. package/src/lib/floorplan/annotation-visibility.test.ts +1 -1
  178. package/src/lib/floorplan/annotation-visibility.ts +1 -1
  179. package/src/lib/floorplan/floorplan-export.test.ts +1 -1
  180. package/src/lib/floorplan/floorplan-export.tsx +8 -11
  181. package/src/lib/floorplan/floorplan-extension.ts +4 -157
  182. package/src/lib/floorplan/floorplan-mode.ts +2 -1
  183. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +1 -1
  184. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +8 -6
  185. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +3 -4
  186. package/src/lib/floorplan/geometry.ts +2 -112
  187. package/src/lib/floorplan/index.ts +0 -9
  188. package/src/lib/floorplan/items.ts +2 -1
  189. package/src/lib/floorplan/open-wall-ends.ts +2 -1
  190. package/src/lib/floorplan/schedules.ts +6 -3
  191. package/src/lib/floorplan/sheet-export.ts +7 -3
  192. package/src/lib/floorplan/site-plan/site-parts.ts +7 -1
  193. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +1 -1
  194. package/src/lib/follow-pascal-presentation.ts +1 -1
  195. package/src/lib/gesture-lifecycle.ts +3 -1
  196. package/src/lib/glb-export.ts +1 -1
  197. package/src/lib/interaction/scope.ts +1 -0
  198. package/src/lib/layer-thickness.ts +1 -1
  199. package/src/lib/level-selection.test.ts +18 -2
  200. package/src/lib/level-selection.ts +31 -8
  201. package/src/lib/linear-display.ts +1 -1
  202. package/src/lib/live-gesture-round-trip.test.ts +1 -1
  203. package/src/lib/material-paint.ts +1 -1
  204. package/src/lib/measurement-label.test.ts +1 -1
  205. package/src/lib/measurements.test.ts +7 -5
  206. package/src/lib/mezzanine-selection.test.ts +1 -1
  207. package/src/lib/node-action-movement.test.tsx +115 -0
  208. package/src/lib/node-action-movement.ts +2 -0
  209. package/src/lib/paint-region-mode.ts +1 -1
  210. package/src/lib/placement-metadata.ts +4 -0
  211. package/src/lib/point-ask/describe-target.ts +6 -8
  212. package/src/lib/point-ask/region.ts +1 -1
  213. package/src/lib/point-ask/session.ts +1 -2
  214. package/src/lib/print-shell-compiler.ts +12 -1
  215. package/src/lib/print-wall-solids.test.ts +11 -0
  216. package/src/lib/print-wall-solids.ts +9 -0
  217. package/src/lib/roof-duplication.test.ts +100 -0
  218. package/src/lib/roof-duplication.ts +30 -5
  219. package/src/lib/room-built-on.test.ts +2 -5
  220. package/src/lib/room-built-on.ts +1 -15
  221. package/src/lib/room-construction-commands.ts +3 -2
  222. package/src/lib/room-controls-phase6.test.ts +1 -1
  223. package/src/lib/room-handle-drag.ts +8 -37
  224. package/src/lib/room-name-catalog.test.ts +2 -1
  225. package/src/lib/room-name-messages.test.ts +2 -1
  226. package/src/lib/room-name-messages.ts +1 -118
  227. package/src/lib/room-name-storage.test.ts +2 -1
  228. package/src/lib/room-push-dimensions.test.ts +1 -1
  229. package/src/lib/room-selection-commands.ts +2 -2
  230. package/src/lib/room-selection.test.ts +6 -6
  231. package/src/lib/room-transform-session.ts +1 -2
  232. package/src/lib/room-zone-routing.ts +2 -2
  233. package/src/lib/scene-clipboard.ts +2 -1
  234. package/src/lib/selection-routing.test.ts +0 -28
  235. package/src/lib/selection-routing.ts +1 -40
  236. package/src/lib/sfx-bus.ts +8 -32
  237. package/src/lib/sfx-player.test.ts +72 -1
  238. package/src/lib/sfx-player.ts +49 -337
  239. package/src/lib/snapping-mode.test.ts +23 -1
  240. package/src/lib/viewer-selection.test.ts +38 -10
  241. package/src/lib/wall-pull.test.ts +1 -1
  242. package/src/lib/wall-pull.ts +1 -27
  243. package/src/lib/wall-region-list.ts +3 -1
  244. package/src/lib/window-interaction.test.ts +4 -2
  245. package/src/store/use-editor.tsx +22 -26
  246. package/src/store/use-floorplan-annotation-visibility.ts +5 -5
  247. package/src/store/use-placement-typing.test.ts +165 -0
  248. package/src/store/use-placement-typing.ts +186 -0
  249. package/src/store/use-wall-region-handles-selection.ts +1 -2
  250. package/src/components/editor/__bench__/room-label-placement.bench.ts +0 -108
  251. package/src/components/editor/first-person/build-collider-world.test.ts +0 -596
  252. package/src/components/editor/first-person/build-collider-world.ts +0 -549
  253. package/src/components/editor/first-person/ground-holes.test.ts +0 -128
  254. package/src/components/editor/first-person/ground-holes.ts +0 -130
  255. package/src/components/editor/first-person/mechanism-targets.test.ts +0 -162
  256. package/src/components/editor/first-person/mechanism-targets.ts +0 -88
  257. package/src/components/editor/first-person/terrain-collider.test.ts +0 -132
  258. package/src/components/editor/first-person/terrain-collider.ts +0 -212
  259. package/src/components/editor/floorplan-camera-sync.test.ts +0 -249
  260. package/src/components/editor/floorplan-camera-sync.ts +0 -213
  261. package/src/components/editor/localized-html.tsx +0 -22
  262. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +0 -236
  263. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +0 -708
  264. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +0 -385
  265. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +0 -670
  266. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +0 -89
  267. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +0 -106
  268. package/src/components/editor-2d/svg-paths.ts +0 -124
  269. package/src/components/tools/elevator/elevator-defaults.ts +0 -8
  270. package/src/components/tools/elevator/elevator-tool.tsx +0 -372
  271. package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
  272. package/src/components/ui/action-menu/action-button.tsx +0 -89
  273. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +0 -42
  274. package/src/components/ui/primitives/button.tsx +0 -69
  275. package/src/components/ui/primitives/dropdown-menu.tsx +0 -232
  276. package/src/components/ui/primitives/popover.tsx +0 -42
  277. package/src/components/ui/primitives/switch.tsx +0 -29
  278. package/src/components/ui/primitives/tooltip.tsx +0 -57
  279. package/src/components/ui/scene-loader.tsx +0 -73
  280. package/src/components/viewer/floorplan-compass-button.tsx +0 -67
  281. package/src/components/viewer/floorplan-preview-geometry.test.ts +0 -107
  282. package/src/components/viewer/floorplan-preview-geometry.ts +0 -281
  283. package/src/components/viewer/floorplan-preview-navigation.test.ts +0 -43
  284. package/src/components/viewer/floorplan-preview-navigation.ts +0 -62
  285. package/src/components/viewer/floorplan-preview-visibility.test.ts +0 -35
  286. package/src/components/viewer/floorplan-preview-visibility.ts +0 -19
  287. package/src/components/viewer/floorplan-preview.tsx +0 -1065
  288. package/src/components/viewer/render-settings-menu.tsx +0 -294
  289. package/src/components/viewer/sky-control.tsx +0 -710
  290. package/src/components/viewer/use-viewer-camera-navigation-sync.ts +0 -140
  291. package/src/components/viewer/use-viewer-ui-sounds.test.ts +0 -15
  292. package/src/components/viewer/use-viewer-ui-sounds.ts +0 -46
  293. package/src/components/viewer/viewer-controls-bar.tsx +0 -496
  294. package/src/components/viewer/viewer-hover-label.tsx +0 -42
  295. package/src/components/viewer/viewer-rooms.tsx +0 -248
  296. package/src/components/viewer/viewer-scene-header.tsx +0 -286
  297. package/src/components/viewer/viewer-selection-manager.tsx +0 -124
  298. package/src/components/viewer/viewer-stage-modes.test.ts +0 -38
  299. package/src/components/viewer/viewer-stage-modes.ts +0 -49
  300. package/src/components/viewer/viewer-stage-switcher.tsx +0 -97
  301. package/src/components/viewer/viewer-stage.test.tsx +0 -47
  302. package/src/components/viewer/viewer-stage.tsx +0 -221
  303. package/src/components/walkthrough-hud.tsx +0 -193
  304. package/src/hooks/use-mobile.ts +0 -19
  305. package/src/i18n/level-name.ts +0 -39
  306. package/src/i18n/scene-theme-name.ts +0 -16
  307. package/src/lib/below-grade.ts +0 -133
  308. package/src/lib/camera-pose.test.ts +0 -237
  309. package/src/lib/camera-pose.ts +0 -172
  310. package/src/lib/door-interaction.ts +0 -102
  311. package/src/lib/floorplan/floorplan-extension.test.ts +0 -42
  312. package/src/lib/floorplan/floorplan-readonly.test.ts +0 -47
  313. package/src/lib/floorplan/floorplan-readonly.ts +0 -95
  314. package/src/lib/floorplan/marks.test.ts +0 -213
  315. package/src/lib/floorplan/marks.ts +0 -238
  316. package/src/lib/measurement-label.ts +0 -68
  317. package/src/lib/measurements.ts +0 -225
  318. package/src/lib/node-display-label.ts +0 -37
  319. package/src/lib/room-assembly-overlay.test.ts +0 -275
  320. package/src/lib/room-assembly-overlay.ts +0 -265
  321. package/src/lib/room-selection.ts +0 -417
  322. package/src/lib/scene-bounds.test.ts +0 -183
  323. package/src/lib/scene-bounds.ts +0 -169
  324. package/src/lib/typing-target.ts +0 -9
  325. package/src/lib/viewer-selection.ts +0 -595
  326. package/src/lib/window-interaction.ts +0 -142
  327. package/src/lib/zone-content.ts +0 -133
  328. package/src/store/camera-pose-store.test.ts +0 -29
  329. package/src/store/camera-pose-store.ts +0 -27
  330. package/src/store/navigation-sync-pose-store.test.ts +0 -34
  331. package/src/store/navigation-sync-pose-store.ts +0 -27
  332. package/src/store/use-audio.test.ts +0 -146
  333. package/src/store/use-audio.tsx +0 -169
  334. package/src/store/use-first-person-hud.ts +0 -40
  335. package/src/store/use-follow-pascal.test.ts +0 -62
  336. package/src/store/use-follow-pascal.ts +0 -53
@@ -5,15 +5,6 @@ export type FloorplanPresentationViewBox = {
5
5
  height: number
6
6
  }
7
7
 
8
- export function setFloorplanCompassRotation(
9
- compass: { style: { transform: string } } | null,
10
- rotationDeg: number,
11
- ): void {
12
- if (compass) {
13
- compass.style.transform = `rotate(${rotationDeg}deg)`
14
- }
15
- }
16
-
17
8
  type FloorplanRotationPresentation = {
18
9
  svg: {
19
10
  style: {
@@ -78,63 +69,6 @@ export function canApplyFloorplanNavigationSync(interactionInProgress: boolean):
78
69
  return !interactionInProgress
79
70
  }
80
71
 
81
- export type FloorplanNavigationSyncScheduler<Pose> = {
82
- update: (pose: Pose) => void
83
- flush: () => void
84
- discard: () => void
85
- }
86
-
87
- export function createFloorplanNavigationSyncScheduler<Pose>({
88
- applyPresentation,
89
- commit,
90
- settleMs = 120,
91
- schedule = globalThis.setTimeout,
92
- cancel = globalThis.clearTimeout,
93
- }: {
94
- applyPresentation: (pose: Pose) => void
95
- commit: (pose: Pose) => void
96
- settleMs?: number
97
- schedule?: (callback: () => void, delay: number) => ReturnType<typeof globalThis.setTimeout>
98
- cancel?: (timer: ReturnType<typeof globalThis.setTimeout>) => void
99
- }): FloorplanNavigationSyncScheduler<Pose> {
100
- let latestPose: Pose | null = null
101
- let settleTimer: ReturnType<typeof globalThis.setTimeout> | null = null
102
-
103
- const clearSettleTimer = () => {
104
- if (settleTimer === null) return
105
- cancel(settleTimer)
106
- settleTimer = null
107
- }
108
-
109
- const flush = () => {
110
- clearSettleTimer()
111
- if (latestPose === null) return
112
- const pose = latestPose
113
- latestPose = null
114
- commit(pose)
115
- }
116
-
117
- const update = (pose: Pose) => {
118
- latestPose = pose
119
- applyPresentation(pose)
120
- clearSettleTimer()
121
- settleTimer = schedule(() => {
122
- settleTimer = null
123
- if (latestPose === null) return
124
- const settledPose = latestPose
125
- latestPose = null
126
- commit(settledPose)
127
- }, settleMs)
128
- }
129
-
130
- const discard = () => {
131
- clearSettleTimer()
132
- latestPose = null
133
- }
134
-
135
- return { update, flush, discard }
136
- }
137
-
138
72
  export function finalizeFloorplanNavigation<RotationState>({
139
73
  zoomPending,
140
74
  panActive,
@@ -5,6 +5,8 @@ import {
5
5
  type AnyNode,
6
6
  type AnyNodeId,
7
7
  type BuildingNode,
8
+ buildFloorplanContext,
9
+ buildSvgPolylinePath,
8
10
  type CameraControlFrameEvent,
9
11
  type CeilingNode,
10
12
  CeilingNode as CeilingNodeSchema,
@@ -18,8 +20,13 @@ import {
18
20
  type ElevatorNode,
19
21
  emitter,
20
22
  type FenceNode,
23
+ FLOORPLAN_VIEW_ROTATION_DEG,
21
24
  type FloorplanGeometry,
25
+ floorplanLocalToWorldPoint,
26
+ formatAngleRadians,
27
+ formatLinearMeasurement,
22
28
  formatNumber as formatLocalizedNumber,
29
+ formatPolygonPath,
23
30
  type GeometryContext,
24
31
  type GuideNode,
25
32
  getRenderableSlabPolygon,
@@ -29,6 +36,7 @@ import {
29
36
  type ItemNode,
30
37
  isCurvedWall,
31
38
  type LevelNode,
39
+ linearUnitToMeters,
32
40
  loadAssetUrl,
33
41
  newZone,
34
42
  nodeRegistry,
@@ -38,6 +46,7 @@ import {
38
46
  type RoofSegmentNode,
39
47
  resolveSlabPlacementElevation,
40
48
  resolveTerrainWallConstructionOptions,
49
+ rotatePlanVector as rotateSharedPlanVector,
41
50
  runAsSingleSceneHistoryStep,
42
51
  type SeparatorNode,
43
52
  type SiteNode,
@@ -61,9 +70,24 @@ import {
61
70
  WindowNode as WindowNodeSchema,
62
71
  wallClosesRoom,
63
72
  wallRectangleCorners,
73
+ worldToFloorplanLocalPoint,
64
74
  } from '@pascal-app/core'
65
75
  import { useSegmentDraftChain, useWallSnapIndicator } from '@pascal-app/editor'
66
76
  import { getSceneTheme, useViewer } from '@pascal-app/viewer'
77
+ import {
78
+ createFloorplanNavigationSyncScheduler,
79
+ FloorplanCompassButton,
80
+ FloorplanGeometryRenderer,
81
+ type FloorplanNavigationSyncScheduler,
82
+ framingAnimates,
83
+ type NavigationSyncPose,
84
+ navigationSyncPoseStore,
85
+ publishNavigationSyncPose,
86
+ setFloorplanCompassRotation,
87
+ subscribeFloorplanCameraNavigation,
88
+ subscribeNavigationSyncPose,
89
+ useFloorplanCameraSyncBridge,
90
+ } from '@pascal-app/viewer/ui'
67
91
  import { Command, Ruler } from 'lucide-react'
68
92
  import {
69
93
  type ComponentProps,
@@ -92,14 +116,9 @@ import {
92
116
  alignFloorplanDraftPoint,
93
117
  buildFloorplanItemEntry,
94
118
  collectLevelDescendants,
95
- FLOORPLAN_VIEW_ROTATION_DEG,
96
- floorplanLocalToWorldPoint,
97
119
  getFloorplanWall as getSharedFloorplanWall,
98
- rotatePlanVector as rotateSharedPlanVector,
99
120
  type FloorplanNodeTransform as SharedFloorplanNodeTransform,
100
- worldToFloorplanLocalPoint,
101
121
  } from '../../lib/floorplan'
102
- import { buildFloorplanContext } from '../../lib/floorplan/floorplan-readonly'
103
122
  // Site-plan view. Owned under `lib/floorplan/site-plan/**`; this panel
104
123
  // only chooses between it and the registry layer, and mounts the switch.
105
124
  import {
@@ -124,7 +143,6 @@ import {
124
143
  syncKeyboardPanMotion,
125
144
  } from '../../lib/keyboard-pan'
126
145
  import { measurementHint, parseMeasurement } from '../../lib/measurement-parser'
127
- import { formatLinearMeasurement, linearUnitToMeters } from '../../lib/measurements'
128
146
  import {
129
147
  createGuideScaleReference,
130
148
  formatReferenceScaleLabel,
@@ -145,7 +163,6 @@ import {
145
163
  zoneAtLevelPoint,
146
164
  } from '../../lib/units'
147
165
  import { cn } from '../../lib/utils'
148
- import { framingAnimates } from '../../lib/viewer-selection'
149
166
  import {
150
167
  getWallDrawVariant,
151
168
  getWallLineKind,
@@ -174,10 +191,9 @@ import {
174
191
  } from '../../lib/wall-run-snap'
175
192
  import { snapBuildingLocalToWorldGrid } from '../../lib/world-grid-snap'
176
193
  import { nextZoneName } from '../../lib/zone-name'
177
- import { subscribeNavigationSyncPose } from '../../store/navigation-sync-pose-store'
178
194
  import useAlignmentGuides from '../../store/use-alignment-guides'
179
195
  import useDrawingView from '../../store/use-drawing-view'
180
- import type { GuideUiState, NavigationSyncPose } from '../../store/use-editor'
196
+ import type { GuideUiState } from '../../store/use-editor'
181
197
  import useEditor, {
182
198
  isAngleSnapActive,
183
199
  isMagneticSnapActive,
@@ -193,6 +209,7 @@ import useInteractionScope, {
193
209
  useReshapingNode,
194
210
  } from '../../store/use-interaction-scope'
195
211
  import usePlacementPreview from '../../store/use-placement-preview'
212
+ import { placementTypingLengthOptions, usePlacementTyping } from '../../store/use-placement-typing'
196
213
  import { expandSessionSelectionForNode } from '../../store/use-session-groups'
197
214
  import { useStairBuildPreview } from '../../store/use-stair-build-preview'
198
215
  import {
@@ -220,15 +237,14 @@ import { FloorplanSnapBeaconLayer } from '../editor-2d/floorplan-snap-beacon-lay
220
237
  import { FloorplanWallMoveGhostLayer } from '../editor-2d/floorplan-wall-move-ghost-layer'
221
238
  import { FloorplanDraftLayer } from '../editor-2d/renderers/floorplan-draft-layer'
222
239
  import { FloorplanDraftMeasurement } from '../editor-2d/renderers/floorplan-draft-measurement'
223
- import { FloorplanGeometryRenderer } from '../editor-2d/renderers/floorplan-geometry-renderer'
224
240
  import { FloorplanMarqueeLayer } from '../editor-2d/renderers/floorplan-marquee-layer'
225
241
  import { FloorplanPlacementPreviewLayer } from '../editor-2d/renderers/floorplan-placement-preview-layer'
242
+ import { FloorplanPlacementTypingHud } from '../editor-2d/renderers/floorplan-placement-typing-hud'
226
243
  import {
227
244
  FloorplanRegistryLayer,
228
245
  RotationAngleOverlay,
229
246
  } from '../editor-2d/renderers/floorplan-registry-layer'
230
247
  import { FloorplanVoronoiLayer } from '../editor-2d/renderers/floorplan-voronoi-layer'
231
- import { buildSvgPolylinePath, formatPolygonPath } from '../editor-2d/svg-paths'
232
248
  import { snapToHalf } from '../tools/item/placement-math'
233
249
  import {
234
250
  isBoxSelectPointerSuppressed,
@@ -253,7 +269,6 @@ import {
253
269
  } from '../tools/select/screen-rectangle-selection'
254
270
  import { collectSelectableCandidateIds } from '../tools/select/select-candidates'
255
271
  import {
256
- formatAngleRadians,
257
272
  getAngleArcToSegmentReference,
258
273
  getAngleToSegmentReference,
259
274
  getSegmentAngleReferenceAtPoint,
@@ -262,6 +277,7 @@ import {
262
277
  chainEndJoinsExistingWall,
263
278
  createInvisibleWallOnCurrentLevel,
264
279
  createWallOnCurrentLevel,
280
+ getSegmentGridStep,
265
281
  isSegmentLongEnough,
266
282
  snapWallDraftPoint,
267
283
  snapWallDraftPointDetailed,
@@ -270,25 +286,22 @@ import {
270
286
  WALL_JOIN_SNAP_RADIUS,
271
287
  type WallPlanPoint,
272
288
  } from '../tools/wall/wall-drafting'
273
- import { FloorplanCompassButton } from '../viewer/floorplan-compass-button'
289
+ import { formatWallDraftHeading, resolveTypedWallEnd } from '../tools/wall/wall-typed-entry'
274
290
  import { resolveFloorplanBackgroundSelection } from './floorplan-background-selection'
275
- import {
276
- subscribeFloorplanCameraNavigation,
277
- useFloorplanCameraSyncBridge,
278
- } from './floorplan-camera-sync'
279
291
  import {
280
292
  canApplyFloorplanNavigationSync,
281
293
  canZoomFloorplanDuringNavigation,
282
- createFloorplanNavigationSyncScheduler,
283
- type FloorplanNavigationSyncScheduler,
284
294
  type FloorplanPresentationViewBox,
285
295
  finalizeFloorplanNavigation,
286
296
  flushFloorplanRotationPresentationRestore,
287
297
  getFloorplanRotationOverscanViewBox,
288
298
  queueFloorplanRotationPresentationRestore,
289
299
  resolveFloorplanPresentationViewBox,
290
- setFloorplanCompassRotation,
291
300
  } from './floorplan-navigation-presentation'
301
+ import {
302
+ endFloorplanWallTypingOnCancel,
303
+ installFloorplanWallTypingKeys,
304
+ } from './floorplan-wall-typing-keys'
292
305
  import { swallowNextClick } from './handles/use-handle-drag'
293
306
  import { useFloorplanBackgroundPlacement } from './use-floorplan-background-placement'
294
307
  import { useFloorplanHitTesting } from './use-floorplan-hit-testing'
@@ -4503,6 +4516,7 @@ function FloorplanLinearDraftLayer({
4503
4516
  // The thickness the wall commits at, as the 3D draft shows it.
4504
4517
  const wallDraftThickness = useWallDraftThickness(DEFAULT_WALL_THICKNESS)
4505
4518
  const wallDraftEnd = useFloorplanDraftPreview((s) => s.wallDraftEnd)
4519
+ const wallTypingActive = usePlacementTyping((s) => s.isActive)
4506
4520
  const fenceDraftEnd = useFloorplanDraftPreview((s) => s.fenceDraftEnd)
4507
4521
  const roofDraftEnd = useFloorplanDraftPreview((s) => s.roofDraftEnd)
4508
4522
  const roofDraftQuarterTurn = useFloorplanDraftPreview((s) => s.roofDraftQuarterTurn)
@@ -4828,6 +4842,7 @@ function FloorplanLinearDraftLayer({
4828
4842
  <FloorplanDraftMeasurement
4829
4843
  labelBackground={isDark ? '#0f172a' : '#ffffff'}
4830
4844
  labelText={isDark ? '#e2e8f0' : '#171717'}
4845
+ lengthHidden={wallTypingActive}
4831
4846
  measurement={draftWallMeasurement}
4832
4847
  measurementStroke={measurementStroke}
4833
4848
  sceneRotationDeg={sceneRotationDeg}
@@ -4835,6 +4850,19 @@ function FloorplanLinearDraftLayer({
4835
4850
  />
4836
4851
  )}
4837
4852
 
4853
+ {wallTypingActive && isWallBuildActive && wallDraftStart ? (
4854
+ <FloorplanPlacementTypingHud
4855
+ placeholders={[
4856
+ draftWallMeasurement?.lengthLabel ?? '',
4857
+ wallDraftEnd ? formatWallDraftHeading(wallDraftStart, wallDraftEnd) : '',
4858
+ ]}
4859
+ sceneRotationDeg={sceneRotationDeg}
4860
+ unitsPerPixel={unitsPerPixel}
4861
+ x={(draftWallMeasurement?.midpoint ?? wallDraftStart)[0]}
4862
+ y={(draftWallMeasurement?.midpoint ?? wallDraftStart)[1]}
4863
+ />
4864
+ ) : null}
4865
+
4838
4866
  {polygonRoomDraft?.map((points, index) => (
4839
4867
  <FloorplanDraftLayer
4840
4868
  anchorFill={draftStroke}
@@ -4889,7 +4917,7 @@ const DRAFT_AXIS_GUIDE_EXTENT = 1000
4889
4917
  export function FloorplanPanel({
4890
4918
  /**
4891
4919
  * Element to portal the compass button into. The 2D/3D navigation poses stay
4892
- * in sync (`navigationSyncPose`), so hosting the compass on the always-visible
4920
+ * in sync (`navigationSyncPoseStore`), so hosting the compass on the always-visible
4893
4921
  * viewer-area container keeps it correct — needle and align-to-north alike —
4894
4922
  * in 2d, 3d, and split modes, while this panel itself may be display:none.
4895
4923
  */
@@ -4912,6 +4940,9 @@ export function FloorplanPanel({
4912
4940
  const floorplanSpacePanPressedRef = useRef(false)
4913
4941
  const floorplanNavigationClickSuppressedRef = useRef(false)
4914
4942
  const guideInteractionRef = useRef<GuideInteractionState | null>(null)
4943
+ // Late-bound so the window keydown effect can commit the wall draft at the
4944
+ // typed length without an ordering dependency on `handleWallPlacementPoint`.
4945
+ const wallPlacementPointRef = useRef<((point: WallPlanPoint) => void) | null>(null)
4915
4946
  const guideTransformDraftRef = useRef<GuideTransformDraft | null>(null)
4916
4947
  const pendingFenceDragRef = useRef<PendingFenceDragState | null>(null)
4917
4948
  const wallEndpointDragRef = useRef<WallEndpointDragState | null>(null)
@@ -4958,7 +4989,7 @@ export function FloorplanPanel({
4958
4989
  viewWidth: number
4959
4990
  } | null>(null)
4960
4991
  const latestNavigationSyncPoseRef = useRef<NavigationSyncPose | null>(
4961
- useEditor.getState().navigationSyncPose,
4992
+ navigationSyncPoseStore.getState().pose,
4962
4993
  )
4963
4994
  const compassNeedleRef = useRef<SVGSVGElement | null>(null)
4964
4995
  const hiddenCompassAnimationRef = useRef<number | null>(null)
@@ -5105,6 +5136,25 @@ export function FloorplanPanel({
5105
5136
  // Walls committed by the current 2D-only chain — exclusion set for the
5106
5137
  // T-junction chain-termination test (mirrors the 3D tool's `chainWallIds`).
5107
5138
  const wallChainWallIdsRef = useRef<string[]>([])
5139
+ // Split view: the 3D wall tool owns the commit and publishes each segment's
5140
+ // resolved end as the chain start. Re-base this panel's draft onto it so a
5141
+ // typed Enter in the plan projects from the segment the 3D chain is
5142
+ // actually drafting — without this, `draftStart` stays on the last point
5143
+ // the panel itself placed and the typed commit uses a stale origin. In
5144
+ // 2D-only the panel is the committer and never publishes, so this
5145
+ // subscription stays dormant there.
5146
+ useEffect(() => {
5147
+ if (!(mode === 'build' && tool === 'wall')) return
5148
+ return useSegmentDraftChain.subscribe((state, previousState) => {
5149
+ const next = state.wall
5150
+ if (next === previousState.wall) return
5151
+ if (!next) return
5152
+ setDraftStart((current) =>
5153
+ current && current[0] === next[0] && current[1] === next[1] ? current : next,
5154
+ )
5155
+ setWallChainFirstVertex((current) => current ?? next)
5156
+ })
5157
+ }, [mode, tool])
5108
5158
  // Direction of the run's last wall (a pulled run's source first): the next
5109
5159
  // holds square to it. Null before the run has a wall.
5110
5160
  const wallRunReferenceRef = useRef<WallPlanPoint | null>(null)
@@ -5115,6 +5165,8 @@ export function FloorplanPanel({
5115
5165
  },
5116
5166
  [],
5117
5167
  )
5168
+ // The pointer's own (snapped) wall draft end; a typed session re-projects it.
5169
+ const wallDraftPointerRef = useRef<WallPlanPoint | null>(null)
5118
5170
  const [fenceDraftStart, setFenceDraftStart] = useState<WallPlanPoint | null>(null)
5119
5171
  const setFenceDraftEnd = useCallback(
5120
5172
  (next: WallPlanPoint | null | ((prev: WallPlanPoint | null) => WallPlanPoint | null)) => {
@@ -7358,7 +7410,7 @@ export function FloorplanPanel({
7358
7410
  )
7359
7411
  const targetY = latestNavigationSyncPoseRef.current?.target[1] ?? floorplanGridWorldY
7360
7412
 
7361
- useEditor.getState().publishNavigationSyncPose({
7413
+ publishNavigationSyncPose({
7362
7414
  source: '2d',
7363
7415
  target: [worldCenter.x, targetY, worldCenter.z],
7364
7416
  azimuth: cameraAzimuthFromFloorplanRotation(userRotationDeg),
@@ -7379,7 +7431,7 @@ export function FloorplanPanel({
7379
7431
  const northAzimuth = cameraAzimuthFromFloorplanRotation(
7380
7432
  nearestEquivalentDegrees(0, currentRotation),
7381
7433
  )
7382
- useEditor.getState().publishNavigationSyncPose({
7434
+ publishNavigationSyncPose({
7383
7435
  source: '2d',
7384
7436
  target: [...pose.target],
7385
7437
  azimuth: northAzimuth,
@@ -7916,6 +7968,26 @@ export function FloorplanPanel({
7916
7968
  }
7917
7969
  }, [isFloorplanOpen, stopFloorplanViewAnimation])
7918
7970
 
7971
+ // Typing reshapes the plan's own draft without a pointer move.
7972
+ useEffect(() => {
7973
+ if (!draftStart) return
7974
+ return usePlacementTyping.subscribe((state, previous) => {
7975
+ if (state.fields === previous.fields && state.isActive === previous.isActive) return
7976
+ const pointer = wallDraftPointerRef.current
7977
+ if (!pointer) return
7978
+ setDraftEnd(
7979
+ (state.isActive &&
7980
+ resolveTypedWallEnd(
7981
+ draftStart,
7982
+ pointer,
7983
+ state.fields,
7984
+ placementTypingLengthOptions(unit, metricNotation),
7985
+ getSegmentGridStep(),
7986
+ )) ||
7987
+ pointer,
7988
+ )
7989
+ })
7990
+ }, [draftStart, metricNotation, setDraftEnd, unit])
7919
7991
  // Clearing an open wall draft drops a Polygon room in progress (nothing of
7920
7992
  // it was written); paths that keep it commit first.
7921
7993
  const clearWallPlacementDraft = useCallback(() => {
@@ -7926,6 +7998,8 @@ export function FloorplanPanel({
7926
7998
  wallChainWallIdsRef.current = []
7927
7999
  wallRunReferenceRef.current = null
7928
8000
  setDraftEnd(null)
8001
+ wallDraftPointerRef.current = null
8002
+ usePlacementTyping.getState().clear()
7929
8003
  useSegmentDraftChain.getState().clear('wall')
7930
8004
  // A run pulled from a wall end ends with the plan's draft: back to Select.
7931
8005
  endWallPullRun()
@@ -8033,6 +8107,7 @@ export function FloorplanPanel({
8033
8107
 
8034
8108
  useEffect(() => {
8035
8109
  const handleCancel = () => {
8110
+ if (endFloorplanWallTypingOnCancel()) return
8036
8111
  clearDraft()
8037
8112
  }
8038
8113
 
@@ -8042,6 +8117,20 @@ export function FloorplanPanel({
8042
8117
  }
8043
8118
  }, [clearDraft])
8044
8119
 
8120
+ // Split view: when the 3D wall tool stops drafting (auto-close,
8121
+ // T-junction, single-segment, cancel) it clears the published chain start.
8122
+ // That null clear must tear this panel's draft down too, or the 2D side
8123
+ // keeps a live draft that swallows typing keys against a chain the 3D
8124
+ // view already ended. Lives here (below `clearDraft`)
8125
+ // rather than beside the re-base subscription so the teardown stays
8126
+ // reachable.
8127
+ useEffect(() => {
8128
+ if (!(mode === 'build' && tool === 'wall')) return
8129
+ return useSegmentDraftChain.subscribe((state, previousState) => {
8130
+ if (state.wall === null && previousState.wall !== null) clearDraft()
8131
+ })
8132
+ }, [mode, tool, clearDraft])
8133
+
8045
8134
  // A wall end's pull grip pressed in the plan opens the Walls run at that
8046
8135
  // end: start the plan's draft there, as if the end had been clicked. A pull
8047
8136
  // from the 3D view drafts there (the plan beside it follows its moves).
@@ -8315,6 +8404,31 @@ export function FloorplanPanel({
8315
8404
  return unsubscribe
8316
8405
  }, [movingNode, scheduleMovingFloorplanNodeRefresh])
8317
8406
 
8407
+ // Read by the typed-entry listener, registered once so it stays ahead of
8408
+ // the global shortcuts.
8409
+ const wallTypingSourceRef = useRef({
8410
+ draftStart: null as WallPlanPoint | null,
8411
+ lengthOptions: placementTypingLengthOptions(unit, metricNotation),
8412
+ })
8413
+ wallTypingSourceRef.current = {
8414
+ draftStart: isWallBuildActive ? draftStart : null,
8415
+ lengthOptions: placementTypingLengthOptions(unit, metricNotation),
8416
+ }
8417
+ useEffect(
8418
+ () =>
8419
+ installFloorplanWallTypingKeys({
8420
+ getDraft: () => {
8421
+ const { draftStart: start, lengthOptions } = wallTypingSourceRef.current
8422
+ if (!start) return null
8423
+ const pointer =
8424
+ wallDraftPointerRef.current ?? useFloorplanDraftPreview.getState().wallDraftEnd ?? start
8425
+ return { start, pointer, lengthOptions }
8426
+ },
8427
+ commit: (pointer) => wallPlacementPointRef.current?.(pointer),
8428
+ }),
8429
+ [],
8430
+ )
8431
+
8318
8432
  useEffect(() => {
8319
8433
  const handleKeyDown = (event: KeyboardEvent) => {
8320
8434
  const target = event.target as HTMLElement | null
@@ -8393,7 +8507,16 @@ export function FloorplanPanel({
8393
8507
  window.removeEventListener('keyup', handleKeyUp)
8394
8508
  window.removeEventListener('blur', handleBlur)
8395
8509
  }
8396
- }, [isFloorplanOpen, isStairBuildActive, movingNode])
8510
+ }, [
8511
+ isFloorplanOpen,
8512
+ isStairBuildActive,
8513
+ movingNode,
8514
+ isWallBuildActive,
8515
+ draftStart,
8516
+ unit,
8517
+ metricNotation,
8518
+ setDraftEnd,
8519
+ ])
8397
8520
 
8398
8521
  useEffect(() => {
8399
8522
  const handleWindowPointerMove = (event: PointerEvent) => {
@@ -9653,8 +9776,23 @@ export function FloorplanPanel({
9653
9776
  // Emit `grid:move` so the registry-driven wall tool's 3D preview
9654
9777
  // tracks the cursor. The local draftEnd update below is what
9655
9778
  // drives the 2D draft polygon — both views update in parallel.
9779
+ // An open typed session re-projects the pointer onto what was typed;
9780
+ // the 3D tool gets the pointer itself and projects it the same way.
9781
+ wallDraftPointerRef.current = draftStart ? snappedPoint : null
9782
+ const wallTyping = usePlacementTyping.getState()
9783
+ const draftEndPoint =
9784
+ (draftStart &&
9785
+ wallTyping.isActive &&
9786
+ resolveTypedWallEnd(
9787
+ draftStart,
9788
+ snappedPoint,
9789
+ wallTyping.fields,
9790
+ placementTypingLengthOptions(unit, metricNotation),
9791
+ getSegmentGridStep(),
9792
+ )) ||
9793
+ snappedPoint
9656
9794
  emitFloorplanGridEvent('move', snappedPoint, event)
9657
- setCursorPoint(snappedPoint)
9795
+ setCursorPoint(draftEndPoint)
9658
9796
 
9659
9797
  if (!draftStart) {
9660
9798
  return
@@ -9663,13 +9801,13 @@ export function FloorplanPanel({
9663
9801
  setDraftEnd((previousEnd) => {
9664
9802
  if (
9665
9803
  !previousEnd ||
9666
- previousEnd[0] !== snappedPoint[0] ||
9667
- previousEnd[1] !== snappedPoint[1]
9804
+ previousEnd[0] !== draftEndPoint[0] ||
9805
+ previousEnd[1] !== draftEndPoint[1]
9668
9806
  ) {
9669
9807
  sfxEmitter.emit('sfx:grid-snap')
9670
9808
  }
9671
9809
 
9672
- return snappedPoint
9810
+ return draftEndPoint
9673
9811
  })
9674
9812
  },
9675
9813
  [
@@ -9704,6 +9842,11 @@ export function FloorplanPanel({
9704
9842
  isRoofBuildActive,
9705
9843
  isWallBuildActive,
9706
9844
  levelId,
9845
+ // `unit` / `metricNotation` feed the typed-length projection helpers
9846
+ // shared with the commit path; listed so a unit change re-binds the
9847
+ // move preview to the new bare-unit parse.
9848
+ metricNotation,
9849
+ unit,
9707
9850
  publishFloorplanNavigationPose,
9708
9851
  referenceScaleDraft,
9709
9852
  siteVertexDragState,
@@ -9889,7 +10032,21 @@ export function FloorplanPanel({
9889
10032
  )
9890
10033
 
9891
10034
  const handleWallPlacementPoint = useCallback(
9892
- (point: WallPlanPoint) => {
10035
+ (rawPoint: WallPlanPoint) => {
10036
+ // An open typed session commits what its preview shows: the pointer
10037
+ // re-projected onto what was typed, never re-snapped.
10038
+ const typing = usePlacementTyping.getState()
10039
+ const typedEnd =
10040
+ draftStart && typing.isActive
10041
+ ? resolveTypedWallEnd(
10042
+ draftStart,
10043
+ rawPoint,
10044
+ typing.fields,
10045
+ placementTypingLengthOptions(unit, metricNotation),
10046
+ getSegmentGridStep(),
10047
+ )
10048
+ : null
10049
+ const point: WallPlanPoint = typedEnd ?? rawPoint
9893
10050
  if (!draftStart) {
9894
10051
  wallConstructionOptionsRef.current = levelId
9895
10052
  ? resolveTerrainWallConstructionOptions(
@@ -9950,6 +10107,7 @@ export function FloorplanPanel({
9950
10107
  setDraftStart(point)
9951
10108
  setDraftEnd(point)
9952
10109
  setCursorPoint(point)
10110
+ usePlacementTyping.getState().clear()
9953
10111
  return
9954
10112
  }
9955
10113
  if (viewIs2DOnly) {
@@ -10015,16 +10173,26 @@ export function FloorplanPanel({
10015
10173
  setDraftStart(nextStart)
10016
10174
  setDraftEnd(nextStart)
10017
10175
  setCursorPoint(nextStart)
10176
+ // What was typed belongs to this segment; the next starts afresh.
10177
+ usePlacementTyping.getState().clear()
10018
10178
  },
10019
10179
  [
10020
10180
  clearWallPlacementDraft,
10021
10181
  draftStart,
10022
10182
  levelId,
10183
+ metricNotation,
10184
+ unit,
10023
10185
  wallChainFirstVertex,
10024
10186
  setDraftEnd,
10025
10187
  setCursorPoint,
10026
10188
  ],
10027
10189
  )
10190
+ // Latest-ref for the Enter-commit path: assigning the ref inside the
10191
+ // callback itself froze the first-render closure (draftStart === null), so
10192
+ // a typed Enter re-started the draft instead of committing the wall.
10193
+ useEffect(() => {
10194
+ wallPlacementPointRef.current = handleWallPlacementPoint
10195
+ }, [handleWallPlacementPoint])
10028
10196
  const { getFloorplanHitIdAtPoint, getFloorplanSelectionIdsInBounds } = useFloorplanHitTesting({
10029
10197
  sceneRef: floorplanSceneRef,
10030
10198
  })
@@ -10254,6 +10422,7 @@ export function FloorplanPanel({
10254
10422
  unit,
10255
10423
  emitFloorplanGridEvent,
10256
10424
  setCursorPoint,
10425
+ locale,
10257
10426
  ],
10258
10427
  )
10259
10428
  const handleSvgClick = useCallback(