@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
@@ -38,7 +38,6 @@ test('roof trim drag keeps its plane hit over a surface joining and leaving a ba
38
38
  id: 'rseg_batch_drag',
39
39
  width: 10,
40
40
  depth: 10,
41
- metadata: { showTrimPlanes: true },
42
41
  })
43
42
  const source = new THREE.Group()
44
43
  const surface = new THREE.Mesh(new THREE.BoxGeometry(30, 0.25, 30), new THREE.MeshBasicMaterial())
@@ -52,6 +51,9 @@ test('roof trim drag keeps its plane hit over a surface joining and leaving a ba
52
51
  hoveredId: null,
53
52
  selection: { buildingId: null, levelId: null, zoneId: null, selectedIds: [segment.id] },
54
53
  })
54
+ useInteractionScope
55
+ .getState()
56
+ .begin({ kind: 'reshaping', reshape: 'trim', nodeId: segment.id, driver: 'tool' })
55
57
  await root.configure({
56
58
  gl: {
57
59
  domElement: canvas,
@@ -99,6 +101,18 @@ test('roof trim drag keeps its plane hit over a surface joining and leaving a ba
99
101
  events.dispatchEvent(new Event('pointercancel'))
100
102
  })
101
103
  expect(useLiveNodeOverrides.getState().overrides.has(segment.id)).toBe(false)
104
+ const fieldEscape = Object.assign(new Event('keydown'), { key: 'Escape' })
105
+ Object.defineProperty(fieldEscape, 'target', {
106
+ value: { closest: () => ({ tagName: 'INPUT' }) },
107
+ })
108
+ await act(async () => {
109
+ events.dispatchEvent(fieldEscape)
110
+ })
111
+ expect(useInteractionScope.getState().scope.kind).toBe('reshaping')
112
+ await act(async () => {
113
+ events.dispatchEvent(Object.assign(new Event('keydown'), { key: 'Escape' }))
114
+ })
115
+ expect(useInteractionScope.getState().scope.kind).toBe('idle')
102
116
  } finally {
103
117
  await act(async () => {
104
118
  root.render(null)
@@ -22,6 +22,7 @@ import {
22
22
  csgEvaluator,
23
23
  generateRoofSegmentGeometry,
24
24
  INTERSECTION,
25
+ isTypingTarget,
25
26
  markPureRaycast,
26
27
  prepareBrushForCSG,
27
28
  useViewer,
@@ -1006,15 +1007,6 @@ function getTrimCursor(side: RoofTrimSide): string {
1006
1007
  }
1007
1008
  }
1008
1009
 
1009
- function shouldShowTrimPlanes(metadata: unknown): boolean {
1010
- return (
1011
- typeof metadata === 'object' &&
1012
- metadata !== null &&
1013
- !Array.isArray(metadata) &&
1014
- (metadata as Record<string, unknown>).showTrimPlanes === true
1015
- )
1016
- }
1017
-
1018
1010
  function commitSegmentTrim(segment: RoofSegmentNode, trim: RoofSegmentTrim) {
1019
1011
  const scene = useScene.getState()
1020
1012
  scene.applyNodeChanges({
@@ -1031,6 +1023,28 @@ function RoofTrimHandles() {
1031
1023
  return node?.type === 'roof-segment' ? (node as RoofSegmentNode) : null
1032
1024
  })
1033
1025
  const readOnly = useScene((s) => s.readOnly)
1026
+ const showTrimPlanes = useInteractionScope(
1027
+ (s) =>
1028
+ s.scope.kind === 'reshaping' && s.scope.reshape === 'trim' && s.scope.nodeId === selectedId,
1029
+ )
1030
+ useEffect(() => {
1031
+ const end = () =>
1032
+ useInteractionScope
1033
+ .getState()
1034
+ .endIf(
1035
+ (scope) =>
1036
+ scope.kind === 'reshaping' && scope.reshape === 'trim' && scope.nodeId === selectedId,
1037
+ )
1038
+ const onKey = (event: KeyboardEvent) => {
1039
+ if (isTypingTarget(event.target)) return
1040
+ if (event.key === 'Escape') end()
1041
+ }
1042
+ window.addEventListener('keydown', onKey)
1043
+ return () => {
1044
+ window.removeEventListener('keydown', onKey)
1045
+ end()
1046
+ }
1047
+ }, [selectedId])
1034
1048
  const liveOverrideKey = useLiveNodeOverrides((s) =>
1035
1049
  segment ? JSON.stringify(s.overrides.get(segment.id) ?? null) : null,
1036
1050
  )
@@ -1042,7 +1056,10 @@ function RoofTrimHandles() {
1042
1056
  const zoom = camera instanceof THREE.OrthographicCamera ? 1 / camera.zoom : 1
1043
1057
  const handleBaseScale = zoom * TRIM_HANDLE_BASE_SCALE
1044
1058
 
1045
- useEffect(() => () => dragCleanupRef.current?.(), [])
1059
+ useEffect(() => {
1060
+ if (!selectedId || !showTrimPlanes || readOnly) dragCleanupRef.current?.()
1061
+ return () => dragCleanupRef.current?.()
1062
+ }, [selectedId, showTrimPlanes, readOnly])
1046
1063
 
1047
1064
  const liveSegment = useMemo(() => {
1048
1065
  if (!segment) return null
@@ -1059,8 +1076,6 @@ function RoofTrimHandles() {
1059
1076
  groupRef.current.matrixAutoUpdate = false
1060
1077
  })
1061
1078
 
1062
- const showTrimPlanes = shouldShowTrimPlanes(liveSegment?.metadata)
1063
-
1064
1079
  if (readOnly || !segment || !liveSegment || !showTrimPlanes) return null
1065
1080
 
1066
1081
  const trim = normalizeRoofSegmentTrim(liveSegment)
@@ -1314,9 +1329,7 @@ function RoofTrimHandles() {
1314
1329
 
1315
1330
  document.body.style.cursor = getTrimCursor(side)
1316
1331
  setDraggingSide(side)
1317
- useInteractionScope
1318
- .getState()
1319
- .begin({ kind: 'handle-drag', nodeId: segmentId, handle: getTrimLabel(side) })
1332
+
1320
1333
  useViewer.getState().setInputDragging(true)
1321
1334
  useScene.temporal.getState().pause()
1322
1335
 
@@ -1355,9 +1368,7 @@ function RoofTrimHandles() {
1355
1368
  document.body.style.cursor = ''
1356
1369
  }
1357
1370
  useScene.temporal.getState().resume()
1358
- useInteractionScope
1359
- .getState()
1360
- .endIf((scope) => scope.kind === 'handle-drag' && scope.nodeId === segmentId)
1371
+
1361
1372
  useViewer.getState().setInputDragging(false)
1362
1373
  setDraggingSide(null)
1363
1374
  dragCleanupRef.current = null
@@ -1393,7 +1404,7 @@ function RoofTrimHandles() {
1393
1404
  onCancel()
1394
1405
  }
1395
1406
 
1396
- dragCleanupRef.current = cleanup
1407
+ dragCleanupRef.current = onCancel
1397
1408
  window.addEventListener('pointermove', onMove)
1398
1409
  window.addEventListener('pointerup', onUp)
1399
1410
  window.addEventListener('pointercancel', onCancel)
@@ -1852,10 +1863,11 @@ export const RoofEditSystem = () => {
1852
1863
  const wasReveal = prevRevealRoofIds.current.has(roofId)
1853
1864
  if (isActive !== wasActive || isMoving !== wasMoving) {
1854
1865
  // Entering or exiting edit and move modes rebuilds the merged shell
1855
- // from the current segment values.
1866
+ // from the current segment values (its faces of other kinds are no part of it).
1856
1867
  const { markDirty } = useScene.getState()
1857
1868
  for (const childId of roofNode.children) {
1858
- markDirty(childId as AnyNodeId)
1869
+ if (nodes[childId as AnyNodeId]?.type === 'roof-segment')
1870
+ markDirty(childId as AnyNodeId)
1859
1871
  }
1860
1872
  }
1861
1873
  // Entering reveal mode (and NOT also full-edit, which already
@@ -1,9 +1,19 @@
1
1
  import { expect, test } from 'bun:test'
2
- import { emitter, type GridEvent, useScene, WallNode } from '@pascal-app/core'
3
- import { useEditor, useInteractionScope } from '@pascal-app/editor'
2
+ import {
3
+ type AnyNode,
4
+ type AnyNodeId,
5
+ emitter,
6
+ type GridEvent,
7
+ nodeRegistry,
8
+ registerNode,
9
+ useScene,
10
+ WallNode,
11
+ } from '@pascal-app/core'
12
+ import { type EditorGridEvent, useEditor, useInteractionScope } from '@pascal-app/editor'
4
13
  import { useThree } from '@react-three/fiber'
5
14
  import { act, create } from '@react-three/test-renderer'
6
- import { useDistributionRunTool } from './distribution-run-tool'
15
+ import { type RunConnection, type RunPoint, useDistributionRunTool } from './distribution-run-tool'
16
+ import { findNearestRunBody3D, type ScenePort } from './ports'
7
17
 
8
18
  test.each(['duct-segment', 'pipe-segment'] as const)(
9
19
  '%s releases its wall plane after an outward commit and retains it along the wall',
@@ -123,3 +133,361 @@ test.each(['duct-segment', 'pipe-segment'] as const)(
123
133
  }
124
134
  },
125
135
  )
136
+
137
+ test.each(['duct-segment', 'pipe-segment'] as const)(
138
+ '%s routes at an exact elevation and updates a vertical target without pointer movement',
139
+ async (toolName) => {
140
+ const previousEditor = useEditor.getState()
141
+ const previousScope = useInteractionScope.getState()
142
+ const previousWindow = Object.getOwnPropertyDescriptor(globalThis, 'window')
143
+ const previousAct = Object.getOwnPropertyDescriptor(globalThis, 'IS_REACT_ACT_ENVIRONMENT')
144
+ Object.defineProperty(globalThis, 'window', {
145
+ value: new EventTarget(),
146
+ configurable: true,
147
+ writable: true,
148
+ })
149
+ Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', {
150
+ value: true,
151
+ configurable: true,
152
+ writable: true,
153
+ })
154
+ let tool: ReturnType<typeof useDistributionRunTool> | undefined
155
+ const commits: Array<[number, number, number]> = []
156
+ let routingMode: 'horizontal' | 'vertical' = 'horizontal'
157
+ let targetElevation = 4
158
+ function Host() {
159
+ const { gl } = useThree()
160
+ gl.domElement.getBoundingClientRect = () =>
161
+ ({ left: 0, top: 0, width: 100, height: 100 }) as DOMRect
162
+ tool = useDistributionRunTool({
163
+ active: true,
164
+ levelId: 'level_test',
165
+ toolName,
166
+ routingMode,
167
+ elevation: 2,
168
+ targetElevation,
169
+ getPorts: () => [],
170
+ findBody: () => null,
171
+ commit: ({ end }) => {
172
+ commits.push(end)
173
+ return { nextStart: end, nextConnection: { port: null, body: null } }
174
+ },
175
+ })
176
+ return null
177
+ }
178
+ let renderer: Awaited<ReturnType<typeof create>> | undefined
179
+ const event = (x: number): GridEvent => ({
180
+ position: [x, 0, 0],
181
+ localPosition: [x, 0, 0],
182
+ nativeEvent: {} as GridEvent['nativeEvent'],
183
+ })
184
+ try {
185
+ useEditor.setState({ snappingModeByContext: { wall: 'off', polygon: 'off', item: 'off' } })
186
+ useEditor.getState().showView('2d')
187
+ renderer = await create(<Host />)
188
+ await act(async () => {
189
+ emitter.emit('grid:click', event(1))
190
+ })
191
+ expect(tool?.start).toEqual([1, 2, 0])
192
+ await act(async () => {
193
+ emitter.emit('grid:click', event(3))
194
+ })
195
+ expect(commits).toEqual([[3, 2, 0]])
196
+ await act(async () => {
197
+ const planEvent: EditorGridEvent = {
198
+ ...event(7),
199
+ screenProjection: { pointer: [120, 180], localToScreen: [1, 0, 0, 1, 0, 0] },
200
+ }
201
+ emitter.emit('grid:move', planEvent)
202
+ })
203
+ expect(tool?.cursorScreen).toEqual({ x: 120, y: 180, depth: 0 })
204
+ await act(async () => useEditor.getState().showView('3d'))
205
+ expect(tool?.cursorScreen).toBeNull()
206
+ routingMode = 'vertical'
207
+ await renderer.update(<Host />)
208
+ expect(tool?.cursor).toEqual([3, 4, 0])
209
+ targetElevation = 5
210
+ await renderer.update(<Host />)
211
+ expect(tool?.cursor).toEqual([3, 5, 0])
212
+ await act(async () => {
213
+ emitter.emit('grid:click', event(7))
214
+ })
215
+ expect(commits.at(-1)).toEqual([3, 5, 0])
216
+ } finally {
217
+ await renderer?.unmount()
218
+ useEditor.setState(previousEditor)
219
+ useInteractionScope.setState(previousScope)
220
+ if (previousWindow) Object.defineProperty(globalThis, 'window', previousWindow)
221
+ else Reflect.deleteProperty(globalThis, 'window')
222
+ if (previousAct) Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', previousAct)
223
+ else Reflect.deleteProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT')
224
+ }
225
+ },
226
+ )
227
+
228
+ test.each(['duct-segment', 'pipe-segment'] as const)(
229
+ '%s previews the vertical riser as soon as its start is placed',
230
+ async (toolName) => {
231
+ const previousEditor = useEditor.getState()
232
+ const previousScope = useInteractionScope.getState()
233
+ const previousWindow = Object.getOwnPropertyDescriptor(globalThis, 'window')
234
+ const previousAct = Object.getOwnPropertyDescriptor(globalThis, 'IS_REACT_ACT_ENVIRONMENT')
235
+ const keyTarget = Object.assign(new EventTarget(), { closest: () => null })
236
+ Object.defineProperty(globalThis, 'window', {
237
+ value: keyTarget,
238
+ configurable: true,
239
+ writable: true,
240
+ })
241
+ Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', {
242
+ value: true,
243
+ configurable: true,
244
+ writable: true,
245
+ })
246
+ let tool: ReturnType<typeof useDistributionRunTool> | undefined
247
+ const commits: Array<[number, number, number]> = []
248
+ function Host() {
249
+ const { gl } = useThree()
250
+ gl.domElement.getBoundingClientRect = () =>
251
+ ({ left: 0, top: 0, width: 100, height: 100 }) as DOMRect
252
+ tool = useDistributionRunTool({
253
+ active: true,
254
+ levelId: 'level_test',
255
+ toolName,
256
+ routingMode: 'vertical',
257
+ elevation: 2,
258
+ targetElevation: 4,
259
+ getPorts: () => [],
260
+ findBody: () => null,
261
+ commit: ({ end }) => {
262
+ commits.push(end)
263
+ return { nextStart: end, nextConnection: { port: null, body: null } }
264
+ },
265
+ })
266
+ return null
267
+ }
268
+ let renderer: Awaited<ReturnType<typeof create>> | undefined
269
+ const event = (x: number): GridEvent => ({
270
+ position: [x, 0, 0],
271
+ localPosition: [x, 0, 0],
272
+ nativeEvent: {} as GridEvent['nativeEvent'],
273
+ })
274
+ try {
275
+ useEditor.setState({ snappingModeByContext: { wall: 'off', polygon: 'off', item: 'off' } })
276
+ renderer = await create(<Host />)
277
+ await act(async () => {
278
+ emitter.emit('grid:click', event(1))
279
+ })
280
+ expect(tool?.start).toEqual([1, 2, 0])
281
+ expect(tool?.cursor).toEqual([1, 4, 0])
282
+
283
+ await act(async () => {
284
+ keyTarget.dispatchEvent(Object.assign(new Event('keydown'), { key: 'Enter' }))
285
+ })
286
+ expect(commits).toEqual([[1, 4, 0]])
287
+ expect(tool?.start).toEqual([1, 4, 0])
288
+ } finally {
289
+ await renderer?.unmount()
290
+ useEditor.setState(previousEditor)
291
+ useInteractionScope.setState(previousScope)
292
+ if (previousWindow) Object.defineProperty(globalThis, 'window', previousWindow)
293
+ else Reflect.deleteProperty(globalThis, 'window')
294
+ if (previousAct) Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', previousAct)
295
+ else Reflect.deleteProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT')
296
+ }
297
+ },
298
+ )
299
+
300
+ test.each(['duct-segment', 'pipe-segment'] as const)(
301
+ '%s drawn from a collar rises to each panel target without a pointer event',
302
+ async (toolName) => {
303
+ const previousEditor = useEditor.getState()
304
+ const previousScope = useInteractionScope.getState()
305
+ const previousWindow = Object.getOwnPropertyDescriptor(globalThis, 'window')
306
+ const previousAct = Object.getOwnPropertyDescriptor(globalThis, 'IS_REACT_ACT_ENVIRONMENT')
307
+ const keyTarget = Object.assign(new EventTarget(), { closest: () => null })
308
+ Object.defineProperty(globalThis, 'window', {
309
+ value: keyTarget,
310
+ configurable: true,
311
+ writable: true,
312
+ })
313
+ Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', {
314
+ value: true,
315
+ configurable: true,
316
+ writable: true,
317
+ })
318
+ let tool: ReturnType<typeof useDistributionRunTool> | undefined
319
+ const commits: Array<[number, number, number]> = []
320
+ let targetElevation = 4
321
+ function Host() {
322
+ const { gl } = useThree()
323
+ gl.domElement.getBoundingClientRect = () =>
324
+ ({ left: 0, top: 0, width: 100, height: 100 }) as DOMRect
325
+ tool = useDistributionRunTool({
326
+ active: true,
327
+ levelId: 'level_test',
328
+ toolName,
329
+ routingMode: 'vertical',
330
+ elevation: 2,
331
+ targetElevation,
332
+ initialStart: [1, 2, 0],
333
+ getPorts: () => [],
334
+ findBody: () => null,
335
+ commit: ({ end }) => {
336
+ commits.push(end)
337
+ return { nextStart: end, nextConnection: { port: null, body: null } }
338
+ },
339
+ })
340
+ return null
341
+ }
342
+ const pressEnter = () =>
343
+ act(async () => {
344
+ keyTarget.dispatchEvent(Object.assign(new Event('keydown'), { key: 'Enter' }))
345
+ })
346
+ let renderer: Awaited<ReturnType<typeof create>> | undefined
347
+ try {
348
+ useEditor.setState({ snappingModeByContext: { wall: 'off', polygon: 'off', item: 'off' } })
349
+ renderer = await create(<Host />)
350
+ expect(tool?.cursor).toEqual([1, 4, 0])
351
+ targetElevation = 5
352
+ await renderer.update(<Host />)
353
+ expect(tool?.cursor).toEqual([1, 5, 0])
354
+ await pressEnter()
355
+ expect(commits).toEqual([[1, 5, 0]])
356
+
357
+ targetElevation = 3
358
+ await renderer.update(<Host />)
359
+ expect(tool?.cursor).toEqual([1, 3, 0])
360
+ await pressEnter()
361
+ expect(commits).toEqual([
362
+ [1, 5, 0],
363
+ [1, 3, 0],
364
+ ])
365
+ } finally {
366
+ await renderer?.unmount()
367
+ useEditor.setState(previousEditor)
368
+ useInteractionScope.setState(previousScope)
369
+ if (previousWindow) Object.defineProperty(globalThis, 'window', previousWindow)
370
+ else Reflect.deleteProperty(globalThis, 'window')
371
+ if (previousAct) Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', previousAct)
372
+ else Reflect.deleteProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT')
373
+ }
374
+ },
375
+ )
376
+
377
+ test('a sloped horizontal route connects at the height its fall reaches', async () => {
378
+ const previousScene = useScene.getState()
379
+ const previousEditor = useEditor.getState()
380
+ const previousScope = useInteractionScope.getState()
381
+ const previousWindow = Object.getOwnPropertyDescriptor(globalThis, 'window')
382
+ const previousAct = Object.getOwnPropertyDescriptor(globalThis, 'IS_REACT_ACT_ENVIRONMENT')
383
+ const restoreRegistry = nodeRegistry._snapshot()
384
+ Object.defineProperty(globalThis, 'window', {
385
+ value: new EventTarget(),
386
+ configurable: true,
387
+ writable: true,
388
+ })
389
+ Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', {
390
+ value: true,
391
+ configurable: true,
392
+ writable: true,
393
+ })
394
+ const toolName = 'test:sloped-run'
395
+ registerNode({
396
+ kind: toolName,
397
+ schemaVersion: 1,
398
+ category: 'structure',
399
+ defaults: () => ({}),
400
+ snapProfile: 'structural',
401
+ } as never)
402
+ const collar = (nodeId: string, position: [number, number, number]): ScenePort => ({
403
+ nodeId: nodeId as AnyNodeId,
404
+ id: 'outlet',
405
+ position,
406
+ direction: [-1, 0, 0],
407
+ diameter: 2,
408
+ })
409
+ const run = (id: string, x: number, y: number) =>
410
+ ({
411
+ object: 'node',
412
+ id,
413
+ type: 'pipe-segment',
414
+ parentId: 'level_test',
415
+ path: [
416
+ [x, y, -2],
417
+ [x, y, 2],
418
+ ],
419
+ }) as unknown as AnyNode
420
+ // A 2% fall from [0, 2, 0] reaches 1.9 m at x = 5 and 1.84 m at x = 8.
421
+ const ports = [collar('item_start_height', [5, 2, 0.05]), collar('item_graded', [5, 1.9, 0])]
422
+ let tool: ReturnType<typeof useDistributionRunTool> | undefined
423
+ const commits: Array<{ end: RunPoint; endConnection: RunConnection }> = []
424
+ function Host() {
425
+ const { gl } = useThree()
426
+ gl.domElement.getBoundingClientRect = () =>
427
+ ({ left: 0, top: 0, width: 100, height: 100 }) as DOMRect
428
+ tool = useDistributionRunTool({
429
+ active: true,
430
+ levelId: 'level_test',
431
+ toolName,
432
+ routingMode: 'horizontal',
433
+ elevation: 2,
434
+ getPorts: () => ports,
435
+ findBody: (point) => findNearestRunBody3D(point, 0.3, { kinds: ['pipe-segment'] }),
436
+ resolveFreeEnd: (start, end) => [
437
+ end[0],
438
+ start[1] - Math.hypot(end[0] - start[0], end[2] - start[2]) * 0.02,
439
+ end[2],
440
+ ],
441
+ commit: ({ end, endConnection }) => {
442
+ commits.push({ end, endConnection })
443
+ return { nextStart: end, nextConnection: endConnection }
444
+ },
445
+ })
446
+ return null
447
+ }
448
+ let renderer: Awaited<ReturnType<typeof create>> | undefined
449
+ const planEvent = (x: number, z: number): EditorGridEvent => ({
450
+ position: [x, 0, z],
451
+ localPosition: [x, 0, z],
452
+ screenProjection: { pointer: [x * 100, z * 100], localToScreen: [100, 0, 0, 100, 0, 0] },
453
+ nativeEvent: {} as GridEvent['nativeEvent'],
454
+ })
455
+ try {
456
+ useScene.setState({
457
+ nodes: Object.fromEntries(
458
+ [run('pipe_start_height', 8.05, 1.9), run('pipe_graded', 8, 1.84)].map((node) => [
459
+ node.id,
460
+ node,
461
+ ]),
462
+ ),
463
+ })
464
+ useEditor.setState({
465
+ snappingModeByContext: { ...previousEditor.snappingModeByContext, wall: 'lines' },
466
+ })
467
+ renderer = await create(<Host />)
468
+ await act(async () => {
469
+ emitter.emit('grid:click', planEvent(0, 0))
470
+ })
471
+ expect(tool?.start).toEqual([0, 2, 0])
472
+ await act(async () => {
473
+ emitter.emit('grid:click', planEvent(5, 0))
474
+ })
475
+ expect(commits[0]?.end).toEqual([5, 1.9, 0])
476
+ expect(commits[0]?.endConnection.port?.nodeId).toBe('item_graded' as AnyNodeId)
477
+ await act(async () => {
478
+ emitter.emit('grid:click', planEvent(8, 0.04))
479
+ })
480
+ expect(commits[1]?.endConnection.body?.nodeId).toBe('pipe_graded' as AnyNodeId)
481
+ expect(commits[1]?.end[1]).toBeCloseTo(1.84, 6)
482
+ } finally {
483
+ await renderer?.unmount()
484
+ restoreRegistry()
485
+ useScene.setState(previousScene, true)
486
+ useEditor.setState(previousEditor, true)
487
+ useInteractionScope.setState(previousScope, true)
488
+ if (previousWindow) Object.defineProperty(globalThis, 'window', previousWindow)
489
+ else Reflect.deleteProperty(globalThis, 'window')
490
+ if (previousAct) Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', previousAct)
491
+ else Reflect.deleteProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT')
492
+ }
493
+ })