@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
@@ -3,6 +3,7 @@
3
3
  import {
4
4
  type AnyNodeId,
5
5
  emitter,
6
+ formatFixed,
6
7
  type GridEvent,
7
8
  sceneRegistry,
8
9
  usePascalI18n,
@@ -18,18 +19,19 @@ import {
18
19
  isGridSnapActive,
19
20
  isMagneticSnapActive,
20
21
  isTypingTarget,
22
+ isViewVisible,
21
23
  markToolCancelConsumed,
22
24
  publishPlacementSurface,
23
25
  triggerSFX,
24
26
  useEditor,
25
27
  useInteractionScope,
26
28
  } from '@pascal-app/editor'
29
+ import { LocalizedHtml as Html } from '@pascal-app/viewer/ui'
27
30
  import { useThree } from '@react-three/fiber'
28
31
  import { type RefObject, useCallback, useEffect, useRef, useState } from 'react'
29
32
  import { createPortal } from 'react-dom'
30
33
  import { type Group, Vector3 } from 'three'
31
34
  import { messageWithSlot } from '../../../i18n/message-slot'
32
- import { Html } from '../../editor/localized-html'
33
35
  import type { RunSurfaceBounds, RunSurfaceTarget } from './distribution-run-contract'
34
36
  import { clearDrawAlignment } from './draw-alignment'
35
37
  import type { RunBodyHit, ScenePort } from './ports'
@@ -226,6 +228,11 @@ export type DistributionRunToolConfig = {
226
228
  active: boolean
227
229
  levelId: AnyNodeId | null
228
230
  toolName: string
231
+ routingMode?: 'surface' | 'horizontal' | 'vertical'
232
+ /** Level-local centreline elevation, or the target height for a vertical leg. */
233
+ elevation?: number
234
+ targetElevation?: number
235
+ connectCrossings?: boolean
229
236
  initialStart?: RunPoint | null
230
237
  initialConnection?: RunConnection | null
231
238
  getPorts: () => ScenePort[]
@@ -524,6 +531,7 @@ function surfacePointFromEvent(
524
531
 
525
532
  export function useDistributionRunTool(config: DistributionRunToolConfig) {
526
533
  const { camera, gl } = useThree()
534
+ const planVisible = useEditor((state) => isViewVisible(state, '2d'))
527
535
  const initialStartRef = useRef<RunPoint | null>(
528
536
  config.initialStart ? [...config.initialStart] : null,
529
537
  )
@@ -534,6 +542,7 @@ export function useDistributionRunTool(config: DistributionRunToolConfig) {
534
542
  const [cursor, setCursor] = useState<RunPoint | null>(initialStartRef.current)
535
543
  const [snapTarget, setSnapTarget] = useState<RunPoint | null>(null)
536
544
  const [snapScreen, setSnapScreen] = useState<PortScreenPoint | null>(null)
545
+ const [cursorScreen, setCursorScreen] = useState<PortScreenPoint | null>(null)
537
546
  const [endConnection, setEndConnection] = useState<RunConnection>({
538
547
  port: null,
539
548
  body: null,
@@ -592,6 +601,7 @@ export function useDistributionRunTool(config: DistributionRunToolConfig) {
592
601
  const typed = Number.parseFloat(lengthInputRef.current)
593
602
  if (
594
603
  !currentStart ||
604
+ configRef.current.routingMode === 'vertical' ||
595
605
  !Number.isFinite(typed) ||
596
606
  typed <= 0 ||
597
607
  resolved.port ||
@@ -617,28 +627,47 @@ export function useDistributionRunTool(config: DistributionRunToolConfig) {
617
627
  }
618
628
  }
619
629
 
630
+ // A vertical route rises from its start to the target elevation wherever the
631
+ // pointer is, so it resolves without a pointer event.
632
+ const verticalTarget = (currentStart: RunPoint): ResolvedRunPoint => ({
633
+ point: [
634
+ currentStart[0],
635
+ configRef.current.targetElevation ?? currentStart[1],
636
+ currentStart[2],
637
+ ],
638
+ frame: createRunSurfaceFrame(currentStart),
639
+ surfaceTarget: null,
640
+ snapped: null,
641
+ directionMode: 'vertical',
642
+ port: null,
643
+ body: null,
644
+ })
645
+
620
646
  const resolvePoint = (event: GridEvent): ResolvedRunPoint => {
621
647
  const adapter = configRef.current
622
648
  const hit = surfacePointFromEvent(event, adapter.levelId)
623
649
  const currentStart = startRef.current
624
650
  const previous = lastResolvedRef.current
625
- const lockedWallTarget = currentStart ? lockedWallTargetRef.current : null
651
+ const explicitRoute = adapter.routingMode && adapter.routingMode !== 'surface'
652
+ const lockedWallTarget = currentStart && !explicitRoute ? lockedWallTargetRef.current : null
626
653
  const lockedWallHit =
627
654
  lockedWallTarget && isSameRunWallSurface(lockedWallTarget, event.surfaceHit)
628
- const working = lockedWallTarget
629
- ? {
630
- ...lockedWallTarget.frame,
631
- origin: lockedWallTarget.frame.origin.map(
632
- (value, index) =>
633
- value +
634
- lockedWallTarget.frame.normal[index]! *
635
- (adapter.surfaceClearance?.(lockedWallTarget) ?? 0),
636
- ) as RunPoint,
637
- }
638
- : !event.surfaceHit && previous?.surfaceTarget?.kind === 'wall' && currentStart
639
- ? createRunSurfaceFrame(currentStart, UP)
640
- : (previous?.frame ?? (currentStart ? createRunSurfaceFrame(currentStart) : null))
641
- const hasSurface = !!event.surfaceHit || !working || !event.localRay
655
+ const working = explicitRoute
656
+ ? createRunSurfaceFrame([0, currentStart?.[1] ?? adapter.elevation ?? 0, 0])
657
+ : lockedWallTarget
658
+ ? {
659
+ ...lockedWallTarget.frame,
660
+ origin: lockedWallTarget.frame.origin.map(
661
+ (value, index) =>
662
+ value +
663
+ lockedWallTarget.frame.normal[index]! *
664
+ (adapter.surfaceClearance?.(lockedWallTarget) ?? 0),
665
+ ) as RunPoint,
666
+ }
667
+ : !event.surfaceHit && previous?.surfaceTarget?.kind === 'wall' && currentStart
668
+ ? createRunSurfaceFrame(currentStart, UP)
669
+ : (previous?.frame ?? (currentStart ? createRunSurfaceFrame(currentStart) : null))
670
+ const hasSurface = !explicitRoute && (!!event.surfaceHit || !working || !event.localRay)
642
671
  const target = lockedWallTarget ?? (hasSurface ? hit.target : null)
643
672
  const hitForCursor = lockedWallTarget
644
673
  ? lockedWallHit
@@ -651,16 +680,25 @@ export function useDistributionRunTool(config: DistributionRunToolConfig) {
651
680
  hit: hitForCursor,
652
681
  working,
653
682
  ray: event.localRay,
654
- fallback: previous?.point ?? currentStart ?? hit.point,
683
+ fallback: explicitRoute ? [...hit.point] : (previous?.point ?? currentStart ?? hit.point),
655
684
  clearance: adapter.surfaceClearance?.(target) ?? 0,
656
685
  })
686
+ const routeElevation = currentStart?.[1] ?? adapter.elevation ?? 0
687
+ if (explicitRoute) {
688
+ resolved.point[1] = routeElevation
689
+ if (currentStart && adapter.routingMode === 'vertical') return verticalTarget(currentStart)
690
+ }
657
691
  const bypass = event.nativeEvent?.altKey === true
658
692
  const gridStep = !bypass && isGridSnapActive() ? useEditor.getState().gridSnapStep : 0
659
693
  const angleLocked = !bypass && (isAngleSnapActive() || isGridSnapActive())
660
694
  let point = currentStart
661
695
  ? resolved.point
662
696
  : snapRunPointToSurface(resolved.point, resolved.frame, gridStep)
663
- const forcedDirection = forcedDirectionRef.current
697
+ const selectedDirection = forcedDirectionRef.current
698
+ const forcedDirection =
699
+ explicitRoute && selectedDirection && Math.abs(selectedDirection[1]) > 1e-6
700
+ ? null
701
+ : selectedDirection
664
702
  if (currentStart && forcedDirection) {
665
703
  point = projectRunToDirection(currentStart, point, forcedDirection)
666
704
  }
@@ -673,7 +711,11 @@ export function useDistributionRunTool(config: DistributionRunToolConfig) {
673
711
  if (currentStart && gridStep > 0) {
674
712
  point = snapRunLength(currentStart, point, gridStep)
675
713
  }
676
- const sample = { frame: resolved.frame, surfaceTarget: target }
714
+ const drawingTarget: RunSurfaceTarget | null =
715
+ explicitRoute && adapter.levelId
716
+ ? { kind: 'surface', levelId: adapter.levelId, frame: resolved.frame }
717
+ : target
718
+ const sample = { frame: resolved.frame, surfaceTarget: drawingTarget }
677
719
  const native = (event.nativeEvent ?? {}) as {
678
720
  clientX?: number
679
721
  clientY?: number
@@ -713,11 +755,22 @@ export function useDistributionRunTool(config: DistributionRunToolConfig) {
713
755
  const screen = projectConnection(candidate)
714
756
  return !!screen && Math.hypot(screen.x - pointer[0], screen.y - pointer[1]) <= 12
715
757
  }
758
+ // An explicit route may only connect at the height its free end reaches above that
759
+ // plan point, which includes the adapter's fall.
760
+ const routeHeightAt = (candidate: readonly number[]): number => {
761
+ const level: RunPoint = [candidate[0]!, routeElevation, candidate[2]!]
762
+ return currentStart
763
+ ? (adapter.resolveFreeEnd?.(currentStart, level, startConnectionRef.current) ?? level)[1]
764
+ : routeElevation
765
+ }
766
+ const onRoute = (candidate: readonly number[]) =>
767
+ !explicitRoute || Math.abs(candidate[1]! - routeHeightAt(candidate)) < 1e-4
716
768
  const resolveConnection = (candidate: RunPoint): ResolvedRunPoint | null => {
717
769
  if (bypass || !(isGridSnapActive() || isMagneticSnapActive() || isAngleSnapActive()))
718
770
  return null
719
771
  const source = startConnectionRef.current.port
720
- const hit = findScreenPort(adapter.getPorts(), pointer, projectConnection, source)
772
+ const ports = adapter.getPorts().filter((port) => onRoute(port.position))
773
+ const hit = findScreenPort(ports, pointer, projectConnection, source)
721
774
  if (hit) {
722
775
  return {
723
776
  frame: createRunSurfaceFrame([...hit.port.position]),
@@ -730,8 +783,19 @@ export function useDistributionRunTool(config: DistributionRunToolConfig) {
730
783
  body: null,
731
784
  }
732
785
  }
733
- const body = adapter.findBody(candidate, target)
734
- if (body && acceptsConnection(body.point) && body.nodeId !== source?.nodeId) {
786
+ const body =
787
+ adapter.connectCrossings === false
788
+ ? null
789
+ : adapter.findBody(
790
+ explicitRoute ? [candidate[0], routeHeightAt(candidate), candidate[2]] : candidate,
791
+ drawingTarget,
792
+ )
793
+ if (
794
+ body &&
795
+ onRoute(body.point) &&
796
+ acceptsConnection(body.point) &&
797
+ body.nodeId !== source?.nodeId
798
+ ) {
735
799
  return {
736
800
  ...sample,
737
801
  point: body.point,
@@ -747,7 +811,7 @@ export function useDistributionRunTool(config: DistributionRunToolConfig) {
747
811
  if (connection) return connection
748
812
  // Rank displayed directions before a surface projection discards height.
749
813
  if (currentStart && event.localRay && !bypass && (forcedDirection || angleLocked)) {
750
- const source = startConnectionRef.current.port?.direction ?? null
814
+ const source = explicitRoute ? null : (startConnectionRef.current.port?.direction ?? null)
751
815
  const candidates = forcedDirection
752
816
  ? [forcedDirection]
753
817
  : source
@@ -803,7 +867,7 @@ export function useDistributionRunTool(config: DistributionRunToolConfig) {
803
867
  forcedDirection ||
804
868
  (Math.abs(directionHit.direction[1]) > 1e-6 && target?.kind === 'floor')
805
869
  ? null
806
- : target,
870
+ : drawingTarget,
807
871
  snapped: null,
808
872
  directionMode: 'angle',
809
873
  port: null,
@@ -951,7 +1015,12 @@ export function useDistributionRunTool(config: DistributionRunToolConfig) {
951
1015
  setAltActive(false)
952
1016
  }
953
1017
 
1018
+ const updateCursorScreen = (event: GridEvent) => {
1019
+ const pointer = getGridEventScreenProjection(event)?.pointer
1020
+ setCursorScreen(pointer ? { x: pointer[0], y: pointer[1], depth: 0 } : null)
1021
+ }
954
1022
  const onMove = (event: RunPointerEvent) => {
1023
+ updateCursorScreen(event)
955
1024
  lastPointerRef.current = event
956
1025
  const clientY = (event.nativeEvent as { clientY?: number } | undefined)?.clientY
957
1026
  if (typeof clientY === 'number') lastClientYRef.current = clientY
@@ -973,6 +1042,7 @@ export function useDistributionRunTool(config: DistributionRunToolConfig) {
973
1042
  }
974
1043
 
975
1044
  const onClick = (event: GridEvent) => {
1045
+ updateCursorScreen(event)
976
1046
  event.nativeEvent?.stopPropagation?.()
977
1047
  const currentStart = startRef.current
978
1048
  if (altAnchorRef.current && currentStart) {
@@ -993,8 +1063,10 @@ export function useDistributionRunTool(config: DistributionRunToolConfig) {
993
1063
  port: resolved.port,
994
1064
  body: resolved.port ? null : resolved.body,
995
1065
  }
996
- if (shouldLockRunWallSurface(resolved.surfaceTarget, connection)) {
997
- lockedWallTargetRef.current = resolved.surfaceTarget
1066
+ if (!configRef.current.routingMode || configRef.current.routingMode === 'surface') {
1067
+ if (shouldLockRunWallSurface(resolved.surfaceTarget, connection)) {
1068
+ lockedWallTargetRef.current = resolved.surfaceTarget
1069
+ }
998
1070
  }
999
1071
  triggerSFX('sfx:grid-snap')
1000
1072
  startConnectionRef.current = connection
@@ -1005,6 +1077,10 @@ export function useDistributionRunTool(config: DistributionRunToolConfig) {
1005
1077
  setSnapTarget(resolved.snapped)
1006
1078
  setSnapScreen(resolved.snapScreen ?? null)
1007
1079
  setEndConnection({ port: null, body: null })
1080
+ // A vertical route ends at the target, not under the pointer: show that
1081
+ // riser now so the next click or Enter places what the preview shows.
1082
+ if (configRef.current.routingMode === 'vertical')
1083
+ updateCursor(verticalTarget(resolved.point))
1008
1084
  return
1009
1085
  }
1010
1086
  const typedResolved = applyTypedLength(resolved)
@@ -1036,6 +1112,11 @@ export function useDistributionRunTool(config: DistributionRunToolConfig) {
1036
1112
  setAltActive(true)
1037
1113
  return
1038
1114
  }
1115
+ if (
1116
+ configRef.current.routingMode === 'vertical' &&
1117
+ (event.key === 'Tab' || /^[0-9.,]$/.test(event.key))
1118
+ )
1119
+ return
1039
1120
  if (
1040
1121
  event.key === 'Tab' &&
1041
1122
  startRef.current &&
@@ -1045,7 +1126,10 @@ export function useDistributionRunTool(config: DistributionRunToolConfig) {
1045
1126
  ) {
1046
1127
  event.preventDefault()
1047
1128
  event.stopImmediatePropagation()
1048
- const source = startConnectionRef.current.port?.direction
1129
+ const source =
1130
+ configRef.current.routingMode === 'horizontal'
1131
+ ? null
1132
+ : startConnectionRef.current.port?.direction
1049
1133
  const candidates = source
1050
1134
  ? run3DDirectionCandidates(source)
1051
1135
  : runHorizontalDirectionCandidates(null)
@@ -1117,7 +1201,10 @@ export function useDistributionRunTool(config: DistributionRunToolConfig) {
1117
1201
  }
1118
1202
 
1119
1203
  refreshCursorRef.current = () => {
1120
- if (lastPointerRef.current)
1204
+ const currentStart = startRef.current
1205
+ if (currentStart && configRef.current.routingMode === 'vertical')
1206
+ updateCursor(verticalTarget(currentStart))
1207
+ else if (lastPointerRef.current)
1121
1208
  updateCursor(applyTypedLength(resolvePoint(lastPointerRef.current)))
1122
1209
  else if (lastResolvedRef.current) updateCursor(applyTypedLength(lastResolvedRef.current))
1123
1210
  }
@@ -1151,18 +1238,39 @@ export function useDistributionRunTool(config: DistributionRunToolConfig) {
1151
1238
  }
1152
1239
  }, [camera, gl, config.active, updateLengthInput])
1153
1240
 
1241
+ // The resolver reads config through a ref; changing these controls must refresh a stationary cursor.
1242
+ // biome-ignore lint/correctness/useExhaustiveDependencies: configuration changes invalidate the cursor
1243
+ useEffect(() => {
1244
+ forcedDirectionRef.current = null
1245
+ if (config.routingMode !== 'surface') lockedWallTargetRef.current = null
1246
+ refreshCursor()
1247
+ }, [
1248
+ config.routingMode,
1249
+ config.elevation,
1250
+ config.targetElevation,
1251
+ config.connectCrossings,
1252
+ refreshCursor,
1253
+ ])
1254
+
1154
1255
  return {
1155
1256
  refreshCursor,
1257
+ routingMode: config.routingMode,
1156
1258
  start,
1157
1259
  cursor,
1158
1260
  snapTarget,
1159
1261
  snapScreen,
1262
+ cursorScreen: planVisible ? cursorScreen : null,
1160
1263
  altActive,
1161
1264
  directionMode,
1162
1265
  lengthInput,
1163
1266
  validationMessage,
1164
1267
  onLengthInputChange: updateLengthInput,
1165
1268
  onDirectionSelect: (direction: RunPoint) => {
1269
+ if (
1270
+ configRef.current.routingMode === 'vertical' ||
1271
+ (configRef.current.routingMode === 'horizontal' && Math.abs(direction[1]) > 1e-6)
1272
+ )
1273
+ return
1166
1274
  forcedDirectionRef.current = direction
1167
1275
  setDirectionMode('angle')
1168
1276
  refreshCursorRef.current()
@@ -1183,6 +1291,7 @@ export function DistributionRunCursor({
1183
1291
  extraParts = [],
1184
1292
  cursorRef,
1185
1293
  directionMode,
1294
+ routingMode,
1186
1295
  startDirection,
1187
1296
  lengthInput,
1188
1297
  validationMessage,
@@ -1198,6 +1307,7 @@ export function DistributionRunCursor({
1198
1307
  extraParts?: DimensionPillPart[]
1199
1308
  cursorRef?: RefObject<Group | null>
1200
1309
  directionMode: RunDirectionMode
1310
+ routingMode?: 'surface' | 'horizontal' | 'vertical'
1201
1311
  startDirection?: readonly [number, number, number] | null
1202
1312
  lengthInput?: string
1203
1313
  validationMessage?: string | null
@@ -1208,8 +1318,8 @@ export function DistributionRunCursor({
1208
1318
  const i18n = usePascalI18n()
1209
1319
  const lengthFieldRef = useRef<HTMLInputElement>(null)
1210
1320
  useEffect(() => {
1211
- if (start) lengthFieldRef.current?.focus()
1212
- }, [start])
1321
+ if (start && routingMode !== 'vertical') lengthFieldRef.current?.focus()
1322
+ }, [start, routingMode])
1213
1323
  if (!cursor) return null
1214
1324
  const parts: DimensionPillPart[] = [
1215
1325
  ...(start
@@ -1270,9 +1380,9 @@ export function DistributionRunCursor({
1270
1380
  cursor={cursor}
1271
1381
  mode={directionMode}
1272
1382
  snapped={!!snapTarget}
1273
- sourceDirection={startDirection ?? null}
1383
+ sourceDirection={routingMode === 'horizontal' ? null : (startDirection ?? null)}
1274
1384
  start={start}
1275
- onDirectionSelect={onDirectionSelect}
1385
+ onDirectionSelect={routingMode === 'vertical' ? undefined : onDirectionSelect}
1276
1386
  />
1277
1387
  )}
1278
1388
  <CursorSphere
@@ -1291,7 +1401,15 @@ export function DistributionRunCursor({
1291
1401
  >
1292
1402
  <div className="flex flex-col items-center gap-1">
1293
1403
  <DimensionPill parts={parts} primary={primary} unit={unit} />
1294
- {start ? (
1404
+ {start && routingMode === 'vertical' ? (
1405
+ <div className="rounded-full border border-border/60 bg-background/90 px-3 py-1 text-xs shadow-sm">
1406
+ {i18n._({
1407
+ id: 'editor.tools.distribution.distributionRunTool.targetElevation',
1408
+ message: 'Target elevation: {elevation} m · click or Enter to place',
1409
+ values: { elevation: formatFixed(cursor[1], 2, i18n.getLocale()) },
1410
+ })}
1411
+ </div>
1412
+ ) : start ? (
1295
1413
  <label className="rounded-full border border-border/60 bg-background/90 px-3 py-1 text-[11px] tabular-nums text-muted-foreground shadow-sm backdrop-blur">
1296
1414
  {messageWithSlot(
1297
1415
  i18n,
@@ -1,8 +1,7 @@
1
- import type { AnyNodeId, ElevatorNode, SpawnNode } from '@pascal-app/core'
1
+ import type { AnyNodeId, SpawnNode } from '@pascal-app/core'
2
2
  import { createSceneApi, nodeRegistry, useScene } from '@pascal-app/core'
3
3
  import { Suspense, useMemo } from 'react'
4
4
  import { useMovingNode } from '../../../store/use-interaction-scope'
5
- import { MoveElevatorTool } from '../elevator/move-elevator-tool'
6
5
  import { MoveRegistryNodeTool } from '../registry/move-registry-node-tool'
7
6
  import { getRegistryAffordanceTool } from '../shared/affordance-dispatch'
8
7
 
@@ -20,8 +19,6 @@ import { getRegistryAffordanceTool } from '../shared/affordance-dispatch'
20
19
  * 2. `MoveRegistryNodeTool` — generic translate-on-XZ for kinds that only
21
20
  * declare `capabilities.movable` (shelf, spawn, duct-terminal,
22
21
  * hvac-equipment, …).
23
- * 3. `elevator` is the lone remaining legacy arm — its bespoke cab/shaft
24
- * mover hasn't been ported to a kind-owned affordance yet.
25
22
  */
26
23
  export const MoveTool: React.FC<{
27
24
  onNodeMoved?: (nodeId: AnyNodeId) => void
@@ -47,7 +44,5 @@ export const MoveTool: React.FC<{
47
44
  return <MoveRegistryNodeTool node={movingNode} />
48
45
  }
49
46
 
50
- if (movingNode.type === 'elevator')
51
- return <MoveElevatorTool node={movingNode as ElevatorNode} onCommitted={onNodeMoved} />
52
47
  return null
53
48
  }
@@ -22,12 +22,12 @@ import {
22
22
  WallNode,
23
23
  } from '@pascal-app/core'
24
24
  import { useViewer } from '@pascal-app/viewer'
25
+ import { LocalizedHtml as Html } from '@pascal-app/viewer/ui'
25
26
  import { act, create } from '@react-three/test-renderer'
26
27
  import { Children, Component, cloneElement, isValidElement, type ReactNode } from 'react'
27
28
  import { Group } from 'three'
28
29
  import useEditor from '../../../store/use-editor'
29
30
  import useInteractionScope from '../../../store/use-interaction-scope'
30
- import { Html } from '../../editor/localized-html'
31
31
  import { useDraftNode } from './use-draft-node'
32
32
  import { usePlacementCoordinator } from './use-placement-coordinator'
33
33
 
@@ -9,6 +9,7 @@ import {
9
9
  collectAlignmentAnchors,
10
10
  emitter,
11
11
  findLevelAncestorId,
12
+ formatLinearMeasurement,
12
13
  type GridEvent,
13
14
  getScaledDimensions,
14
15
  getWallBodyCenterOffset,
@@ -29,7 +30,8 @@ import {
29
30
  useSpatialQuery,
30
31
  type WallEvent,
31
32
  } from '@pascal-app/core'
32
- import { useViewer } from '@pascal-app/viewer'
33
+ import { isTypingTarget, useViewer } from '@pascal-app/viewer'
34
+ import { LocalizedHtml as Html } from '@pascal-app/viewer/ui'
33
35
  import { useFrame, useThree } from '@react-three/fiber'
34
36
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
35
37
  import {
@@ -49,7 +51,6 @@ import {
49
51
  publishPlacementSurface,
50
52
  } from '../../../lib/active-placement-surface'
51
53
  import { EDITOR_LAYER } from '../../../lib/constants'
52
- import { formatLinearMeasurement } from '../../../lib/measurements'
53
54
  import { isFreshPlacementMetadata } from '../../../lib/placement-metadata'
54
55
  import { createMovementSfxTick } from '../../../lib/sfx/movement-tick'
55
56
  import { sfxEmitter } from '../../../lib/sfx-bus'
@@ -58,7 +59,6 @@ import {
58
59
  surfaceFramePose,
59
60
  updateSurfaceNode as writeSurfaceNode,
60
61
  } from '../../../lib/surface-attachment'
61
- import { isTypingTarget } from '../../../lib/typing-target'
62
62
  import { createWallPointerTracker } from '../../../lib/wall-placement-pointer'
63
63
  import {
64
64
  projectAlignmentGuidesWorldToActiveBuildingLocal,
@@ -73,7 +73,6 @@ import useEditor, {
73
73
  import useFacingPose from '../../../store/use-facing-pose'
74
74
  import { getMovingNode } from '../../../store/use-interaction-scope'
75
75
  import usePlacementPreview from '../../../store/use-placement-preview'
76
- import { Html } from '../../editor/localized-html'
77
76
  import {
78
77
  createItemSurfaceGridDispatch,
79
78
  createItemSurfacePointerArbitration,
@@ -4,12 +4,18 @@ import {
4
4
  type AnyNode,
5
5
  type AnyNodeId,
6
6
  emitter,
7
+ formatLinearMeasurement,
7
8
  sceneRegistry,
8
9
  useScene,
9
10
  type WallFace,
10
11
  type WallNode,
11
12
  } from '@pascal-app/core'
12
- import { getSceneTheme, setSurfaceRaycastLayers, useViewer } from '@pascal-app/viewer'
13
+ import {
14
+ getSceneTheme,
15
+ isTypingTarget,
16
+ setSurfaceRaycastLayers,
17
+ useViewer,
18
+ } from '@pascal-app/viewer'
13
19
  import { useFrame, useThree } from '@react-three/fiber'
14
20
  import { useEffect, useMemo, useRef } from 'react'
15
21
  import {
@@ -27,9 +33,7 @@ import {
27
33
  import { LineBasicNodeMaterial, MeshBasicNodeMaterial } from 'three/webgpu'
28
34
  import { markToolCancelConsumed } from '../../../hooks/use-keyboard'
29
35
  import { EDITOR_LAYER } from '../../../lib/constants'
30
- import { formatLinearMeasurement } from '../../../lib/measurements'
31
36
  import { paintRegionTargets, usePaintRegionMode } from '../../../lib/paint-region-mode'
32
- import { isTypingTarget } from '../../../lib/typing-target'
33
37
  import {
34
38
  activeWallRegionGesture,
35
39
  activeWallRegionTarget,
@@ -107,6 +107,7 @@ test('focus completion: mounted registry mover owns only outside R T and Alt', a
107
107
  cancelAnimationFrame: { configurable: true, value: (key: number) => frames.delete(key) },
108
108
  IS_REACT_ACT_ENVIRONMENT: { configurable: true, value: true },
109
109
  })
110
+ let supportY = 0
110
111
  core.registerNode({
111
112
  kind: node.type,
112
113
  schemaVersion: 1,
@@ -114,7 +115,14 @@ test('focus completion: mounted registry mover owns only outside R T and Alt', a
114
115
  schema: z.object({ type: z.literal(node.type) }).passthrough(),
115
116
  defaults: () => ({}),
116
117
  capabilities: {
117
- movable: { isValidPosition: () => false },
118
+ movable: {
119
+ isValidPosition: () => false,
120
+ resolvePosition: ({ position }: { position: [number, number, number] }) => [
121
+ position[0],
122
+ supportY,
123
+ position[2],
124
+ ],
125
+ },
118
126
  floorPlaced: {
119
127
  collides: true,
120
128
  footprint: (candidate: AnyNode) => ({
@@ -251,6 +259,39 @@ test('focus completion: mounted registry mover owns only outside R T and Alt', a
251
259
  expect((await key('t', false)).defaultPrevented).toBe(true)
252
260
  expect(mesh.rotation.y).toBeCloseTo(0.25)
253
261
  expect(core.useLiveTransforms.getState().transforms.get(id)?.rotation).toBeCloseTo(0.25)
262
+ supportY = 0.75
263
+ await act(async () => {
264
+ core.emitter.emit('grid:move', {
265
+ nativeEvent: {
266
+ target: { closest: () => null },
267
+ } as unknown as import('@pascal-app/core').GridEvent['nativeEvent'],
268
+ position: [7, 0, 3],
269
+ localPosition: [7, 0, 3],
270
+ stopPropagation() {},
271
+ } as import('@pascal-app/core').GridEvent)
272
+ flushFrames()
273
+ })
274
+ expect(mesh.position.y).toBeCloseTo(0.75)
275
+ expect(core.useLiveTransforms.getState().transforms.get(id)?.position?.[1]).toBeCloseTo(0.75)
276
+ expect(
277
+ (core.useScene.getState().nodes[id] as unknown as { position: number[] }).position[1],
278
+ ).toBe(0)
279
+ supportY = 1.25
280
+ await key('Alt', false)
281
+ await act(async () => {
282
+ core.emitter.emit('grid:click', {
283
+ nativeEvent: {
284
+ target: { closest: () => null },
285
+ } as unknown as import('@pascal-app/core').GridEvent['nativeEvent'],
286
+ position: [7, 0, 3],
287
+ localPosition: [7, 0, 3],
288
+ stopPropagation() {},
289
+ } as import('@pascal-app/core').GridEvent)
290
+ flushFrames()
291
+ })
292
+ expect(
293
+ (core.useScene.getState().nodes[id] as unknown as { position: number[] }).position[1],
294
+ ).toBeCloseTo(1.25)
254
295
  } finally {
255
296
  if (root)
256
297
  await act(async () => {