@pascal-app/editor 0.9.1 → 1.0.0-beta.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 (366) hide show
  1. package/package.json +14 -9
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +10 -2
  3. package/src/components/editor/bake-exporter.tsx +49 -0
  4. package/src/components/editor/bake-thumbnail.tsx +95 -0
  5. package/src/components/editor/camera-dragging-lifecycle.test.ts +49 -0
  6. package/src/components/editor/camera-dragging-lifecycle.ts +41 -0
  7. package/src/components/editor/custom-camera-controls.tsx +332 -199
  8. package/src/components/editor/delete-confirmation-dialog.tsx +54 -0
  9. package/src/components/editor/editor-layout-mobile.tsx +5 -0
  10. package/src/components/editor/editor-layout-v2.tsx +18 -0
  11. package/src/components/editor/elevation-3d-guide-layer.tsx +120 -0
  12. package/src/components/editor/export-manager.tsx +71 -63
  13. package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
  14. package/src/components/editor/first-person/build-collider-world.test.ts +102 -5
  15. package/src/components/editor/first-person/build-collider-world.ts +52 -5
  16. package/src/components/editor/first-person/terrain-collider.test.ts +132 -0
  17. package/src/components/editor/first-person/terrain-collider.ts +212 -0
  18. package/src/components/editor/first-person-controls.tsx +410 -136
  19. package/src/components/editor/floating-action-menu.tsx +435 -64
  20. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  21. package/src/components/editor/floorplan-background-selection.test.ts +72 -0
  22. package/src/components/editor/floorplan-background-selection.ts +7 -20
  23. package/src/components/editor/floorplan-camera-sync.test.ts +249 -0
  24. package/src/components/editor/floorplan-camera-sync.ts +213 -0
  25. package/src/components/editor/floorplan-mode-coordinator.tsx +82 -0
  26. package/src/components/editor/floorplan-navigation-presentation.test.ts +142 -0
  27. package/src/components/editor/floorplan-navigation-presentation.ts +156 -0
  28. package/src/components/editor/floorplan-panel.tsx +2517 -1492
  29. package/src/components/editor/grid.tsx +166 -39
  30. package/src/components/editor/group-actions.ts +597 -0
  31. package/src/components/editor/group-floating-action-menu.tsx +130 -0
  32. package/src/components/editor/group-move-3d.ts +397 -0
  33. package/src/components/editor/group-rotate-handle.tsx +102 -64
  34. package/src/components/editor/group-selection-box-3d.tsx +173 -0
  35. package/src/components/editor/group-transform-shared.test.ts +192 -1
  36. package/src/components/editor/group-transform-shared.ts +240 -7
  37. package/src/components/editor/handles/handle-arrow.tsx +81 -23
  38. package/src/components/editor/handles/handle-drag-history.test.ts +96 -0
  39. package/src/components/editor/handles/handle-drag-history.ts +14 -0
  40. package/src/components/editor/handles/preview-overrides.test.ts +26 -0
  41. package/src/components/editor/handles/preview-overrides.ts +13 -0
  42. package/src/components/editor/handles/resize-snap.test.ts +52 -0
  43. package/src/components/editor/handles/resize-snap.ts +23 -0
  44. package/src/components/editor/handles/use-handle-drag.ts +49 -6
  45. package/src/components/editor/index.tsx +289 -35
  46. package/src/components/editor/measurement-pill.tsx +68 -23
  47. package/src/components/editor/node-action-menu.tsx +14 -1
  48. package/src/components/editor/node-arrow-handles.tsx +409 -180
  49. package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
  50. package/src/components/editor/quick-measurement-card.tsx +76 -0
  51. package/src/components/editor/quick-measurement-hud.tsx +34 -0
  52. package/src/components/editor/riser-diagram-panel.tsx +137 -0
  53. package/src/components/editor/selection-manager.tsx +814 -466
  54. package/src/components/editor/site-edge-labels.tsx +28 -6
  55. package/src/components/editor/slab-hole-highlights.tsx +13 -5
  56. package/src/components/editor/snapshot-capture-overlay.tsx +258 -115
  57. package/src/components/editor/three-context-bridge.ts +22 -0
  58. package/src/components/editor/thumbnail-generator.tsx +118 -253
  59. package/src/components/editor/use-floorplan-background-placement.ts +113 -45
  60. package/src/components/editor/use-floorplan-hit-testing.test.ts +82 -0
  61. package/src/components/editor/use-floorplan-hit-testing.ts +144 -161
  62. package/src/components/editor/use-floorplan-scene-data.ts +5 -5
  63. package/src/components/editor/use-mesh-settle-epoch.ts +27 -0
  64. package/src/components/editor/wall-measurement-label.tsx +10 -28
  65. package/src/components/editor/wall-move-side-handles.tsx +229 -39
  66. package/src/components/editor/wall-opening-highlights.tsx +63 -32
  67. package/src/components/editor/wall-snap-beacon-layer.tsx +162 -6
  68. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +9 -5
  69. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +2 -1
  70. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +40 -9
  71. package/src/components/editor-2d/floorplan-cursor-indicator-position.test.ts +31 -0
  72. package/src/components/editor-2d/floorplan-cursor-indicator-position.ts +39 -0
  73. package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
  74. package/src/components/editor-2d/floorplan-group-move.tsx +744 -0
  75. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
  76. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1672 -0
  77. package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
  78. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +72 -0
  79. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +219 -20
  80. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +327 -59
  81. package/src/components/editor-2d/floorplan-render-context.test.ts +27 -0
  82. package/src/components/editor-2d/floorplan-render-context.tsx +113 -8
  83. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +5 -3
  84. package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +285 -0
  85. package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +516 -0
  86. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +236 -0
  87. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +618 -0
  88. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +369 -0
  89. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +431 -32
  90. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +89 -0
  91. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +106 -0
  92. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +94 -26
  93. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +587 -0
  94. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +2280 -578
  95. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +20 -10
  96. package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
  97. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +425 -61
  98. package/src/components/systems/ceiling/ceiling-system.tsx +97 -4
  99. package/src/components/systems/roof/roof-edit-system.tsx +1768 -13
  100. package/src/components/systems/selection-affordance-manager.tsx +38 -0
  101. package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
  102. package/src/components/systems/zone/zone-system.tsx +42 -13
  103. package/src/components/tools/elevator/elevator-tool.tsx +34 -8
  104. package/src/components/tools/elevator/move-elevator-tool.tsx +13 -2
  105. package/src/components/tools/fence/fence-drafting.ts +107 -8
  106. package/src/components/tools/item/move-tool.tsx +17 -13
  107. package/src/components/tools/item/placement-math.test.ts +13 -1
  108. package/src/components/tools/item/placement-math.ts +28 -2
  109. package/src/components/tools/item/placement-strategies.ts +246 -5
  110. package/src/components/tools/item/placement-types.ts +13 -1
  111. package/src/components/tools/item/use-draft-node.ts +146 -57
  112. package/src/components/tools/item/use-placement-coordinator.tsx +813 -125
  113. package/src/components/tools/registry/move-registry-node-tool.tsx +636 -103
  114. package/src/components/tools/roof/roof-tool.tsx +336 -94
  115. package/src/components/tools/select/box-select-state.ts +18 -7
  116. package/src/components/tools/select/box-select-tool.tsx +176 -40
  117. package/src/components/tools/select/marquee-geometry.test.ts +161 -0
  118. package/src/components/tools/select/marquee-geometry.ts +155 -0
  119. package/src/components/tools/select/plane-box-select-tool.tsx +1 -8
  120. package/src/components/tools/select/select-candidates.ts +1 -1
  121. package/src/components/tools/shared/cursor-sphere.tsx +62 -26
  122. package/src/components/tools/shared/drag-bounding-box.tsx +28 -4
  123. package/src/components/tools/shared/facing-indicator.tsx +87 -0
  124. package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
  125. package/src/components/tools/shared/floor-stack-preview.ts +4 -0
  126. package/src/components/tools/shared/horizontal-construction-plane.ts +123 -0
  127. package/src/components/tools/shared/placement-box.tsx +183 -1
  128. package/src/components/tools/shared/pointer-support-cap.test.ts +98 -0
  129. package/src/components/tools/shared/pointer-support-cap.ts +273 -0
  130. package/src/components/tools/shared/polygon-editor.tsx +276 -35
  131. package/src/components/tools/site/site-boundary-editor.tsx +101 -42
  132. package/src/components/tools/site/stroke-pointer-ownership.test.ts +91 -0
  133. package/src/components/tools/site/stroke-pointer-ownership.ts +57 -0
  134. package/src/components/tools/site/terrain-brush-cursor.tsx +247 -0
  135. package/src/components/tools/site/terrain-sculpt-grid.tsx +192 -0
  136. package/src/components/tools/site/terrain-sculpt-tool.tsx +356 -0
  137. package/src/components/tools/stair/stair-click-guard.test.ts +58 -0
  138. package/src/components/tools/stair/stair-click-guard.ts +72 -0
  139. package/src/components/tools/stair/stair-defaults.ts +1 -0
  140. package/src/components/tools/stair/stair-tool.tsx +164 -42
  141. package/src/components/tools/tool-manager.tsx +149 -30
  142. package/src/components/tools/wall/wall-drafting.test.ts +447 -0
  143. package/src/components/tools/wall/wall-drafting.ts +304 -108
  144. package/src/components/tools/wall/wall-snap-geometry.test.ts +52 -0
  145. package/src/components/tools/wall/wall-snap-geometry.ts +137 -9
  146. package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
  147. package/src/components/tools/zone/zone-tool.tsx +98 -88
  148. package/src/components/ui/action-menu/camera-actions.tsx +24 -17
  149. package/src/components/ui/action-menu/control-modes.tsx +44 -94
  150. package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
  151. package/src/components/ui/action-menu/index.tsx +2 -4
  152. package/src/components/ui/action-menu/measurement-control.tsx +267 -0
  153. package/src/components/ui/action-menu/structure-tools.tsx +22 -13
  154. package/src/components/ui/action-menu/view-toggles.tsx +42 -90
  155. package/src/components/ui/command-palette/editor-commands.tsx +16 -3
  156. package/src/components/ui/command-palette/index.tsx +4 -3
  157. package/src/components/ui/controls/material-paint-panel.tsx +93 -18
  158. package/src/components/ui/controls/material-picker.tsx +162 -152
  159. package/src/components/ui/controls/material-properties-editor.tsx +108 -0
  160. package/src/components/ui/controls/metric-control.tsx +118 -44
  161. package/src/components/ui/controls/scene-material-list.tsx +247 -0
  162. package/src/components/ui/controls/slider-control.tsx +88 -38
  163. package/src/components/ui/controls/terrain-sculpt-panel.tsx +193 -0
  164. package/src/components/ui/floating-level-selector.tsx +74 -20
  165. package/src/components/ui/helpers/building-helper.tsx +10 -23
  166. package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
  167. package/src/components/ui/helpers/helper-manager.tsx +273 -14
  168. package/src/components/ui/helpers/item-helper.tsx +28 -32
  169. package/src/components/ui/helpers/registered-tool-helper.tsx +45 -11
  170. package/src/components/ui/helpers/roof-helper.tsx +10 -12
  171. package/src/components/ui/icon-ref.tsx +47 -0
  172. package/src/components/ui/item-catalog/catalog-items.tsx +39 -1
  173. package/src/components/ui/item-catalog/item-catalog.tsx +13 -36
  174. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  175. package/src/components/ui/panels/multi-selection-panel.tsx +57 -0
  176. package/src/components/ui/panels/node-display.ts +17 -17
  177. package/src/components/ui/panels/panel-manager.tsx +36 -14
  178. package/src/components/ui/panels/panel-wrapper.tsx +24 -3
  179. package/src/components/ui/panels/parametric-inspector.tsx +19 -4
  180. package/src/components/ui/panels/reference-panel.tsx +54 -19
  181. package/src/components/ui/panels/selection-breakdown.test.ts +24 -0
  182. package/src/components/ui/panels/selection-breakdown.ts +20 -0
  183. package/src/components/ui/primitives/shortcut-token.tsx +39 -3
  184. package/src/components/ui/primitives/sidebar.tsx +1 -0
  185. package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
  186. package/src/components/ui/sidebar/icon-rail.tsx +50 -31
  187. package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
  188. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +82 -41
  189. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +76 -6
  190. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +12 -4
  191. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +2 -1
  192. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -3
  193. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +10 -7
  194. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
  195. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
  196. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +10 -7
  197. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -1
  198. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +1 -1
  199. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +10 -7
  200. package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -17
  201. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
  202. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
  203. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +72 -25
  204. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
  205. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +12 -8
  206. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +4 -3
  207. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +10 -7
  208. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
  209. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
  210. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +44 -3
  211. package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
  212. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
  213. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
  214. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
  215. package/src/components/ui/sidebar/tab-bar.tsx +42 -28
  216. package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
  217. package/src/components/ui/snap-target-badge.test.tsx +40 -0
  218. package/src/components/ui/snap-target-badge.tsx +88 -0
  219. package/src/components/viewer/viewer-controls-bar.tsx +466 -0
  220. package/src/components/viewer/viewer-scene-header.tsx +235 -0
  221. package/src/components/viewer-overlay.tsx +36 -596
  222. package/src/components/viewer-zone-system.tsx +6 -1
  223. package/src/components/walkthrough-hud.tsx +111 -0
  224. package/src/hooks/use-auto-save.test.ts +14 -0
  225. package/src/hooks/use-auto-save.ts +54 -7
  226. package/src/hooks/use-ceiling-events.ts +3 -2
  227. package/src/hooks/use-drag-action.ts +4 -1
  228. package/src/hooks/use-grid-events.ts +24 -0
  229. package/src/hooks/use-keyboard.ts +438 -70
  230. package/src/index.tsx +355 -9
  231. package/src/lib/active-placement-surface.test.ts +24 -0
  232. package/src/lib/active-placement-surface.ts +42 -0
  233. package/src/lib/camera-pose.test.ts +237 -0
  234. package/src/lib/camera-pose.ts +172 -0
  235. package/src/lib/ceiling-plan-snap.ts +24 -0
  236. package/src/lib/contextual-help.test.ts +112 -0
  237. package/src/lib/contextual-help.ts +144 -0
  238. package/src/lib/continuation.ts +64 -0
  239. package/src/lib/direct-manipulation.test.ts +275 -0
  240. package/src/lib/direct-manipulation.ts +118 -0
  241. package/src/lib/door-interaction.ts +1 -1
  242. package/src/lib/editor-api.ts +23 -35
  243. package/src/lib/elevation-guides.test.ts +107 -0
  244. package/src/lib/elevation-guides.ts +236 -0
  245. package/src/lib/floorplan/annotation-visibility.test.ts +327 -0
  246. package/src/lib/floorplan/annotation-visibility.ts +133 -0
  247. package/src/lib/floorplan/apply-alignment.test.ts +25 -0
  248. package/src/lib/floorplan/apply-alignment.ts +18 -12
  249. package/src/lib/floorplan/drawing-coordination.test.ts +46 -0
  250. package/src/lib/floorplan/drawing-coordination.ts +13 -0
  251. package/src/lib/floorplan/floorplan-export.test.ts +319 -0
  252. package/src/lib/floorplan/floorplan-export.tsx +978 -0
  253. package/src/lib/floorplan/floorplan-extension.test.ts +43 -0
  254. package/src/lib/floorplan/floorplan-extension.ts +181 -0
  255. package/src/lib/floorplan/floorplan-mode.test.ts +120 -0
  256. package/src/lib/floorplan/floorplan-mode.ts +73 -0
  257. package/src/lib/floorplan/floorplan-pdfkit-document.ts +194 -0
  258. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +107 -0
  259. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +680 -0
  260. package/src/lib/floorplan/geometry.ts +52 -0
  261. package/src/lib/floorplan/index.ts +3 -0
  262. package/src/lib/floorplan/items.ts +5 -2
  263. package/src/lib/floorplan/plan-coords.ts +21 -0
  264. package/src/lib/fresh-planar-placement.test.ts +283 -3
  265. package/src/lib/fresh-planar-placement.ts +85 -1
  266. package/src/lib/glb-export.test.ts +556 -0
  267. package/src/lib/glb-export.ts +1071 -0
  268. package/src/lib/ground-surface.ts +95 -0
  269. package/src/lib/history.test.ts +96 -0
  270. package/src/lib/history.ts +31 -0
  271. package/src/lib/interaction/hot-set.test.ts +133 -0
  272. package/src/lib/interaction/hot-set.ts +67 -0
  273. package/src/lib/interaction/overlay-policy.test.ts +52 -0
  274. package/src/lib/interaction/overlay-policy.ts +59 -0
  275. package/src/lib/interaction/scope.ts +243 -0
  276. package/src/lib/level-duplication.test.ts +2 -1
  277. package/src/lib/level-selection.ts +2 -2
  278. package/src/lib/material-paint.ts +36 -49
  279. package/src/lib/measurement-kind.test.ts +30 -0
  280. package/src/lib/measurement-kind.ts +19 -0
  281. package/src/lib/measurement-label.test.ts +47 -0
  282. package/src/lib/measurement-label.ts +68 -0
  283. package/src/lib/measurement-parser.ts +116 -0
  284. package/src/lib/measurements.test.ts +188 -0
  285. package/src/lib/measurements.ts +210 -0
  286. package/src/lib/paint-scope.test.ts +454 -0
  287. package/src/lib/paint-scope.ts +461 -0
  288. package/src/lib/placement-drag-release.ts +23 -0
  289. package/src/lib/planar-cursor-placement.test.ts +57 -0
  290. package/src/lib/plugin-panels.test.ts +19 -0
  291. package/src/lib/plugin-panels.ts +91 -0
  292. package/src/lib/quick-action-feedback.ts +33 -0
  293. package/src/lib/quick-action-nodes.test.ts +94 -0
  294. package/src/lib/quick-action-nodes.ts +47 -0
  295. package/src/lib/quick-measurement.test.ts +77 -0
  296. package/src/lib/quick-measurement.ts +109 -0
  297. package/src/lib/roof-duplication.ts +6 -6
  298. package/src/lib/roof-hover-outline-proxy.ts +22 -0
  299. package/src/lib/roof-wall-hit.ts +164 -0
  300. package/src/lib/scene-clipboard.test.ts +325 -0
  301. package/src/lib/scene-clipboard.ts +310 -21
  302. package/src/lib/scene.ts +28 -5
  303. package/src/lib/selection-routing.test.ts +313 -0
  304. package/src/lib/selection-routing.ts +180 -0
  305. package/src/lib/sfx/index.ts +1 -0
  306. package/src/lib/sfx/movement-tick.test.ts +65 -0
  307. package/src/lib/sfx/movement-tick.ts +18 -0
  308. package/src/lib/sfx-bus.ts +82 -14
  309. package/src/lib/sfx-player.test.ts +266 -0
  310. package/src/lib/sfx-player.ts +262 -69
  311. package/src/lib/site-boundary.test.ts +53 -0
  312. package/src/lib/site-boundary.ts +27 -0
  313. package/src/lib/slab-plan-snap.test.ts +93 -0
  314. package/src/lib/slab-plan-snap.ts +108 -0
  315. package/src/lib/snapping-mode.test.ts +165 -0
  316. package/src/lib/snapping-mode.ts +191 -0
  317. package/src/lib/stair-duplication.test.ts +103 -0
  318. package/src/lib/stair-duplication.ts +58 -86
  319. package/src/lib/stair-levels.ts +2 -0
  320. package/src/lib/surface-plan-snap.test.ts +71 -0
  321. package/src/lib/surface-plan-snap.ts +256 -0
  322. package/src/lib/terrain-sculpt.test.ts +429 -0
  323. package/src/lib/terrain-sculpt.ts +301 -0
  324. package/src/lib/terrain-verb-color.test.ts +51 -0
  325. package/src/lib/terrain-verb-color.ts +58 -0
  326. package/src/lib/touch-gesture-priority.test.ts +45 -0
  327. package/src/lib/touch-gesture-priority.ts +30 -0
  328. package/src/lib/use-linear-display.ts +40 -0
  329. package/src/lib/window-interaction.ts +1 -1
  330. package/src/lib/world-grid-snap.ts +37 -5
  331. package/src/store/camera-pose-store.test.ts +29 -0
  332. package/src/store/camera-pose-store.ts +27 -0
  333. package/src/store/live-draft-preview-stores.test.ts +136 -0
  334. package/src/store/navigation-sync-pose-store.test.ts +34 -0
  335. package/src/store/navigation-sync-pose-store.ts +27 -0
  336. package/src/store/terrain-sculpt-mode.test.ts +223 -0
  337. package/src/store/use-delete-confirmation.ts +27 -0
  338. package/src/store/use-direct-manipulation-feedback.ts +20 -0
  339. package/src/store/use-drawing-view.test.ts +23 -0
  340. package/src/store/use-drawing-view.ts +84 -0
  341. package/src/store/use-editor.tsx +665 -126
  342. package/src/store/use-elevation-guides.ts +40 -0
  343. package/src/store/use-facing-pose.ts +44 -0
  344. package/src/store/use-fence-curve-draft.ts +51 -0
  345. package/src/store/use-first-person-hud.ts +40 -0
  346. package/src/store/use-floorplan-annotation-visibility.ts +60 -0
  347. package/src/store/use-floorplan-draft-preview.ts +151 -0
  348. package/src/store/use-floorplan-marquee.ts +50 -0
  349. package/src/store/use-floorplan-mode.ts +110 -0
  350. package/src/store/use-interaction-scope.test.ts +206 -0
  351. package/src/store/use-interaction-scope.ts +140 -0
  352. package/src/store/use-measurement-draft.test.ts +559 -0
  353. package/src/store/use-measurement-draft.ts +570 -0
  354. package/src/store/use-opening-guides.ts +41 -0
  355. package/src/store/use-path-draft-preview.ts +90 -0
  356. package/src/store/use-placement-preview.ts +11 -3
  357. package/src/store/use-quick-measurement-hud.test.ts +53 -0
  358. package/src/store/use-quick-measurement-hud.ts +77 -0
  359. package/src/store/use-segment-draft-chain.ts +28 -0
  360. package/src/store/use-stair-build-preview.ts +53 -0
  361. package/src/store/use-wall-snap-indicator.ts +2 -0
  362. package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
  363. package/src/components/editor/group-move-handle.tsx +0 -316
  364. package/src/components/ui/panels/paint-panel.tsx +0 -163
  365. package/src/lib/floorplan/selection-tool.ts +0 -271
  366. package/src/lib/level-name.ts +0 -11
@@ -2,24 +2,27 @@
2
2
 
3
3
  import { emitter, type FenceNode, isCurvedWall, type WallNode } from '@pascal-app/core'
4
4
  import { type MouseEvent as ReactMouseEvent, useCallback } from 'react'
5
+ import { resolveCeilingPlanPointSnap } from '../../lib/ceiling-plan-snap'
5
6
  import { alignFloorplanDraftPoint, getPlanPointDistance } from '../../lib/floorplan'
7
+ import { resolveSlabPlanPointSnap } from '../../lib/slab-plan-snap'
8
+ import useAlignmentGuides from '../../store/use-alignment-guides'
9
+ import useEditor, { isAngleSnapActive, isMagneticSnapActive } from '../../store/use-editor'
10
+ import usePlacementPreview from '../../store/use-placement-preview'
11
+ import useSegmentDraftChain from '../../store/use-segment-draft-chain'
6
12
  import { snapFenceDraftPoint } from '../tools/fence/fence-drafting'
7
- import {
8
- WALL_FINE_GRID_STEP,
9
- WALL_GRID_STEP,
10
- type WallPlanPoint,
11
- } from '../tools/wall/wall-drafting'
13
+ import { getSegmentGridStep, type WallPlanPoint } from '../tools/wall/wall-drafting'
12
14
 
13
15
  type UseFloorplanBackgroundPlacementArgs = {
14
16
  activePolygonDraftPoints: WallPlanPoint[]
15
17
  ceilingDraftPoints: WallPlanPoint[]
16
18
  clearFencePlacementDraft: () => void
17
19
  clearRoofPlacementDraft: () => void
20
+ clearWallPlacementDraft: () => void
18
21
  emitFloorplanGridEvent: (
19
22
  type: 'click' | 'double-click' | 'move',
20
23
  planPoint: WallPlanPoint,
21
24
  event: ReactMouseEvent<SVGSVGElement>,
22
- ) => WallPlanPoint
25
+ ) => void
23
26
  fenceDraftStart: WallPlanPoint | null
24
27
  fences: FenceNode[]
25
28
  findClosestWallPoint: (
@@ -40,7 +43,7 @@ type UseFloorplanBackgroundPlacementArgs = {
40
43
  ) => boolean
41
44
  handleCeilingPlacementPoint: (point: WallPlanPoint) => void
42
45
  handleSlabPlacementPoint: (point: WallPlanPoint) => void
43
- handleWallPlacementPoint: (point: WallPlanPoint, options?: { singleWall?: boolean }) => void
46
+ handleWallPlacementPoint: (point: WallPlanPoint) => void
44
47
  handleZonePlacementPoint: (point: WallPlanPoint) => void
45
48
  isCeilingBuildActive: boolean
46
49
  isCeilingItemPlacementActive: boolean
@@ -51,18 +54,19 @@ type UseFloorplanBackgroundPlacementArgs = {
51
54
  isRoofBuildActive: boolean
52
55
  isWallBuildActive: boolean
53
56
  isZoneBuildActive: boolean
57
+ levelId: string | null
54
58
  roofDraftStart: WallPlanPoint | null
55
59
  setCursorPoint: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
56
60
  setFenceDraftEnd: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
57
61
  setFenceDraftStart: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
58
62
  setRoofDraftEnd: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
59
63
  setRoofDraftStart: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
60
- shiftPressed: boolean
61
64
  snapWallDraftPoint: (args: {
62
65
  point: WallPlanPoint
63
66
  walls: WallNode[]
64
67
  start?: WallPlanPoint
65
68
  angleSnap?: boolean
69
+ bypassSnap?: boolean
66
70
  step?: number
67
71
  gridSnap?: (point: WallPlanPoint) => WallPlanPoint
68
72
  }) => WallPlanPoint
@@ -77,7 +81,7 @@ type UseFloorplanBackgroundPlacementArgs = {
77
81
  * Snap a building-local plan point to the world XZ grid at `step`.
78
82
  * Injected so the hook doesn't have to know the building's rotation
79
83
  * or position — used by wall / fence branches that snap at variable
80
- * step (Shift = fine).
84
+ * step.
81
85
  */
82
86
  worldGridSnap: (point: WallPlanPoint, step: number) => WallPlanPoint
83
87
  }
@@ -87,6 +91,7 @@ export function useFloorplanBackgroundPlacement({
87
91
  ceilingDraftPoints,
88
92
  clearFencePlacementDraft,
89
93
  clearRoofPlacementDraft,
94
+ clearWallPlacementDraft,
90
95
  emitFloorplanGridEvent,
91
96
  fenceDraftStart,
92
97
  fences,
@@ -107,13 +112,13 @@ export function useFloorplanBackgroundPlacement({
107
112
  isRoofBuildActive,
108
113
  isWallBuildActive,
109
114
  isZoneBuildActive,
115
+ levelId,
110
116
  roofDraftStart,
111
117
  setCursorPoint,
112
118
  setFenceDraftEnd,
113
119
  setFenceDraftStart,
114
120
  setRoofDraftEnd,
115
121
  setRoofDraftStart,
116
- shiftPressed,
117
122
  snapWallDraftPoint,
118
123
  snapPolygonDraftPoint,
119
124
  toPoint2D,
@@ -144,22 +149,29 @@ export function useFloorplanBackgroundPlacement({
144
149
  stopPropagation: () => {},
145
150
  } as any)
146
151
  }
152
+ // Drop the off-wall ghost on commit so it doesn't linger at the
153
+ // just-placed spot before the next pointer move re-evaluates.
154
+ usePlacementPreview.getState().clear()
147
155
  return true
148
156
  }
149
157
 
150
158
  if (isCeilingBuildActive) {
151
- // Align the committed vertex the same way the move-preview did, so
152
- // the placed point matches what the user saw. Skip when angle snap
153
- // owns the vertex (matches the move branch).
154
- const angleSnap = ceilingDraftPoints.length > 0 && !shiftPressed
155
- let snappedPoint = snapPolygonDraftPoint({
159
+ // Align the committed vertex the same way the move-preview did, so the
160
+ // placed point matches what the user saw mode-driven (the chip):
161
+ // `grid` quantizes, `angles` locks 15° rays, `lines` snaps onto walls /
162
+ // alignment, `off` is free. Alt remains force/free at commit time.
163
+ const angleSnap = ceilingDraftPoints.length > 0 && isAngleSnapActive()
164
+ const fallbackPoint = snapPolygonDraftPoint({
156
165
  point: planPoint,
157
166
  start: ceilingDraftPoints[ceilingDraftPoints.length - 1],
158
167
  angleSnap,
159
168
  })
160
- if (!angleSnap) {
161
- snappedPoint = alignFloorplanDraftPoint(snappedPoint, { bypass: event.altKey })
162
- }
169
+ const snappedPoint = resolveCeilingPlanPointSnap({
170
+ rawPoint: planPoint,
171
+ fallbackPoint,
172
+ levelId,
173
+ align: !angleSnap,
174
+ }).point
163
175
 
164
176
  emitFloorplanGridEvent('click', snappedPoint, event)
165
177
  handleCeilingPlacementPoint(snappedPoint)
@@ -167,8 +179,11 @@ export function useFloorplanBackgroundPlacement({
167
179
  }
168
180
 
169
181
  if (isRoofBuildActive) {
182
+ // Footprint placement (polygon context: grid / lines / off, no angle),
183
+ // mode-driven to match the chip. Alt is force/free at commit time;
184
+ // alignment display/pull follows the active magnetic mode.
170
185
  const snappedPoint = alignFloorplanDraftPoint(getSnappedFloorplanPoint(planPoint), {
171
- bypass: event.altKey,
186
+ applySnap: isMagneticSnapActive(),
172
187
  })
173
188
  emitFloorplanGridEvent('click', snappedPoint, event)
174
189
  setCursorPoint(snappedPoint)
@@ -183,16 +198,19 @@ export function useFloorplanBackgroundPlacement({
183
198
  }
184
199
 
185
200
  if (isFenceBuildActive) {
186
- // Fence draft: grid snap (+ existing-wall/fence endpoint snap), then
187
- // Figma alignment endpoint snap wins (same precedence as move).
188
- // `gridSnap` keeps the snap on the world XZ grid even when the
189
- // building is rotated.
190
- const fenceStep = shiftPressed ? WALL_FINE_GRID_STEP : WALL_GRID_STEP
201
+ // Fence draft: mode-driven (matches the chip), same as the move
202
+ // preview. `grid` snaps to the world XZ grid (rotation-safe via the
203
+ // `gridSnap` callback), `angles` locks 15° rays from the start, `lines`
204
+ // pulls onto walls / fences / alignment, `off` is free.
205
+ const fenceStep = getSegmentGridStep()
206
+ const fenceAngleSnap = fenceDraftStart !== null && isAngleSnapActive()
191
207
  const fenceSnapped = snapFenceDraftPoint({
192
208
  point: planPoint,
193
209
  walls,
194
210
  fences,
195
- step: shiftPressed ? WALL_FINE_GRID_STEP : undefined,
211
+ start: fenceDraftStart ?? undefined,
212
+ angleSnap: fenceAngleSnap,
213
+ magnetic: isMagneticSnapActive(),
196
214
  gridSnap: (p) => worldGridSnap(p, fenceStep),
197
215
  })
198
216
  const fenceGridBase = worldGridSnap(planPoint, fenceStep)
@@ -200,18 +218,42 @@ export function useFloorplanBackgroundPlacement({
200
218
  fenceSnapped[0] !== fenceGridBase[0] || fenceSnapped[1] !== fenceGridBase[1]
201
219
  const snappedPoint = fenceLocked
202
220
  ? fenceSnapped
203
- : alignFloorplanDraftPoint(fenceSnapped, { bypass: event.altKey })
221
+ : alignFloorplanDraftPoint(fenceSnapped, {
222
+ applySnap: isMagneticSnapActive() && !fenceAngleSnap,
223
+ })
204
224
 
205
225
  emitFloorplanGridEvent('click', snappedPoint, event)
206
226
  setCursorPoint(snappedPoint)
207
227
 
228
+ // Double-click finishes the chain. The emit above already made the
229
+ // 3D fence tool stopDrafting (its detail >= 2 guard), so close the
230
+ // 2D draft too — leaving it open desyncs the two views.
231
+ if (fenceDraftStart && event.detail >= 2) {
232
+ clearFencePlacementDraft()
233
+ return true
234
+ }
235
+
208
236
  if (!fenceDraftStart) {
209
237
  setFenceDraftStart(snappedPoint)
210
238
  setFenceDraftEnd(snappedPoint)
211
239
  } else if (
212
240
  getPlanPointDistance(toPoint2D(fenceDraftStart), toPoint2D(snappedPoint)) >= 0.01
213
241
  ) {
214
- clearFencePlacementDraft()
242
+ // Single mode commits one segment per click: the same emit above
243
+ // already made the 3D fence tool stopDrafting, so close the 2D
244
+ // draft too instead of chaining.
245
+ if (useEditor.getState().getContinuation('fence') === 'single') {
246
+ clearFencePlacementDraft()
247
+ setCursorPoint(snappedPoint)
248
+ return true
249
+ }
250
+ // The 3D fence tool owns creation and keeps chaining from the
251
+ // committed fence's resolved end — chain the 2D draft from the
252
+ // same published point so both views draft the next segment
253
+ // from the same start.
254
+ const nextStart = useSegmentDraftChain.getState().fence ?? snappedPoint
255
+ setFenceDraftStart(nextStart)
256
+ setFenceDraftEnd(nextStart)
215
257
  } else {
216
258
  setFenceDraftEnd(snappedPoint)
217
259
  }
@@ -224,15 +266,20 @@ export function useFloorplanBackgroundPlacement({
224
266
  // swallow the click and skip local draft state updates — leaving
225
267
  // the 2D draft polygon invisible while the 3D tool builds fine).
226
268
  if (isPolygonBuildActive) {
227
- const angleSnap = activePolygonDraftPoints.length > 0 && !shiftPressed
228
- let snappedPoint = snapPolygonDraftPoint({
269
+ const angleSnap = activePolygonDraftPoints.length > 0 && isAngleSnapActive()
270
+ const fallbackPoint = snapPolygonDraftPoint({
229
271
  point: planPoint,
230
272
  start: activePolygonDraftPoints[activePolygonDraftPoints.length - 1],
231
273
  angleSnap,
232
274
  })
233
- if (!angleSnap) {
234
- snappedPoint = alignFloorplanDraftPoint(snappedPoint, { bypass: event.altKey })
235
- }
275
+ // Zone shares the slab surface snap (wall corners / midpoints /
276
+ // crossings + alignment) it's the same polygon-on-a-level draw.
277
+ const snappedPoint = resolveSlabPlanPointSnap({
278
+ rawPoint: planPoint,
279
+ fallbackPoint,
280
+ levelId,
281
+ align: !angleSnap,
282
+ }).point
236
283
 
237
284
  // Emit the grid event so the registry-driven slab tool also
238
285
  // sees the click (parity with ceiling / fence / roof branches
@@ -255,26 +302,45 @@ export function useFloorplanBackgroundPlacement({
255
302
  // / draftEnd state in the floor plan would never update, leaving
256
303
  // the dashed-line draft preview invisible.
257
304
  if (isWallBuildActive) {
258
- // Wall draft: grid snap (+ existing-wall endpoint/join snap), then
259
- // Figma alignment endpoint/join snap wins (same precedence as the
260
- // move-preview branch), so committing onto a corner still works.
261
- // `gridSnap` keeps the snap on the world XZ grid even when the
262
- // building is rotated.
263
- const wallStep = shiftPressed ? WALL_FINE_GRID_STEP : WALL_GRID_STEP
305
+ // Wall draft: mode-driven (matches the chip + the move-preview branch).
306
+ // `grid` snaps to the world XZ grid (rotation-safe via `gridSnap`),
307
+ // `angles` locks 15° rays from the start, `lines` pulls the endpoint
308
+ // onto existing wall corners / edges + alignment, `off` is free.
309
+ const wallStep = getSegmentGridStep()
310
+ const wallAngleSnap = draftStart !== null && isAngleSnapActive()
264
311
  const wallSnapped = snapWallDraftPoint({
265
312
  point: planPoint,
266
313
  walls,
267
- step: shiftPressed ? WALL_FINE_GRID_STEP : undefined,
314
+ start: draftStart ?? undefined,
315
+ angleSnap: wallAngleSnap,
268
316
  gridSnap: (p) => worldGridSnap(p, wallStep),
269
317
  })
270
318
  const wallGridBase = worldGridSnap(planPoint, wallStep)
271
319
  const wallLocked = wallSnapped[0] !== wallGridBase[0] || wallSnapped[1] !== wallGridBase[1]
272
- const snappedPoint = wallLocked
273
- ? wallSnapped
274
- : alignFloorplanDraftPoint(wallSnapped, { bypass: false })
320
+ let snappedPoint = wallSnapped
321
+ if (wallLocked) {
322
+ useAlignmentGuides.getState().clear()
323
+ } else {
324
+ // Alignment lines are shown in every mode; the pull applies only when
325
+ // magnetic ('lines') and the segment isn't angle-locked.
326
+ snappedPoint = alignFloorplanDraftPoint(wallSnapped, {
327
+ applySnap: isMagneticSnapActive() && !wallAngleSnap,
328
+ })
329
+ }
275
330
 
276
331
  emitFloorplanGridEvent('click', snappedPoint, event)
277
- handleWallPlacementPoint(snappedPoint, { singleWall: event.altKey })
332
+
333
+ // Double-click finishes the chain. The emit above already made the
334
+ // 3D wall tool stopDrafting (its detail >= 2 guard), so close the
335
+ // 2D draft too — otherwise it stays open against a closed 3D tool
336
+ // and the next previewed segment is silently never created.
337
+ if (draftStart && event.detail >= 2) {
338
+ clearWallPlacementDraft()
339
+ setCursorPoint(snappedPoint)
340
+ return true
341
+ }
342
+
343
+ handleWallPlacementPoint(snappedPoint)
278
344
  return true
279
345
  }
280
346
 
@@ -291,7 +357,8 @@ export function useFloorplanBackgroundPlacement({
291
357
  // local floor-plan draft handler (column / spawn / shelf / etc.).
292
358
  // The tool's `grid:click` subscriber owns the placement.
293
359
  if (isFloorplanGridInteractionActive) {
294
- const snappedPoint = emitFloorplanGridEvent('click', planPoint, event)
360
+ const snappedPoint = getSnappedFloorplanPoint(planPoint)
361
+ emitFloorplanGridEvent('click', snappedPoint, event)
295
362
  setCursorPoint(snappedPoint)
296
363
  return true
297
364
  }
@@ -303,6 +370,7 @@ export function useFloorplanBackgroundPlacement({
303
370
  ceilingDraftPoints,
304
371
  clearFencePlacementDraft,
305
372
  clearRoofPlacementDraft,
373
+ clearWallPlacementDraft,
306
374
  emitFloorplanGridEvent,
307
375
  fenceDraftStart,
308
376
  fences,
@@ -322,13 +390,13 @@ export function useFloorplanBackgroundPlacement({
322
390
  isRoofBuildActive,
323
391
  isWallBuildActive,
324
392
  isZoneBuildActive,
393
+ levelId,
325
394
  roofDraftStart,
326
395
  setCursorPoint,
327
396
  setFenceDraftEnd,
328
397
  setFenceDraftStart,
329
398
  setRoofDraftEnd,
330
399
  setRoofDraftStart,
331
- shiftPressed,
332
400
  snapWallDraftPoint,
333
401
  snapPolygonDraftPoint,
334
402
  toPoint2D,
@@ -0,0 +1,82 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { collectRegistrySelectionIdsInBounds } from './use-floorplan-hit-testing'
3
+
4
+ const identityMatrix = {
5
+ inverse() {
6
+ return this
7
+ },
8
+ }
9
+
10
+ function pointFactory() {
11
+ return {
12
+ x: 0,
13
+ y: 0,
14
+ matrixTransform() {
15
+ return pointFactoryWithPosition(this.x, this.y)
16
+ },
17
+ }
18
+ }
19
+
20
+ function pointFactoryWithPosition(x: number, y: number) {
21
+ return {
22
+ x,
23
+ y,
24
+ matrixTransform() {
25
+ return pointFactoryWithPosition(x, y)
26
+ },
27
+ }
28
+ }
29
+
30
+ function geometry(x: number, y: number, width: number, height: number) {
31
+ return {
32
+ getBBox: () => ({ x, y, width, height }),
33
+ getScreenCTM: () => identityMatrix,
34
+ } as unknown as SVGGraphicsElement
35
+ }
36
+
37
+ function entry(nodeId: string, children: SVGGraphicsElement[]) {
38
+ return {
39
+ dataset: { nodeId },
40
+ querySelectorAll: () => children,
41
+ } as unknown as SVGGElement
42
+ }
43
+
44
+ function scene(entries: SVGGElement[]) {
45
+ return {
46
+ getScreenCTM: () => identityMatrix,
47
+ ownerSVGElement: {
48
+ createSVGPoint: pointFactory,
49
+ },
50
+ querySelectorAll: () => entries,
51
+ } as unknown as SVGGElement
52
+ }
53
+
54
+ describe('collectRegistrySelectionIdsInBounds', () => {
55
+ test('selects rendered registry geometry and preserves candidate order', () => {
56
+ const renderedScene = scene([
57
+ entry('wall_1', [geometry(0, 0, 4, 0.2)]),
58
+ entry('door_1', [geometry(1, 1, 0.9, 0.9)]),
59
+ entry('outside_1', [geometry(20, 20, 1, 1)]),
60
+ ])
61
+
62
+ expect(
63
+ collectRegistrySelectionIdsInBounds(renderedScene, { minX: -1, minY: -1, maxX: 5, maxY: 5 }, [
64
+ 'door_1',
65
+ 'outside_1',
66
+ 'wall_1',
67
+ ]),
68
+ ).toEqual(['door_1', 'wall_1'])
69
+ })
70
+
71
+ test('ignores rendered entries outside the selectable registry candidates', () => {
72
+ const renderedScene = scene([entry('zone_1', [geometry(0, 0, 2, 2)])])
73
+
74
+ expect(
75
+ collectRegistrySelectionIdsInBounds(
76
+ renderedScene,
77
+ { minX: -1, minY: -1, maxX: 3, maxY: 3 },
78
+ [],
79
+ ),
80
+ ).toEqual([])
81
+ })
82
+ })