@pascal-app/editor 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (624) hide show
  1. package/README.md +106 -0
  2. package/package.json +42 -38
  3. package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
  4. package/src/components/editor/action-menu-button.tsx +83 -0
  5. package/src/components/editor/bake-thumbnail.tsx +307 -5
  6. package/src/components/editor/capture-level-picker.tsx +72 -0
  7. package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
  8. package/src/components/editor/construction-reveal-audio.tsx +40 -0
  9. package/src/components/editor/custom-camera-controls.tsx +275 -57
  10. package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
  11. package/src/components/editor/duplicate-node.ts +244 -0
  12. package/src/components/editor/editor-layout-mobile.tsx +37 -26
  13. package/src/components/editor/editor-layout-v2.tsx +132 -59
  14. package/src/components/editor/export-manager.tsx +47 -26
  15. package/src/components/editor/first-person/build-collider-world.test.ts +320 -0
  16. package/src/components/editor/first-person/build-collider-world.ts +28 -7
  17. package/src/components/editor/first-person/ground-holes.test.ts +128 -0
  18. package/src/components/editor/first-person/ground-holes.ts +130 -0
  19. package/src/components/editor/first-person/mechanism-targets.test.ts +162 -0
  20. package/src/components/editor/first-person/mechanism-targets.ts +88 -0
  21. package/src/components/editor/first-person-controls.tsx +220 -9
  22. package/src/components/editor/floating-action-menu.tsx +63 -207
  23. package/src/components/editor/floor-aware-camera-controls.test.ts +466 -0
  24. package/src/components/editor/floor-aware-camera-controls.ts +324 -0
  25. package/src/components/editor/floor-openings-overlay.tsx +243 -0
  26. package/src/components/editor/floor-region-controls.tsx +846 -0
  27. package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
  28. package/src/components/editor/floor-surface.test.ts +165 -0
  29. package/src/components/editor/floor-surface.ts +182 -0
  30. package/src/components/editor/floorplan-background-selection.test.ts +11 -2
  31. package/src/components/editor/floorplan-background-selection.ts +1 -3
  32. package/src/components/editor/floorplan-mode-coordinator.tsx +2 -2
  33. package/src/components/editor/floorplan-panel.tsx +414 -363
  34. package/src/components/editor/follow-pascal.test.tsx +912 -0
  35. package/src/components/editor/follow-pascal.tsx +354 -0
  36. package/src/components/editor/footprint-height-handle.tsx +204 -0
  37. package/src/components/editor/group-actions.ts +56 -2
  38. package/src/components/editor/group-move-3d.ts +2 -0
  39. package/src/components/editor/group-rotate-handle.tsx +2 -0
  40. package/src/components/editor/group-transform-shared.test.ts +460 -0
  41. package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
  42. package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
  43. package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
  44. package/src/components/editor/handles/use-handle-drag.ts +269 -197
  45. package/src/components/editor/index.tsx +409 -197
  46. package/src/components/editor/join-walls-pill.tsx +44 -0
  47. package/src/components/editor/node-action-menu.tsx +68 -70
  48. package/src/components/editor/node-arrow-handles.tsx +25 -6
  49. package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
  50. package/src/components/editor/open-wall-ends-hint.tsx +89 -0
  51. package/src/components/editor/quick-measurement-hud.tsx +3 -2
  52. package/src/components/editor/registry-action-contributions.test.tsx +63 -0
  53. package/src/components/editor/registry-action-contributions.tsx +80 -1
  54. package/src/components/editor/riser-diagram-panel.tsx +14 -7
  55. package/src/components/editor/room-controls.tsx +847 -0
  56. package/src/components/editor/room-floor-highlight.tsx +128 -0
  57. package/src/components/editor/room-handles-2d.tsx +354 -0
  58. package/src/components/editor/room-handles.tsx +675 -0
  59. package/src/components/editor/room-highlight.tsx +233 -0
  60. package/src/components/editor/room-labels.test.ts +850 -0
  61. package/src/components/editor/room-labels.tsx +1890 -0
  62. package/src/components/editor/room-transform-ghost.tsx +256 -0
  63. package/src/components/editor/room-webgpu-materials.test.ts +27 -0
  64. package/src/components/editor/selection-manager.tsx +474 -419
  65. package/src/components/editor/slab-hole-highlights.tsx +5 -2
  66. package/src/components/editor/snapshot-capture-overlay.tsx +7 -2
  67. package/src/components/editor/snapshot-capture.test.ts +119 -60
  68. package/src/components/editor/snapshot-capture.ts +36 -5
  69. package/src/components/editor/thumbnail-generator.test.ts +654 -0
  70. package/src/components/editor/thumbnail-generator.tsx +1103 -223
  71. package/src/components/editor/use-floorplan-background-placement.ts +34 -12
  72. package/src/components/editor/vector-edge-extractor.tsx +121 -0
  73. package/src/components/editor/views/host-view.tsx +61 -0
  74. package/src/components/editor/views/plugins-view.tsx +663 -0
  75. package/src/components/editor/views/scene-region.tsx +59 -0
  76. package/src/components/editor/views/use-editor-views.ts +57 -0
  77. package/src/components/editor/views/view-bar.test.tsx +54 -0
  78. package/src/components/editor/views/view-bar.tsx +343 -0
  79. package/src/components/editor/wall-justification.test.ts +140 -0
  80. package/src/components/editor/wall-move-side-handles.tsx +43 -142
  81. package/src/components/editor/wall-push-arrow.tsx +92 -0
  82. package/src/components/editor/wall-region-handles.tsx +509 -0
  83. package/src/components/editor/wall-snap-beacon-layer.tsx +8 -4
  84. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
  85. package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
  86. package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
  87. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
  88. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +2 -2
  89. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
  90. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +116 -106
  91. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
  92. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
  93. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
  94. package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
  95. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
  96. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
  97. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +319 -233
  98. package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
  99. package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
  100. package/src/components/editor-2d/svg-paths.ts +12 -15
  101. package/src/components/point-ask/bubble.tsx +538 -0
  102. package/src/components/point-ask/hover-chip.tsx +91 -0
  103. package/src/components/point-ask/mode-pill.tsx +92 -0
  104. package/src/components/point-ask/optional-motion.ts +22 -0
  105. package/src/components/point-ask/outline-corners.tsx +93 -0
  106. package/src/components/point-ask/pins.tsx +711 -0
  107. package/src/components/point-ask/point-ask-layer.tsx +83 -0
  108. package/src/components/point-ask/point-ask-styles.ts +105 -0
  109. package/src/components/point-ask/region-marquee-styles.ts +15 -0
  110. package/src/components/point-ask/region-marquee.tsx +64 -0
  111. package/src/components/procedural-items/inspector.tsx +40 -15
  112. package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
  113. package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
  114. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
  115. package/src/components/systems/roof/roof-edit-system.tsx +1 -0
  116. package/src/components/systems/zone/zone-system.test.ts +47 -0
  117. package/src/components/systems/zone/zone-system.tsx +29 -68
  118. package/src/components/tools/distribution/connection-compatibility.test.ts +34 -0
  119. package/src/components/tools/distribution/connection-compatibility.ts +43 -0
  120. package/src/components/tools/distribution/distribution-public.ts +9 -0
  121. package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
  122. package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
  123. package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
  124. package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
  125. package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
  126. package/src/components/tools/distribution/draw-alignment.ts +35 -0
  127. package/src/components/tools/distribution/ports.test.ts +56 -0
  128. package/src/components/tools/distribution/ports.ts +378 -0
  129. package/src/components/tools/distribution/run-cursor.test.ts +135 -0
  130. package/src/components/tools/distribution/run-cursor.ts +36 -0
  131. package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
  132. package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
  133. package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
  134. package/src/components/tools/distribution/run-port-snap.ts +25 -0
  135. package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
  136. package/src/components/tools/distribution/run-screen-direction.ts +30 -0
  137. package/src/components/tools/elevator/elevator-tool.tsx +3 -6
  138. package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
  139. package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
  140. package/src/components/tools/item/placement-history.test.tsx +512 -0
  141. package/src/components/tools/item/placement-strategies.test.ts +46 -1
  142. package/src/components/tools/item/placement-strategies.ts +11 -10
  143. package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
  144. package/src/components/tools/item/use-draft-node.test.tsx +432 -1
  145. package/src/components/tools/item/use-draft-node.ts +166 -95
  146. package/src/components/tools/item/use-placement-coordinator.tsx +148 -95
  147. package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
  148. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
  149. package/src/components/tools/registry/item-surface-move.test.ts +62 -63
  150. package/src/components/tools/registry/item-surface-move.ts +2 -0
  151. package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
  152. package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
  153. package/src/components/tools/registry/move-registry-node-tool.tsx +66 -30
  154. package/src/components/tools/select/box-select-tool.tsx +2 -12
  155. package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
  156. package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
  157. package/src/components/tools/select/select-candidates.test.ts +37 -4
  158. package/src/components/tools/select/select-candidates.ts +15 -11
  159. package/src/components/tools/shared/cursor-sphere.tsx +36 -12
  160. package/src/components/tools/site/site-boundary-editor.tsx +1 -2
  161. package/src/components/tools/tool-manager.tsx +59 -24
  162. package/src/components/tools/wall/wall-drafting.test.ts +175 -47
  163. package/src/components/tools/zone/zone-tool.tsx +4 -12
  164. package/src/components/ui/action-menu/action-button.tsx +9 -1
  165. package/src/components/ui/action-menu/camera-actions.tsx +9 -4
  166. package/src/components/ui/action-menu/control-modes.tsx +15 -42
  167. package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
  168. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
  169. package/src/components/ui/action-menu/index.tsx +56 -33
  170. package/src/components/ui/action-menu/measurement-control.tsx +14 -19
  171. package/src/components/ui/action-menu/placement.ts +20 -0
  172. package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
  173. package/src/components/ui/action-menu/view-toggles.tsx +47 -42
  174. package/src/components/ui/build-panel/build-panel.tsx +242 -0
  175. package/src/components/ui/command-palette/editor-commands.tsx +7 -42
  176. package/src/components/ui/command-palette/index.tsx +50 -39
  177. package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
  178. package/src/components/ui/controls/action-button.tsx +5 -2
  179. package/src/components/ui/controls/finish-swatch.tsx +110 -0
  180. package/src/components/ui/controls/material-paint-panel.tsx +97 -26
  181. package/src/components/ui/controls/material-picker.tsx +4 -1
  182. package/src/components/ui/controls/metric-control.tsx +1 -1
  183. package/src/components/ui/controls/scene-material-list.tsx +4 -32
  184. package/src/components/ui/controls/segmented-control.tsx +21 -8
  185. package/src/components/ui/controls/select-control.tsx +65 -0
  186. package/src/components/ui/controls/slider-control.tsx +1 -1
  187. package/src/components/ui/controls/toggle-control.tsx +3 -3
  188. package/src/components/ui/controls/tool-options-panel.tsx +1 -1
  189. package/src/components/ui/floating-level-selector.tsx +159 -59
  190. package/src/components/ui/helpers/building-helper.tsx +4 -1
  191. package/src/components/ui/helpers/contextual-helper-panel.tsx +181 -72
  192. package/src/components/ui/helpers/helper-manager.test.tsx +121 -0
  193. package/src/components/ui/helpers/helper-manager.tsx +331 -14
  194. package/src/components/ui/helpers/item-helper.tsx +17 -1
  195. package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
  196. package/src/components/ui/icon-ref.tsx +45 -1
  197. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  198. package/src/components/ui/node-icon-image.tsx +16 -0
  199. package/src/components/ui/panels/collections/collections-popover.tsx +28 -22
  200. package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
  201. package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
  202. package/src/components/ui/panels/mobile-panel-sheet.tsx +4 -4
  203. package/src/components/ui/panels/mobile-selection-bar.tsx +8 -5
  204. package/src/components/ui/panels/multi-field-value.test.ts +26 -3
  205. package/src/components/ui/panels/multi-field-value.ts +6 -3
  206. package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
  207. package/src/components/ui/panels/multi-selection-panel.tsx +161 -5
  208. package/src/components/ui/panels/node-display.ts +8 -3
  209. package/src/components/ui/panels/panel-manager.tsx +103 -68
  210. package/src/components/ui/panels/panel-wrapper.tsx +130 -62
  211. package/src/components/ui/panels/parametric-field-control.tsx +3 -3
  212. package/src/components/ui/panels/parametric-inspector.tsx +7 -7
  213. package/src/components/ui/panels/reference-panel.tsx +2 -3
  214. package/src/components/ui/panels/room-construction-rows.tsx +774 -0
  215. package/src/components/ui/panels/room-name-options.test.ts +55 -0
  216. package/src/components/ui/panels/room-name-options.ts +47 -0
  217. package/src/components/ui/panels/room-openings-field.tsx +110 -0
  218. package/src/components/ui/panels/room-panel.tsx +790 -0
  219. package/src/components/ui/panels/shape-choice.tsx +58 -0
  220. package/src/components/ui/panels/wall-region-list.tsx +109 -0
  221. package/src/components/ui/primitives/badge.tsx +8 -0
  222. package/src/components/ui/primitives/color-dot.tsx +36 -24
  223. package/src/components/ui/primitives/sidebar.tsx +3 -1
  224. package/src/components/ui/right-stack.test.ts +29 -0
  225. package/src/components/ui/right-stack.tsx +164 -0
  226. package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
  227. package/src/components/ui/sidebar/icon-rail.tsx +8 -3
  228. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  229. package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
  230. package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
  231. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
  232. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
  233. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
  234. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +138 -27
  235. package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
  236. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
  237. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
  238. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
  239. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
  240. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
  241. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
  242. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
  243. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
  244. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
  245. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
  246. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
  247. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
  248. package/src/components/ui/sidebar/panels/site-panel/index.tsx +181 -223
  249. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
  250. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
  251. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
  252. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
  253. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
  254. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
  255. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
  256. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
  257. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
  258. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
  259. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +13 -37
  260. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
  261. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
  262. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
  263. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
  264. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +2 -4
  265. package/src/components/ui/sidebar/tab-bar.tsx +14 -4
  266. package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
  267. package/src/components/viewer/floorplan-compass-button.tsx +9 -1
  268. package/src/components/viewer/floorplan-preview.tsx +5 -6
  269. package/src/components/viewer/viewer-controls-bar.tsx +18 -6
  270. package/src/components/viewer/viewer-hover-label.tsx +34 -0
  271. package/src/components/viewer/viewer-rooms.tsx +191 -0
  272. package/src/components/viewer/viewer-scene-header.tsx +181 -145
  273. package/src/components/viewer/viewer-selection-manager.tsx +124 -0
  274. package/src/components/viewer/viewer-stage.tsx +2 -2
  275. package/src/components/viewer-zone-system.tsx +3 -8
  276. package/src/hooks/use-auto-save.integration.test.tsx +215 -43
  277. package/src/hooks/use-auto-save.ts +60 -9
  278. package/src/hooks/use-ceiling-events.test.ts +123 -118
  279. package/src/hooks/use-grid-events.test.ts +105 -105
  280. package/src/hooks/use-keyboard.test.ts +243 -19
  281. package/src/hooks/use-keyboard.ts +118 -83
  282. package/src/hooks/use-open-wall-ends.test.ts +99 -0
  283. package/src/hooks/use-open-wall-ends.ts +238 -0
  284. package/src/hooks/use-placement-notice.test.ts +71 -0
  285. package/src/hooks/use-placement-notice.ts +73 -0
  286. package/src/hooks/use-selected-room.ts +143 -0
  287. package/src/index.tsx +312 -58
  288. package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
  289. package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
  290. package/src/lib/add-storey.test.ts +177 -0
  291. package/src/lib/add-storey.ts +59 -0
  292. package/src/lib/below-grade.test.tsx +171 -0
  293. package/src/lib/below-grade.ts +133 -0
  294. package/src/lib/camera-facing.test.ts +24 -0
  295. package/src/lib/camera-facing.ts +24 -0
  296. package/src/lib/ceiling-edit-session.test.ts +321 -0
  297. package/src/lib/ceiling-edit-session.ts +206 -0
  298. package/src/lib/ceiling-region-session.test.ts +179 -0
  299. package/src/lib/continuation.test.ts +19 -1
  300. package/src/lib/continuation.ts +25 -4
  301. package/src/lib/direct-manipulation.ts +2 -0
  302. package/src/lib/door-interaction.ts +14 -0
  303. package/src/lib/download-project-file.ts +17 -0
  304. package/src/lib/editor-level-display.test.ts +96 -0
  305. package/src/lib/editor-level-display.ts +55 -0
  306. package/src/lib/editor-views.ts +178 -0
  307. package/src/lib/export-geometry-wait.test.ts +90 -0
  308. package/src/lib/export-texture-utils.ts +2 -4
  309. package/src/lib/floor-edit-session.ts +153 -0
  310. package/src/lib/floor-footprints.test.ts +563 -0
  311. package/src/lib/floor-footprints.ts +395 -0
  312. package/src/lib/floor-foundation-support.test.ts +284 -0
  313. package/src/lib/floor-opening-draft.ts +218 -0
  314. package/src/lib/floor-openings.test.ts +216 -0
  315. package/src/lib/floor-region-geometry.ts +191 -0
  316. package/src/lib/floor-region-session.test.ts +485 -0
  317. package/src/lib/floor-region-session.ts +589 -0
  318. package/src/lib/floor-region-snap.ts +101 -0
  319. package/src/lib/floor-step-hover.test.ts +32 -0
  320. package/src/lib/floorplan/annotation-visibility.ts +17 -0
  321. package/src/lib/floorplan/dimension-drive.test.ts +360 -0
  322. package/src/lib/floorplan/dimension-drive.ts +479 -0
  323. package/src/lib/floorplan/floorplan-export.test.ts +12 -8
  324. package/src/lib/floorplan/floorplan-export.tsx +32 -8
  325. package/src/lib/floorplan/floorplan-extension.ts +32 -0
  326. package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
  327. package/src/lib/floorplan/floorplan-mode.ts +2 -0
  328. package/src/lib/floorplan/floorplan-pdfkit-document.ts +363 -27
  329. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +177 -8
  330. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +60 -25
  331. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
  332. package/src/lib/floorplan/floorplan-readonly.ts +6 -0
  333. package/src/lib/floorplan/geometry.ts +13 -6
  334. package/src/lib/floorplan/index.ts +0 -9
  335. package/src/lib/floorplan/items.ts +5 -2
  336. package/src/lib/floorplan/marks.test.ts +213 -0
  337. package/src/lib/floorplan/marks.ts +238 -0
  338. package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
  339. package/src/lib/floorplan/open-wall-ends.ts +162 -0
  340. package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
  341. package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
  342. package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
  343. package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
  344. package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
  345. package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
  346. package/src/lib/floorplan/pdfkit-standard-fonts.d.ts +5 -0
  347. package/src/lib/floorplan/schedules.test.ts +189 -0
  348. package/src/lib/floorplan/schedules.ts +339 -0
  349. package/src/lib/floorplan/sheet-export.test.ts +124 -0
  350. package/src/lib/floorplan/sheet-export.ts +318 -0
  351. package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +768 -0
  352. package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
  353. package/src/lib/floorplan/site-plan/contributors.ts +97 -0
  354. package/src/lib/floorplan/site-plan/coverage.ts +230 -0
  355. package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
  356. package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
  357. package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
  358. package/src/lib/floorplan/site-plan/front-edge.ts +157 -0
  359. package/src/lib/floorplan/site-plan/geometry.test.ts +269 -0
  360. package/src/lib/floorplan/site-plan/geometry.ts +142 -0
  361. package/src/lib/floorplan/site-plan/index.ts +55 -0
  362. package/src/lib/floorplan/site-plan/site-annotations.test.ts +483 -0
  363. package/src/lib/floorplan/site-plan/site-annotations.ts +1084 -0
  364. package/src/lib/floorplan/site-plan/site-parts.ts +287 -0
  365. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
  366. package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
  367. package/src/lib/floorplan/site-plan/street-name.ts +302 -0
  368. package/src/lib/floorplan/site-plan/yards.test.ts +70 -0
  369. package/src/lib/floorplan/types.ts +1 -27
  370. package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
  371. package/src/lib/follow-pascal-exterior.test.ts +171 -0
  372. package/src/lib/follow-pascal-exterior.ts +39 -0
  373. package/src/lib/follow-pascal-presentation.test.ts +203 -0
  374. package/src/lib/follow-pascal-presentation.ts +129 -0
  375. package/src/lib/follow-pascal-sight.ts +84 -0
  376. package/src/lib/follow-pascal-targets.test.ts +406 -0
  377. package/src/lib/follow-pascal-targets.ts +198 -0
  378. package/src/lib/follow-pascal.test.ts +350 -0
  379. package/src/lib/follow-pascal.ts +284 -0
  380. package/src/lib/fresh-planar-placement.test.ts +148 -0
  381. package/src/lib/fresh-planar-placement.ts +114 -37
  382. package/src/lib/geometry-script/author.ts +157 -0
  383. package/src/lib/geometry-script/client.ts +159 -0
  384. package/src/lib/geometry-script/finish-scripts.ts +127 -0
  385. package/src/lib/geometry-script/protocol.ts +13 -0
  386. package/src/lib/gesture-lifecycle.test.ts +595 -0
  387. package/src/lib/gesture-lifecycle.ts +290 -0
  388. package/src/lib/glb-export-zone-holes.test.ts +41 -0
  389. package/src/lib/glb-export.test.ts +636 -126
  390. package/src/lib/glb-export.ts +241 -120
  391. package/src/lib/history.test.ts +18 -7
  392. package/src/lib/history.ts +7 -0
  393. package/src/lib/hud-title.ts +109 -0
  394. package/src/lib/hydration-gated-storage.ts +49 -0
  395. package/src/lib/ifc-export.test.ts +174 -0
  396. package/src/lib/ifc-export.ts +201 -0
  397. package/src/lib/ifc-parts.ts +15 -0
  398. package/src/lib/inspector-expanded.test.ts +44 -0
  399. package/src/lib/inspector-expanded.ts +36 -0
  400. package/src/lib/interaction/scope.ts +22 -0
  401. package/src/lib/keyboard-pan.test.ts +129 -7
  402. package/src/lib/keyboard-pan.ts +161 -12
  403. package/src/lib/level-selection.test.ts +57 -0
  404. package/src/lib/level-selection.ts +16 -1
  405. package/src/lib/live-gesture-round-trip.test.ts +360 -0
  406. package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
  407. package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
  408. package/src/lib/lot/clean-ring.test.ts +187 -0
  409. package/src/lib/lot/clean-ring.ts +217 -0
  410. package/src/lib/lot/dossier.test.ts +202 -0
  411. package/src/lib/lot/dossier.ts +498 -0
  412. package/src/lib/lot/drop-in.test.ts +326 -0
  413. package/src/lib/lot/drop-in.ts +392 -0
  414. package/src/lib/lot/index.ts +43 -0
  415. package/src/lib/lot/lot-patch.test.ts +340 -0
  416. package/src/lib/lot/lot-patch.ts +394 -0
  417. package/src/lib/lot/parcel-provider.ts +43 -0
  418. package/src/lib/lot/terrain.test.ts +161 -0
  419. package/src/lib/lot/terrain.ts +266 -0
  420. package/src/lib/material-catalog-model.ts +3 -1
  421. package/src/lib/material-paint-panel-model.ts +9 -5
  422. package/src/lib/material-paint.ts +4 -0
  423. package/src/lib/measurement-kind.test.ts +6 -8
  424. package/src/lib/measurements.test.ts +23 -0
  425. package/src/lib/mezzanine-actions.test.ts +342 -0
  426. package/src/lib/mezzanine-draft.test.ts +501 -0
  427. package/src/lib/mezzanine-draft.ts +278 -0
  428. package/src/lib/mezzanine-messages.ts +26 -0
  429. package/src/lib/mezzanine-selection.test.ts +207 -0
  430. package/src/lib/mezzanine-stairs.ts +46 -0
  431. package/src/lib/model-export.ts +3 -1
  432. package/src/lib/node-action-movement.ts +17 -6
  433. package/src/lib/node-display-label.ts +37 -0
  434. package/src/lib/orbit-floor-clearance.test.ts +201 -0
  435. package/src/lib/orbit-floor-clearance.ts +154 -0
  436. package/src/lib/paint-commit-route.test.ts +37 -0
  437. package/src/lib/paint-commit-route.ts +17 -0
  438. package/src/lib/paint-eyedropper.test.tsx +165 -0
  439. package/src/lib/paint-eyedropper.ts +99 -0
  440. package/src/lib/paint-pass-through.test.ts +122 -0
  441. package/src/lib/paint-pass-through.ts +78 -0
  442. package/src/lib/paint-region-hover.ts +25 -0
  443. package/src/lib/paint-region-mode.ts +154 -0
  444. package/src/lib/paint-regions.test.ts +166 -0
  445. package/src/lib/paint-regions.ts +192 -0
  446. package/src/lib/paint-scope.test.ts +317 -35
  447. package/src/lib/paint-scope.ts +267 -25
  448. package/src/lib/panel-rows.ts +21 -0
  449. package/src/lib/parametric-node-update.ts +16 -7
  450. package/src/lib/plate-paint-affected.test.ts +300 -0
  451. package/src/lib/plate-paint-affected.ts +177 -0
  452. package/src/lib/plugin-chat.ts +65 -0
  453. package/src/lib/plugin-panels.test.ts +25 -0
  454. package/src/lib/plugin-panels.ts +94 -0
  455. package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
  456. package/src/lib/point-ask/__fixtures__/house.ts +81 -0
  457. package/src/lib/point-ask/ask-created.test.ts +195 -0
  458. package/src/lib/point-ask/ask-flow.test.ts +380 -0
  459. package/src/lib/point-ask/build-context.test.ts +173 -0
  460. package/src/lib/point-ask/build-context.ts +94 -0
  461. package/src/lib/point-ask/capture-crop.ts +129 -0
  462. package/src/lib/point-ask/choreography.ts +69 -0
  463. package/src/lib/point-ask/compare.test.ts +145 -0
  464. package/src/lib/point-ask/compare.ts +73 -0
  465. package/src/lib/point-ask/describe-target.test.ts +165 -0
  466. package/src/lib/point-ask/describe-target.ts +234 -0
  467. package/src/lib/point-ask/dictation.ts +79 -0
  468. package/src/lib/point-ask/edge-marker.test.ts +61 -0
  469. package/src/lib/point-ask/edge-marker.ts +27 -0
  470. package/src/lib/point-ask/frame-loop.ts +25 -0
  471. package/src/lib/point-ask/history-marks.test.ts +94 -0
  472. package/src/lib/point-ask/history-marks.ts +39 -0
  473. package/src/lib/point-ask/host-api.ts +32 -0
  474. package/src/lib/point-ask/outline-box.test.ts +30 -0
  475. package/src/lib/point-ask/outline-box.ts +11 -0
  476. package/src/lib/point-ask/overlay-metrics.ts +6 -0
  477. package/src/lib/point-ask/pick.test.ts +220 -0
  478. package/src/lib/point-ask/pick.ts +121 -0
  479. package/src/lib/point-ask/pin-machine.test.ts +494 -0
  480. package/src/lib/point-ask/pin-machine.ts +217 -0
  481. package/src/lib/point-ask/placement.test.ts +203 -0
  482. package/src/lib/point-ask/placement.ts +141 -0
  483. package/src/lib/point-ask/pointer-tracker.ts +34 -0
  484. package/src/lib/point-ask/projector.ts +83 -0
  485. package/src/lib/point-ask/region.test.ts +157 -0
  486. package/src/lib/point-ask/region.ts +136 -0
  487. package/src/lib/point-ask/retry.test.ts +217 -0
  488. package/src/lib/point-ask/retry.ts +37 -0
  489. package/src/lib/point-ask/scene-commit.test.ts +312 -0
  490. package/src/lib/point-ask/scene-commit.ts +32 -0
  491. package/src/lib/point-ask/session.ts +1391 -0
  492. package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
  493. package/src/lib/point-ask/spring-keyframes.ts +38 -0
  494. package/src/lib/point-ask/spring.test.ts +63 -0
  495. package/src/lib/point-ask/spring.ts +107 -0
  496. package/src/lib/point-ask/suggestions.test.ts +80 -0
  497. package/src/lib/point-ask/suggestions.ts +125 -0
  498. package/src/lib/point-ask/target-scope.test.ts +622 -0
  499. package/src/lib/point-ask/target-scope.ts +338 -0
  500. package/src/lib/point-ask/types.ts +48 -0
  501. package/src/lib/portable-export.test.ts +34 -1
  502. package/src/lib/portable-export.ts +119 -53
  503. package/src/lib/print-feature-thickness.ts +10 -1
  504. package/src/lib/print-roof-solids.test.ts +39 -1
  505. package/src/lib/print-roof-solids.ts +43 -10
  506. package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
  507. package/src/lib/print-shell-compiler.ts +1 -0
  508. package/src/lib/registered-draft-snap.ts +12 -0
  509. package/src/lib/roof-duplication.ts +10 -2
  510. package/src/lib/room-assembly-overlay.test.ts +275 -0
  511. package/src/lib/room-assembly-overlay.ts +265 -0
  512. package/src/lib/room-built-on.test.ts +379 -0
  513. package/src/lib/room-built-on.ts +138 -0
  514. package/src/lib/room-construction-commands.ts +712 -0
  515. package/src/lib/room-construction.test.ts +746 -0
  516. package/src/lib/room-construction.ts +385 -0
  517. package/src/lib/room-controls-phase6.test.ts +630 -0
  518. package/src/lib/room-divide-session.ts +523 -0
  519. package/src/lib/room-handle-drag.ts +1068 -0
  520. package/src/lib/room-name-catalog.test.ts +90 -0
  521. package/src/lib/room-name-catalog.ts +168 -0
  522. package/src/lib/room-push-dimensions.test.ts +311 -0
  523. package/src/lib/room-push-dimensions.ts +251 -0
  524. package/src/lib/room-selection-commands.ts +62 -0
  525. package/src/lib/room-selection.test.ts +1017 -0
  526. package/src/lib/room-selection.ts +417 -0
  527. package/src/lib/room-structure-commands.ts +94 -0
  528. package/src/lib/room-structure.test.ts +921 -0
  529. package/src/lib/room-transform-session.ts +570 -0
  530. package/src/lib/room-zone-routing.test.ts +294 -0
  531. package/src/lib/room-zone-routing.ts +197 -0
  532. package/src/lib/scene-clipboard.test.ts +610 -1
  533. package/src/lib/scene-clipboard.ts +269 -58
  534. package/src/lib/scene-material-refs.test.ts +79 -0
  535. package/src/lib/scene-material-refs.ts +79 -0
  536. package/src/lib/scene.test.ts +24 -36
  537. package/src/lib/scene.ts +33 -57
  538. package/src/lib/selection-routing.test.ts +129 -45
  539. package/src/lib/selection-routing.ts +54 -75
  540. package/src/lib/session-writes.test.ts +120 -0
  541. package/src/lib/session-writes.ts +205 -0
  542. package/src/lib/sfx-bus.ts +13 -0
  543. package/src/lib/sfx-player.test.ts +70 -9
  544. package/src/lib/sfx-player.ts +6 -0
  545. package/src/lib/sidebar-panel.ts +62 -0
  546. package/src/lib/site-boundary.test.ts +4 -6
  547. package/src/lib/site-boundary.ts +1 -1
  548. package/src/lib/snapping-mode.test.ts +4 -0
  549. package/src/lib/snapping-mode.ts +8 -1
  550. package/src/lib/stair-levels.test.ts +25 -0
  551. package/src/lib/stair-levels.ts +22 -3
  552. package/src/lib/terrace-draft.test.ts +145 -0
  553. package/src/lib/terrace-draft.ts +214 -0
  554. package/src/lib/tool-cursor-icon.test.ts +16 -0
  555. package/src/lib/tool-cursor-icon.ts +11 -0
  556. package/src/lib/typing-target.ts +9 -0
  557. package/src/lib/units.ts +77 -39
  558. package/src/lib/vector-edges.test.ts +58 -0
  559. package/src/lib/vector-edges.ts +400 -0
  560. package/src/lib/viewer-selection.test.ts +577 -0
  561. package/src/lib/viewer-selection.ts +580 -0
  562. package/src/lib/wall-draw-variant.test.ts +71 -0
  563. package/src/lib/wall-draw-variant.ts +91 -0
  564. package/src/lib/wall-paint-region-session.ts +246 -0
  565. package/src/lib/wall-paint-region.test.ts +373 -0
  566. package/src/lib/wall-placement-pointer.test.ts +42 -0
  567. package/src/lib/wall-placement-pointer.ts +57 -0
  568. package/src/lib/wall-polygon-draft.test.ts +262 -0
  569. package/src/lib/wall-polygon-draft.ts +286 -0
  570. package/src/lib/wall-push.test.ts +453 -0
  571. package/src/lib/wall-region-face.ts +352 -0
  572. package/src/lib/wall-region-handles.test.ts +304 -0
  573. package/src/lib/wall-region-handles.ts +368 -0
  574. package/src/lib/wall-region-list.test.ts +119 -0
  575. package/src/lib/wall-region-list.ts +136 -0
  576. package/src/lib/wall-region-snap.ts +110 -0
  577. package/src/lib/window-changes.ts +35 -0
  578. package/src/lib/window-interaction.ts +14 -0
  579. package/src/lib/zone-content.ts +4 -1
  580. package/src/lib/zone-name.test.ts +17 -0
  581. package/src/lib/zone-name.ts +17 -0
  582. package/src/store/opens-in-select-mode.test.ts +223 -0
  583. package/src/store/paint-mode.test.ts +188 -0
  584. package/src/store/terrain-sculpt-mode.test.ts +19 -21
  585. package/src/store/tool-mode.test.ts +137 -6
  586. package/src/store/use-audio.test.ts +146 -0
  587. package/src/store/use-audio.tsx +127 -3
  588. package/src/store/use-construction-reveal.test.ts +173 -0
  589. package/src/store/use-construction-reveal.ts +145 -0
  590. package/src/store/use-corner-snap-hint.test.ts +24 -0
  591. package/src/store/use-corner-snap-hint.ts +47 -0
  592. package/src/store/use-delete-confirmation.ts +28 -1
  593. package/src/store/use-drawing-view.test.ts +24 -3
  594. package/src/store/use-drawing-view.ts +29 -1
  595. package/src/store/use-editor.tsx +432 -321
  596. package/src/store/use-floorplan-draft-preview.ts +13 -0
  597. package/src/store/use-floorplan-marquee.ts +12 -2
  598. package/src/store/use-follow-pascal.test.ts +62 -0
  599. package/src/store/use-follow-pascal.ts +53 -0
  600. package/src/store/use-point-ask.ts +247 -0
  601. package/src/store/use-quick-measurement-hud.ts +3 -3
  602. package/src/store/use-stair-build-preview.ts +33 -5
  603. package/src/store/use-wall-move-ghosts.ts +4 -2
  604. package/src/store/use-wall-region-handles-selection.test.ts +222 -0
  605. package/src/store/use-wall-region-handles-selection.ts +139 -0
  606. package/src/store/view-layout.test.ts +201 -0
  607. package/src/store/view-layout.ts +199 -0
  608. package/src/test-utils/immediate-animation-frames.ts +16 -0
  609. package/src/test-utils/live-gesture-round-trip.ts +224 -0
  610. package/src/test-utils/render-current-markup.tsx +16 -0
  611. package/src/test-utils/selection-harness.ts +82 -0
  612. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
  613. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
  614. package/src/components/tools/fence/fence-drafting.test.ts +0 -100
  615. package/src/components/tools/fence/fence-drafting.ts +0 -266
  616. package/src/components/tools/stair/stair-click-guard.test.ts +0 -58
  617. package/src/components/tools/stair/stair-click-guard.ts +0 -72
  618. package/src/components/tools/stair/stair-defaults.ts +0 -18
  619. package/src/components/tools/stair/stair-tool.tsx +0 -634
  620. package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
  621. package/src/components/viewer/viewer-units-panel.tsx +0 -116
  622. package/src/lib/floorplan/stairs.ts +0 -489
  623. package/src/lib/level-duplication.test.ts +0 -144
  624. package/src/lib/level-duplication.ts +0 -195
@@ -5,6 +5,7 @@ import {
5
5
  nodeRegistry,
6
6
  pauseSpaceDetection,
7
7
  resumeSpaceDetection,
8
+ toggleNodeMechanism,
8
9
  useScene,
9
10
  } from '@pascal-app/core'
10
11
  import { cancelPerfAction, markPerfAction, useViewer } from '@pascal-app/viewer'
@@ -23,16 +24,22 @@ import {
23
24
  rotateGroupPatches,
24
25
  } from '../components/editor/group-transform-shared'
25
26
  import { steppedRotation } from '../components/tools/item/placement-math'
27
+ import { keyCyclableContinuationContext } from '../lib/continuation'
26
28
  import { resolveDirectManipulationNode } from '../lib/direct-manipulation'
27
- import { toggleDoorOpenState } from '../lib/door-interaction'
29
+ import { isSceneView } from '../lib/editor-views'
30
+ import { cancelGestures } from '../lib/gesture-lifecycle'
28
31
  import { guideEmitter } from '../lib/guide-events'
29
32
  import { isHistoryShortcut, runRedo, runUndo, shouldCancelDraftOnHistoryJump } from '../lib/history'
30
33
  import { isActive } from '../lib/interaction/scope'
34
+ import { paintRegionModeActive } from '../lib/paint-region-mode'
35
+ import { popRoomSelection } from '../lib/room-selection-commands'
31
36
  import { copySelectedNodesToEditorClipboard } from '../lib/scene-clipboard'
32
37
  import { sfxEmitter } from '../lib/sfx-bus'
38
+ import { openSidebarPanel } from '../lib/sidebar-panel'
33
39
  import { activeSiteNode, clampBrushRadius } from '../lib/terrain-sculpt'
40
+ import { isTypingTarget } from '../lib/typing-target'
34
41
  import { leaveUnitFocus } from '../lib/units'
35
- import { toggleWindowOpenState } from '../lib/window-interaction'
42
+ import { selectWallDrawVariant } from '../lib/wall-draw-variant'
36
43
  import useDeleteConfirmation from '../store/use-delete-confirmation'
37
44
  import useEditor, { getActiveContinuationContext, getActiveSnapContext } from '../store/use-editor'
38
45
  import useInteractionScope, {
@@ -40,6 +47,12 @@ import useInteractionScope, {
40
47
  isInteractionSubtreeDraft,
41
48
  } from '../store/use-interaction-scope'
42
49
  import { groupCurrentSelection, ungroupCurrentSelection } from '../store/use-session-groups'
50
+ import { activeViewLayout } from '../store/view-layout'
51
+
52
+ export function hasSceneKeyboardFocus(): boolean {
53
+ const layout = activeViewLayout(useEditor.getState())
54
+ return isSceneView(layout.panes[layout.focus])
55
+ }
43
56
 
44
57
  // References (guide/scan) are selected via `useEditor.selectedReferenceId`, not
45
58
  // the viewer selection, so selection-based key arms (R/T rotate) need this
@@ -108,9 +121,13 @@ export const markToolCancelConsumed = () => {
108
121
  // preset/item placement rely on this — they pass no coordinator onCancel, and
109
122
  // it is the mode switch unmounting them that destroys the draft.
110
123
  const exitToSelectAfterUnconsumedCancel = () => {
111
- const currentPhase = useEditor.getState().phase
112
- const currentStructureLayer = useEditor.getState().structureLayer
113
-
124
+ const editor = useEditor.getState()
125
+ if (
126
+ editor.mode === 'select' &&
127
+ useInteractionScope.getState().scope.kind === 'idle' &&
128
+ popRoomSelection()
129
+ )
130
+ return
114
131
  useInteractionScope
115
132
  .getState()
116
133
  .endIf(
@@ -119,14 +136,8 @@ const exitToSelectAfterUnconsumedCancel = () => {
119
136
  (sc.kind === 'reshaping' && sc.reshape === 'hole'),
120
137
  )
121
138
 
122
- // From zone mode, return to structure select
123
- if (currentPhase === 'structure' && currentStructureLayer === 'zones') {
124
- useEditor.getState().setStructureLayer('elements')
125
- useEditor.getState().armToolMode({ mode: 'select' })
126
- } else {
127
- // Return to the default select tool while keeping the active building/level context.
128
- useEditor.getState().armToolMode({ mode: 'select' })
129
- }
139
+ // Back to the select tool, keeping the active building/level context.
140
+ useEditor.getState().armToolMode({ mode: 'select' })
130
141
 
131
142
  useEditor.getState().setFloorplanSelectionTool('click')
132
143
 
@@ -160,6 +171,11 @@ const cancelInteractionForHistoryShortcut = () => {
160
171
  guideEmitter.emit('guide:cancel-reference-scale')
161
172
  return true
162
173
  }
174
+ // A live gesture reads ⌘Z as "abort", never as a jump under the pointer.
175
+ if (cancelGestures('history')) {
176
+ cancelPerfAction()
177
+ return true
178
+ }
163
179
  const activeScope = useInteractionScope.getState().scope
164
180
  if (shouldCancelDraftOnHistoryJump()) return false
165
181
  if (activeScope.kind === 'mesh-editing' && activeScope.phase === 'selecting') return false
@@ -191,8 +207,42 @@ export const runHistoryShortcut = (direction: 'undo' | 'redo') => {
191
207
  return true
192
208
  }
193
209
 
194
- /** Whether an armed tool owns the rotation key (`R` or `T`) instead of the selection. */
195
- export const isToolOwnedRotation = (key: 'r' | 't' = 'r') => {
210
+ /**
211
+ * B: the wall tool on Rectangle — the quickest room — with the Build panel
212
+ * showing, so the lit tile says what is in hand. The panel's tiles still pick
213
+ * another shape for the session. The panel opens before the tool is armed: a
214
+ * host may drop the armed tool when the panel changes.
215
+ */
216
+ export const armWallToolFromShortcut = () => {
217
+ selectWallDrawVariant('rectangle')
218
+ openSidebarPanel(['build'])
219
+ useEditor.getState().armToolMode({ mode: 'build', tool: 'wall' })
220
+ }
221
+
222
+ /** 1: site, 2: the building — each back on the select tool. Returns whether `key` was one. */
223
+ export const switchPhaseFromShortcut = (key: string): boolean => {
224
+ const phase = key === '1' ? 'site' : key === '2' ? 'building' : null
225
+ if (!phase) return false
226
+ useEditor.getState().setPhase(phase)
227
+ useEditor.getState().armToolMode({ mode: 'select' })
228
+ return true
229
+ }
230
+
231
+ /** F: the item tool with the items catalog showing, opened first as B's is. */
232
+ export const openItemCatalogFromShortcut = () => {
233
+ openSidebarPanel(['items'])
234
+ useEditor.getState().setActiveSidebarPanel('items')
235
+ useEditor.getState().armToolMode({ mode: 'build', tool: 'item' })
236
+ }
237
+
238
+ /** P: paint mode, with the Paint panel showing (the Build panel where a host has no Paint tab). */
239
+ export const armPaintFromShortcut = () => {
240
+ useEditor.getState().armMaterialPaint()
241
+ openSidebarPanel(['paint', 'build'])
242
+ }
243
+
244
+ /** Whether an armed tool owns the rotation keys (`R` / `T`) instead of the selection. */
245
+ export const isToolOwnedRotation = () => {
196
246
  const editor = useEditor.getState()
197
247
  const moving = getMovingNode()
198
248
  if (
@@ -212,10 +262,7 @@ export const isToolOwnedRotation = (key: 'r' | 't' = 'r') => {
212
262
  // exist. Without this check, selecting an existing item in the 2D plan
213
263
  // while the item tool is armed silently drops the global rotate key.
214
264
  (editor.tool === 'item' && editor.selectedItem !== null) ||
215
- editor.tool === 'lean-to-extension' ||
216
- // R toggles the wall tool between line and rectangle drawing; T stays
217
- // the selection's.
218
- (editor.tool === 'wall' && key === 'r'))
265
+ editor.tool === 'lean-to-extension')
219
266
  )
220
267
  }
221
268
 
@@ -235,7 +282,23 @@ export function blocksSnappingShortcut(
235
282
  ): boolean {
236
283
  if (!target) return false
237
284
  if (target.tagName === 'INPUT' && target.hasAttribute('data-run-length-input')) return false
238
- return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable
285
+ return (
286
+ target.tagName === 'INPUT' ||
287
+ target.tagName === 'TEXTAREA' ||
288
+ target.tagName === 'SELECT' ||
289
+ target.isContentEditable ||
290
+ isTypingTarget(target as HTMLElement)
291
+ )
292
+ }
293
+
294
+ /** E on one selected node: the kind's own E action, else its mechanism. False when neither applies. */
295
+ export function runNodeInteraction(node: AnyNode): boolean {
296
+ const action = nodeRegistry.get(node.type)?.keyboardActions?.e
297
+ if (action?.appliesTo(node)) {
298
+ action.run(node)
299
+ return true
300
+ }
301
+ return toggleNodeMechanism(node)
239
302
  }
240
303
 
241
304
  export const useKeyboard = ({
@@ -268,6 +331,11 @@ export const useKeyboard = ({
268
331
  let shiftTapClean = false
269
332
 
270
333
  const handleKeyDown = (e: KeyboardEvent) => {
334
+ if (e.defaultPrevented || !hasSceneKeyboardFocus()) {
335
+ ctrlTapClean = false
336
+ shiftTapClean = false
337
+ return
338
+ }
271
339
  if (e.key === 'Shift') {
272
340
  shiftTapClean = !e.repeat && !e.metaKey && !e.ctrlKey && !e.altKey
273
341
  } else {
@@ -296,12 +364,8 @@ export const useKeyboard = ({
296
364
  return
297
365
  }
298
366
 
299
- // Don't handle shortcuts if user is typing in an input
300
- if (
301
- e.target instanceof HTMLInputElement ||
302
- e.target instanceof HTMLTextAreaElement ||
303
- (e.target instanceof HTMLElement && e.target.isContentEditable)
304
- ) {
367
+ // Editable controls own their keyboard events.
368
+ if (isTypingTarget(e.target)) {
305
369
  return
306
370
  }
307
371
 
@@ -309,7 +373,12 @@ export const useKeyboard = ({
309
373
  return
310
374
  }
311
375
 
312
- if (e.key === 'Shift' && !e.repeat && useEditor.getState().mode === 'material-paint') {
376
+ if (
377
+ e.key === 'Shift' &&
378
+ !e.repeat &&
379
+ useEditor.getState().mode === 'material-paint' &&
380
+ !paintRegionModeActive('material-paint')
381
+ ) {
313
382
  // In paint mode Shift cycles the application scope (this surface →
314
383
  // whole item / all matching / room) — the paint-mode analogue of the
315
384
  // snapping-mode cycle below. The scope chip mirrors this key.
@@ -361,7 +430,10 @@ export const useKeyboard = ({
361
430
  const current = useEditor.getState().getContinuation('fence')
362
431
  useEditor
363
432
  .getState()
364
- .setContinuation('fence', current === 'curved' ? 'continuous' : 'curved')
433
+ .setContinuation(
434
+ 'fence',
435
+ current === 'curved' ? 'freehand' : current === 'freehand' ? 'continuous' : 'curved',
436
+ )
365
437
  sfxEmitter.emit('sfx:grid-snap')
366
438
  return
367
439
  }
@@ -375,12 +447,13 @@ export const useKeyboard = ({
375
447
  !e.shiftKey &&
376
448
  !e.altKey
377
449
  ) {
378
- const context = getActiveContinuationContext()
450
+ // The wall's drawing mode is picked in the Build panel, not cycled here.
451
+ const context = keyCyclableContinuationContext(getActiveContinuationContext())
379
452
  if (context) {
380
453
  e.preventDefault()
381
454
  if (context === 'fence') {
382
455
  const current = useEditor.getState().getContinuation('fence')
383
- if (current !== 'curved') {
456
+ if (current !== 'curved' && current !== 'freehand') {
384
457
  useEditor
385
458
  .getState()
386
459
  .setContinuation('fence', current === 'single' ? 'continuous' : 'single')
@@ -407,37 +480,21 @@ export const useKeyboard = ({
407
480
  // Only switch to select mode if no tool had an active mid-action to cancel.
408
481
  // (e.g. mid-wall draw or mid-slab polygon should only cancel the action, not exit the tool)
409
482
  cancelActiveTool()
410
- } else if (e.key === '1' && !e.metaKey && !e.ctrlKey) {
411
- e.preventDefault()
412
- useEditor.getState().setPhase('site')
413
- useEditor.getState().armToolMode({ mode: 'select' })
414
- } else if (e.key === '2' && !e.metaKey && !e.ctrlKey) {
415
- e.preventDefault()
416
- useEditor.getState().setPhase('structure')
417
- useEditor.getState().armToolMode({ mode: 'select' })
418
- } else if (e.key === '3' && !e.metaKey && !e.ctrlKey) {
483
+ } else if (!e.metaKey && !e.ctrlKey && switchPhaseFromShortcut(e.key)) {
419
484
  e.preventDefault()
420
- useEditor.getState().setPhase('furnish')
421
- useEditor.getState().armToolMode({ mode: 'select' })
422
485
  } else if (e.key === 'f' && !e.metaKey && !e.ctrlKey) {
423
486
  if (isVersionPreviewMode) return
424
487
  if (isToolOwnedCanopyForm()) return
425
488
  e.preventDefault()
426
- useEditor.getState().setPhase('furnish')
427
- useEditor.getState().armToolMode({ mode: 'build', tool: 'item' })
428
- useEditor.getState().setActiveSidebarPanel('items')
489
+ openItemCatalogFromShortcut()
429
490
  } else if (e.key === 'z' && !e.metaKey && !e.ctrlKey) {
430
491
  if (isVersionPreviewMode) return
431
492
  e.preventDefault()
432
- useEditor.getState().setPhase('structure')
433
- useEditor.getState().setStructureLayer('zones')
434
493
  useEditor.getState().armToolMode({ mode: 'build', tool: 'zone' })
435
494
  } else if (e.key === 'm' && !e.metaKey && !e.ctrlKey) {
436
495
  if (isVersionPreviewMode) return
437
496
  e.preventDefault()
438
497
  const editor = useEditor.getState()
439
- editor.setPhase('structure')
440
- editor.setStructureLayer('elements')
441
498
  editor.setToolDefaults('measurement', { kind: editor.lastMeasurementKind })
442
499
  editor.armToolMode({ mode: 'build', tool: 'measurement' })
443
500
  }
@@ -448,9 +505,7 @@ export const useKeyboard = ({
448
505
  } else if (e.key === 'b' && !e.metaKey && !e.ctrlKey) {
449
506
  if (isVersionPreviewMode) return
450
507
  e.preventDefault()
451
- useEditor.getState().setPhase('structure')
452
- useEditor.getState().setStructureLayer('elements')
453
- useEditor.getState().armToolMode({ mode: 'build', tool: 'wall' })
508
+ armWallToolFromShortcut()
454
509
  } else if (e.key === 'x' && !e.metaKey && !e.ctrlKey) {
455
510
  if (isVersionPreviewMode) return
456
511
  e.preventDefault()
@@ -458,9 +513,7 @@ export const useKeyboard = ({
458
513
  } else if (e.key === 'p' && !e.metaKey && !e.ctrlKey) {
459
514
  if (isVersionPreviewMode) return
460
515
  e.preventDefault()
461
- useEditor.getState().setPhase('structure')
462
- useEditor.getState().setStructureLayer('elements')
463
- useEditor.getState().armMaterialPaint()
516
+ armPaintFromShortcut()
464
517
  } else if (e.key === 'g' && !e.metaKey && !e.ctrlKey) {
465
518
  if (isVersionPreviewMode) return
466
519
  e.preventDefault()
@@ -632,7 +685,7 @@ export const useKeyboard = ({
632
685
  } else if (
633
686
  (e.key === 't' || e.key === 'T') &&
634
687
  !isVersionPreviewMode &&
635
- !isToolOwnedRotation('t') &&
688
+ !isToolOwnedRotation() &&
636
689
  canRunGlobalRotationShortcut()
637
690
  ) {
638
691
  // Rotate selected node counter-clockwise
@@ -692,34 +745,14 @@ export const useKeyboard = ({
692
745
  }
693
746
  }
694
747
  } else if ((e.key === 'e' || e.key === 'E') && !isVersionPreviewMode) {
695
- // Toggle door / operable-window open/closed state. Moved off R,
696
- // which now flips the opening (side + π rotation).
748
+ // E runs the kind's interaction or mechanism (doors and windows open and
749
+ // close). Moved off R, which now flips the opening (side + π rotation).
697
750
  const selectedNodeIds = useViewer.getState().selection.selectedIds as AnyNodeId[]
698
751
  if (selectedNodeIds.length === 1) {
699
752
  const node = useScene.getState().nodes[selectedNodeIds[0]!]
700
- const registryE = node && nodeRegistry.get(node.type)?.keyboardActions?.e
701
- if (node && registryE?.appliesTo(node)) {
753
+ if (node && runNodeInteraction(node)) {
702
754
  // Registry-driven E interaction. Same shape as the R/T arms.
703
755
  e.preventDefault()
704
- registryE.run(node)
705
- sfxEmitter.emit('sfx:item-rotate')
706
- } else if (node?.type === 'door' && node.openingKind !== 'opening') {
707
- e.preventDefault()
708
- toggleDoorOpenState(node.id)
709
- sfxEmitter.emit('sfx:item-rotate')
710
- } else if (
711
- node?.type === 'window' &&
712
- node.openingKind !== 'opening' &&
713
- (node.windowType === 'sliding' ||
714
- node.windowType === 'casement' ||
715
- node.windowType === 'awning' ||
716
- node.windowType === 'hopper' ||
717
- node.windowType === 'single-hung' ||
718
- node.windowType === 'double-hung' ||
719
- node.windowType === 'louvered')
720
- ) {
721
- e.preventDefault()
722
- toggleWindowOpenState(node.id)
723
756
  sfxEmitter.emit('sfx:item-rotate')
724
757
  }
725
758
  }
@@ -752,6 +785,11 @@ export const useKeyboard = ({
752
785
  }
753
786
  }
754
787
  const handleKeyUp = (e: KeyboardEvent) => {
788
+ if (e.defaultPrevented || !hasSceneKeyboardFocus()) {
789
+ ctrlTapClean = false
790
+ shiftTapClean = false
791
+ return
792
+ }
755
793
  if (e.key === 'Shift') {
756
794
  const wasClean = shiftTapClean
757
795
  shiftTapClean = false
@@ -786,13 +824,10 @@ export const useKeyboard = ({
786
824
  // registry move overlay) — safe only because none of them claim Ctrl/Cmd+G.
787
825
  // `e.code` keeps it on the physical G key across keyboard layouts.
788
826
  const handleSessionGroupKeyDown = (e: KeyboardEvent) => {
827
+ if (e.defaultPrevented || !hasSceneKeyboardFocus()) return
789
828
  if (e.key !== 'Shift') shiftTapClean = false
790
829
  if (e.key !== 'Control' && e.key !== 'Meta') ctrlTapClean = false
791
- if (
792
- e.target instanceof HTMLInputElement ||
793
- e.target instanceof HTMLTextAreaElement ||
794
- (e.target instanceof HTMLElement && e.target.isContentEditable)
795
- ) {
830
+ if (isTypingTarget(e.target)) {
796
831
  return
797
832
  }
798
833
  if (useDeleteConfirmation.getState().request) return
@@ -0,0 +1,99 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { type AnyNode, DoorNode, type OpenWallEnd, WallNode } from '@pascal-app/core'
3
+ import { analyseOpenWallEnds } from './use-open-wall-ends'
4
+
5
+ const wall = (id: string, start: [number, number], end: [number, number]) =>
6
+ ({
7
+ id,
8
+ type: 'wall',
9
+ object: 'node',
10
+ parentId: 'level_a',
11
+ visible: true,
12
+ children: [],
13
+ metadata: {},
14
+ start,
15
+ end,
16
+ thickness: 0.1,
17
+ }) as unknown as WallNode
18
+
19
+ // A 4 x 4 room whose last corner is left 4 cm open (bodies are 0.1 thick).
20
+ const walls = () => [
21
+ wall('wall_1', [0, 0], [4, 0]),
22
+ wall('wall_2', [4, 0], [4, 4]),
23
+ wall('wall_3', [4, 4], [0, 4]),
24
+ wall('wall_4', [0, 4], [0, 0.09]),
25
+ ]
26
+
27
+ describe('open wall end analysis shared by the floor plan and 3D view', () => {
28
+ test('finds the open corner', () => {
29
+ const ends = analyseOpenWallEnds('level_a', walls())
30
+ expect(ends.some((end) => end.wallId === 'wall_4' && end.end === 'end')).toBe(true)
31
+ })
32
+
33
+ test('two views reading the same walls get one result', () => {
34
+ const shared = walls()
35
+ const first = analyseOpenWallEnds('level_a', [...shared])
36
+ expect(analyseOpenWallEnds('level_a', [...shared])).toBe(first)
37
+ })
38
+
39
+ test('a changed wall is analysed again', () => {
40
+ const shared = walls()
41
+ const first = analyseOpenWallEnds('level_a', shared)
42
+ const closed = [...shared.slice(0, 3), wall('wall_4', [0, 4], [0, 0])]
43
+ const next = analyseOpenWallEnds('level_a', closed)
44
+ expect(next).not.toBe(first)
45
+ expect(next.some((end) => end.wallId === 'wall_4')).toBe(false)
46
+ })
47
+ })
48
+
49
+ describe('the open-end preview reads what the walls host', () => {
50
+ // An 88° near-miss T: the source wall stops 9 cm short of the long target wall.
51
+ const level = 'level_t'
52
+ const tWall = (id: string, start: [number, number], end: [number, number], thickness = 0.1) =>
53
+ WallNode.parse({ id, parentId: level, start, end, thickness })
54
+ const scene = () => {
55
+ const tip: [number, number] = [2 + (3 - 0.09) / Math.tan((88 * Math.PI) / 180), 0.09]
56
+ const source = tWall('wall_source', [2, 3], tip, 0.01)
57
+ return {
58
+ source,
59
+ walls: [
60
+ source,
61
+ tWall('wall_target', [-20, 0], [30, 0], 0.01),
62
+ tWall('wall_anchor', [2, 3], [-20, 3]),
63
+ tWall('wall_left', [-20, 3], [-20, 0]),
64
+ ],
65
+ }
66
+ }
67
+ const sourceEnd = (ends: OpenWallEnd[]) =>
68
+ ends.find((end) => end.wallId === 'wall_source' && end.end === 'end')
69
+
70
+ test('without an opening the preview squares the corner', () => {
71
+ const point = sourceEnd(analyseOpenWallEnds(level, scene().walls))?.candidate?.point
72
+ expect(point?.[0]).toBeCloseTo(2, 6)
73
+ expect(point?.[1]).toBeCloseTo(0, 6)
74
+ })
75
+
76
+ test('a door flush with the anchored end keeps the join straight, and only the door changing re-runs it', () => {
77
+ const { source, walls } = scene()
78
+ const door = DoorNode.parse({
79
+ id: 'door_source',
80
+ parentId: source.id,
81
+ wallId: source.id,
82
+ position: [0.4, 1.05, 0],
83
+ width: 0.8,
84
+ })
85
+ const hosted: AnyNode[] = [
86
+ { ...source, children: [door.id] } as AnyNode,
87
+ ...walls.slice(1),
88
+ door as AnyNode,
89
+ ]
90
+ const first = analyseOpenWallEnds(level, hosted)
91
+ const point = sourceEnd(first)?.candidate?.point
92
+ expect(point?.[0]).toBeCloseTo(2 + 3 / Math.tan((88 * Math.PI) / 180), 6)
93
+ expect(point?.[1]).toBeCloseTo(0, 6)
94
+
95
+ const narrower = { ...door, width: 0.6 } as AnyNode
96
+ const next = analyseOpenWallEnds(level, [...hosted.slice(0, -1), narrower])
97
+ expect(next).not.toBe(first)
98
+ })
99
+ })