@pascal-app/editor 0.8.0 → 0.9.2

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 (350) hide show
  1. package/package.json +15 -13
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +168 -0
  3. package/src/components/editor/bake-exporter.tsx +47 -0
  4. package/src/components/editor/custom-camera-controls.tsx +795 -28
  5. package/src/components/editor/editor-layout-mobile.tsx +8 -3
  6. package/src/components/editor/editor-layout-v2.tsx +76 -51
  7. package/src/components/editor/export-manager.tsx +69 -32
  8. package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
  9. package/src/components/editor/first-person/build-collider-world.test.ts +182 -0
  10. package/src/components/editor/first-person/build-collider-world.ts +228 -83
  11. package/src/components/editor/first-person-controls.tsx +943 -27
  12. package/src/components/editor/floating-action-menu.tsx +676 -208
  13. package/src/components/editor/floating-building-action-menu.tsx +2 -2
  14. package/src/components/editor/floorplan-background-selection.test.ts +58 -0
  15. package/src/components/editor/floorplan-background-selection.ts +3 -2
  16. package/src/components/editor/floorplan-panel.tsx +6916 -12137
  17. package/src/components/editor/grid.tsx +197 -36
  18. package/src/components/editor/group-actions.ts +457 -0
  19. package/src/components/editor/group-floating-action-menu.tsx +130 -0
  20. package/src/components/editor/group-move-3d.ts +397 -0
  21. package/src/components/editor/group-rotate-handle.tsx +420 -0
  22. package/src/components/editor/group-selection-box-3d.tsx +173 -0
  23. package/src/components/editor/group-transform-shared.test.ts +396 -0
  24. package/src/components/editor/group-transform-shared.ts +489 -0
  25. package/src/components/editor/handles/handle-arrow.tsx +581 -0
  26. package/src/components/editor/handles/use-handle-drag.ts +246 -0
  27. package/src/components/editor/index.tsx +412 -162
  28. package/src/components/editor/measurement-pill.tsx +115 -0
  29. package/src/components/editor/node-action-menu.tsx +14 -1
  30. package/src/components/editor/node-arrow-handles.tsx +1646 -0
  31. package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
  32. package/src/components/editor/quick-measurement-card.tsx +70 -0
  33. package/src/components/editor/quick-measurement-hud.tsx +28 -0
  34. package/src/components/editor/riser-diagram-panel.tsx +137 -0
  35. package/src/components/editor/selection-manager.tsx +1106 -483
  36. package/src/components/editor/site-edge-labels.tsx +45 -21
  37. package/src/components/editor/slab-hole-highlights.tsx +474 -0
  38. package/src/components/editor/snapshot-capture-overlay.tsx +369 -155
  39. package/src/components/editor/three-context-bridge.ts +22 -0
  40. package/src/components/editor/thumbnail-generator.tsx +140 -41
  41. package/src/components/editor/use-floorplan-background-placement.ts +190 -35
  42. package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
  43. package/src/components/editor/use-floorplan-scene-data.ts +19 -7
  44. package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
  45. package/src/components/editor/wall-measurement-label.tsx +14 -19
  46. package/src/components/editor/wall-move-side-handles.tsx +870 -0
  47. package/src/components/editor/wall-opening-highlights.tsx +181 -0
  48. package/src/components/editor/wall-snap-beacon-layer.tsx +304 -0
  49. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +15 -6
  50. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
  51. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
  52. package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
  53. package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
  54. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
  55. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
  56. package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
  57. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +392 -0
  58. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +857 -0
  59. package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
  60. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +77 -0
  61. package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
  62. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
  63. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
  64. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
  65. package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -66
  66. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +62 -0
  67. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
  68. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +3325 -0
  69. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +24 -9
  70. package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
  71. package/src/components/editor-2d/svg-paths.ts +10 -2
  72. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +439 -57
  73. package/src/components/systems/ceiling/ceiling-system.tsx +109 -12
  74. package/src/components/systems/roof/roof-edit-system.tsx +1851 -22
  75. package/src/components/systems/selection-affordance-manager.tsx +38 -0
  76. package/src/components/systems/stair/stair-edit-system.tsx +1 -1
  77. package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
  78. package/src/components/systems/zone/zone-system.tsx +42 -13
  79. package/src/components/tools/elevator/elevator-defaults.ts +8 -0
  80. package/src/components/tools/elevator/elevator-tool.tsx +375 -0
  81. package/src/components/tools/elevator/move-elevator-tool.tsx +263 -0
  82. package/src/components/tools/fence/fence-drafting.ts +141 -27
  83. package/src/components/tools/item/move-tool.tsx +45 -105
  84. package/src/components/tools/item/placement-math.test.ts +22 -0
  85. package/src/components/tools/item/placement-math.ts +59 -7
  86. package/src/components/tools/item/placement-strategies.ts +432 -16
  87. package/src/components/tools/item/placement-types.ts +22 -1
  88. package/src/components/tools/item/use-draft-node.ts +51 -2
  89. package/src/components/tools/item/use-placement-coordinator.tsx +1399 -256
  90. package/src/components/tools/registry/move-registry-node-tool.tsx +1064 -0
  91. package/src/components/tools/roof/roof-tool.tsx +350 -28
  92. package/src/components/tools/select/box-select-state.ts +88 -0
  93. package/src/components/tools/select/box-select-tool.tsx +442 -520
  94. package/src/components/tools/select/marquee-geometry.test.ts +161 -0
  95. package/src/components/tools/select/marquee-geometry.ts +155 -0
  96. package/src/components/tools/select/plane-box-select-tool.tsx +570 -0
  97. package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
  98. package/src/components/tools/select/select-candidates.test.ts +109 -0
  99. package/src/components/tools/select/select-candidates.ts +85 -0
  100. package/src/components/tools/shared/affordance-dispatch.ts +30 -0
  101. package/src/components/tools/shared/cursor-sphere.tsx +62 -26
  102. package/src/components/tools/shared/drag-bounding-box.tsx +195 -0
  103. package/src/components/tools/shared/facing-indicator.tsx +87 -0
  104. package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
  105. package/src/components/tools/shared/floor-stack-preview.ts +25 -0
  106. package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
  107. package/src/components/tools/shared/placement-box-geometry.ts +123 -0
  108. package/src/components/tools/shared/placement-box.tsx +305 -0
  109. package/src/components/tools/shared/polygon-editor.tsx +866 -175
  110. package/src/components/tools/shared/segment-angle.ts +56 -0
  111. package/src/components/tools/site/site-boundary-editor.tsx +520 -16
  112. package/src/components/tools/site/site-flag-model.ts +18 -0
  113. package/src/components/tools/stair/stair-defaults.ts +2 -1
  114. package/src/components/tools/stair/stair-tool.tsx +342 -37
  115. package/src/components/tools/tool-manager.tsx +239 -62
  116. package/src/components/tools/wall/wall-drafting.test.ts +330 -0
  117. package/src/components/tools/wall/wall-drafting.ts +248 -171
  118. package/src/components/tools/wall/wall-snap-geometry.test.ts +123 -0
  119. package/src/components/tools/wall/wall-snap-geometry.ts +331 -0
  120. package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
  121. package/src/components/tools/zone/zone-tool.tsx +86 -82
  122. package/src/components/ui/action-menu/action-button.tsx +21 -1
  123. package/src/components/ui/action-menu/camera-actions.tsx +24 -17
  124. package/src/components/ui/action-menu/control-modes.tsx +40 -170
  125. package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
  126. package/src/components/ui/action-menu/index.tsx +3 -117
  127. package/src/components/ui/action-menu/measurement-control.tsx +188 -0
  128. package/src/components/ui/action-menu/structure-tools.tsx +27 -93
  129. package/src/components/ui/action-menu/view-toggles.tsx +279 -90
  130. package/src/components/ui/command-palette/editor-commands.tsx +20 -15
  131. package/src/components/ui/command-palette/index.tsx +5 -3
  132. package/src/components/ui/controls/material-paint-panel.tsx +152 -0
  133. package/src/components/ui/controls/material-picker.tsx +87 -177
  134. package/src/components/ui/controls/material-properties-editor.tsx +108 -0
  135. package/src/components/ui/controls/metric-control.tsx +142 -46
  136. package/src/components/ui/controls/scene-material-list.tsx +247 -0
  137. package/src/components/ui/controls/slider-control.tsx +88 -38
  138. package/src/components/ui/floating-level-selector.tsx +47 -10
  139. package/src/components/ui/helpers/building-helper.tsx +10 -23
  140. package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
  141. package/src/components/ui/helpers/helper-manager.tsx +235 -23
  142. package/src/components/ui/helpers/item-helper.tsx +28 -32
  143. package/src/components/ui/helpers/registered-tool-helper.tsx +59 -0
  144. package/src/components/ui/helpers/roof-helper.tsx +10 -12
  145. package/src/components/ui/icon-ref.tsx +47 -0
  146. package/src/components/ui/item-catalog/catalog-items.tsx +40 -1
  147. package/src/components/ui/item-catalog/item-catalog.tsx +16 -36
  148. package/src/components/ui/level-duplicate-dialog.tsx +2 -1
  149. package/src/components/ui/panels/node-display.ts +17 -16
  150. package/src/components/ui/panels/panel-manager.tsx +55 -58
  151. package/src/components/ui/panels/panel-wrapper.tsx +238 -12
  152. package/src/components/ui/panels/parametric-inspector.tsx +482 -0
  153. package/src/components/ui/panels/reference-panel.tsx +54 -19
  154. package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
  155. package/src/components/ui/primitives/shortcut-token.tsx +39 -3
  156. package/src/components/ui/primitives/sidebar.tsx +1 -0
  157. package/src/components/ui/scene-loader.tsx +1 -3
  158. package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
  159. package/src/components/ui/sidebar/icon-rail.tsx +50 -31
  160. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
  161. package/src/components/ui/sidebar/panels/items-panel/index.tsx +82 -8
  162. package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
  163. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +122 -41
  164. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +66 -7
  165. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +267 -0
  166. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
  167. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +9 -5
  168. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +86 -0
  169. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
  170. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
  171. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +92 -0
  172. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
  173. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +3 -3
  174. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +86 -0
  175. package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -21
  176. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
  177. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
  178. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +140 -0
  179. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +48 -8
  180. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +132 -0
  181. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -5
  182. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +86 -0
  183. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +7 -1
  184. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
  185. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +112 -39
  186. package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
  187. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
  188. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
  189. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
  190. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +51 -2
  191. package/src/components/ui/sidebar/tab-bar.tsx +75 -1
  192. package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
  193. package/src/components/ui/snap-target-badge.test.tsx +40 -0
  194. package/src/components/ui/snap-target-badge.tsx +88 -0
  195. package/src/components/viewer-overlay.tsx +253 -61
  196. package/src/components/viewer-zone-system.tsx +7 -2
  197. package/src/hooks/use-auto-save.test.ts +14 -0
  198. package/src/hooks/use-auto-save.ts +64 -10
  199. package/src/hooks/use-ceiling-events.ts +176 -0
  200. package/src/hooks/use-drag-action.ts +127 -0
  201. package/src/hooks/use-keyboard.ts +493 -95
  202. package/src/hooks/use-selection.ts +64 -0
  203. package/src/index.tsx +451 -6
  204. package/src/lib/active-placement-surface.ts +35 -0
  205. package/src/lib/ceiling-plan-snap.ts +24 -0
  206. package/src/lib/constants.ts +6 -2
  207. package/src/lib/contextual-help.test.ts +112 -0
  208. package/src/lib/contextual-help.ts +144 -0
  209. package/src/lib/continuation.ts +64 -0
  210. package/src/lib/direct-manipulation.test.ts +192 -0
  211. package/src/lib/direct-manipulation.ts +109 -0
  212. package/src/lib/editor-api.ts +50 -0
  213. package/src/lib/elevator-support.ts +96 -0
  214. package/src/lib/floorplan/apply-alignment.test.ts +25 -0
  215. package/src/lib/floorplan/apply-alignment.ts +117 -0
  216. package/src/lib/floorplan/floorplan-export.tsx +364 -0
  217. package/src/lib/floorplan/geometry.ts +53 -0
  218. package/src/lib/floorplan/index.ts +10 -0
  219. package/src/lib/floorplan/items.ts +5 -2
  220. package/src/lib/floorplan/plan-coords.ts +21 -0
  221. package/src/lib/floorplan/selection-tool.ts +40 -0
  222. package/src/lib/floorplan/stairs.ts +12 -1
  223. package/src/lib/fresh-planar-placement.test.ts +339 -0
  224. package/src/lib/fresh-planar-placement.ts +130 -0
  225. package/src/lib/glb-export.test.ts +441 -0
  226. package/src/lib/glb-export.ts +874 -0
  227. package/src/lib/history.ts +11 -1
  228. package/src/lib/interaction/hot-set.test.ts +133 -0
  229. package/src/lib/interaction/hot-set.ts +67 -0
  230. package/src/lib/interaction/overlay-policy.test.ts +51 -0
  231. package/src/lib/interaction/overlay-policy.ts +59 -0
  232. package/src/lib/interaction/scope.ts +211 -0
  233. package/src/lib/level-duplication.ts +54 -42
  234. package/src/lib/level-selection.ts +2 -2
  235. package/src/lib/material-paint.ts +227 -48
  236. package/src/lib/measurement-kind.test.ts +30 -0
  237. package/src/lib/measurement-kind.ts +19 -0
  238. package/src/lib/measurement-label.test.ts +47 -0
  239. package/src/lib/measurement-label.ts +68 -0
  240. package/src/lib/measurement-parser.ts +116 -0
  241. package/src/lib/measurements.test.ts +183 -0
  242. package/src/lib/measurements.ts +199 -0
  243. package/src/lib/paint-scope.test.ts +454 -0
  244. package/src/lib/paint-scope.ts +461 -0
  245. package/src/lib/placement-drag-release.ts +23 -0
  246. package/src/lib/placement-metadata.ts +29 -0
  247. package/src/lib/planar-cursor-placement.test.ts +100 -0
  248. package/src/lib/planar-cursor-placement.ts +42 -0
  249. package/src/lib/plugin-panels.test.ts +19 -0
  250. package/src/lib/plugin-panels.ts +91 -0
  251. package/src/lib/quick-measurement.test.ts +77 -0
  252. package/src/lib/quick-measurement.ts +109 -0
  253. package/src/lib/roof-duplication.ts +7 -7
  254. package/src/lib/roof-hover-outline-proxy.ts +22 -0
  255. package/src/lib/roof-wall-hit.ts +164 -0
  256. package/src/lib/scene-clipboard.test.ts +196 -0
  257. package/src/lib/scene-clipboard.ts +332 -0
  258. package/src/lib/scene.ts +40 -8
  259. package/src/lib/selection-routing.test.ts +298 -0
  260. package/src/lib/selection-routing.ts +175 -0
  261. package/src/lib/sfx/index.ts +1 -0
  262. package/src/lib/sfx/movement-tick.test.ts +65 -0
  263. package/src/lib/sfx/movement-tick.ts +18 -0
  264. package/src/lib/sfx-bus.ts +69 -10
  265. package/src/lib/sfx-player.test.ts +124 -0
  266. package/src/lib/sfx-player.ts +148 -55
  267. package/src/lib/site-boundary.ts +1 -0
  268. package/src/lib/slab-plan-snap.test.ts +93 -0
  269. package/src/lib/slab-plan-snap.ts +108 -0
  270. package/src/lib/snapping-mode.test.ts +138 -0
  271. package/src/lib/snapping-mode.ts +179 -0
  272. package/src/lib/stair-duplication.ts +4 -4
  273. package/src/lib/stair-levels.test.ts +146 -0
  274. package/src/lib/stair-levels.ts +177 -0
  275. package/src/lib/surface-plan-snap.test.ts +71 -0
  276. package/src/lib/surface-plan-snap.ts +256 -0
  277. package/src/lib/use-linear-display.ts +40 -0
  278. package/src/lib/world-grid-snap.ts +231 -0
  279. package/src/lib/zone-content.ts +130 -0
  280. package/src/store/use-alignment-guides.ts +21 -0
  281. package/src/store/use-direct-manipulation-feedback.ts +20 -0
  282. package/src/store/use-editor.tsx +767 -114
  283. package/src/store/use-facing-pose.ts +44 -0
  284. package/src/store/use-fence-curve-draft.ts +21 -0
  285. package/src/store/use-floorplan-draft-preview.ts +101 -0
  286. package/src/store/use-floorplan-marquee.ts +50 -0
  287. package/src/store/use-interaction-scope.test.ts +186 -0
  288. package/src/store/use-interaction-scope.ts +132 -0
  289. package/src/store/use-measurement-draft.test.ts +530 -0
  290. package/src/store/use-measurement-draft.ts +543 -0
  291. package/src/store/use-opening-guides.ts +41 -0
  292. package/src/store/use-placement-preview.ts +39 -0
  293. package/src/store/use-quick-measurement-hud.test.ts +53 -0
  294. package/src/store/use-quick-measurement-hud.ts +77 -0
  295. package/src/store/use-segment-draft-chain.ts +28 -0
  296. package/src/store/use-stair-build-preview.ts +43 -0
  297. package/src/store/use-wall-move-ghosts.ts +36 -0
  298. package/src/store/use-wall-snap-indicator.ts +34 -0
  299. package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
  300. package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
  301. package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
  302. package/src/components/tools/building/move-building-tool.tsx +0 -157
  303. package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
  304. package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
  305. package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
  306. package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -264
  307. package/src/components/tools/column/column-tool.tsx +0 -97
  308. package/src/components/tools/column/move-column-tool.tsx +0 -105
  309. package/src/components/tools/door/door-math.ts +0 -110
  310. package/src/components/tools/door/door-tool.tsx +0 -324
  311. package/src/components/tools/door/move-door-tool.tsx +0 -412
  312. package/src/components/tools/fence/curve-fence-tool.tsx +0 -178
  313. package/src/components/tools/fence/fence-tool.tsx +0 -346
  314. package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -441
  315. package/src/components/tools/fence/move-fence-tool.tsx +0 -302
  316. package/src/components/tools/item/item-tool.tsx +0 -26
  317. package/src/components/tools/roof/move-roof-tool.tsx +0 -364
  318. package/src/components/tools/slab/move-slab-tool.tsx +0 -182
  319. package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
  320. package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
  321. package/src/components/tools/slab/slab-tool.tsx +0 -322
  322. package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
  323. package/src/components/tools/spawn/spawn-tool.tsx +0 -130
  324. package/src/components/tools/wall/curve-wall-tool.tsx +0 -178
  325. package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -426
  326. package/src/components/tools/wall/move-wall-tool.tsx +0 -358
  327. package/src/components/tools/wall/wall-tool.tsx +0 -332
  328. package/src/components/tools/window/move-window-tool.tsx +0 -447
  329. package/src/components/tools/window/window-math.ts +0 -117
  330. package/src/components/tools/window/window-tool.tsx +0 -332
  331. package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
  332. package/src/components/ui/helpers/slab-helper.tsx +0 -20
  333. package/src/components/ui/helpers/wall-helper.tsx +0 -20
  334. package/src/components/ui/panels/ceiling-panel.tsx +0 -249
  335. package/src/components/ui/panels/column-panel.tsx +0 -759
  336. package/src/components/ui/panels/door-panel.tsx +0 -1299
  337. package/src/components/ui/panels/fence-panel.tsx +0 -222
  338. package/src/components/ui/panels/item-panel.tsx +0 -306
  339. package/src/components/ui/panels/paint-panel.tsx +0 -163
  340. package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
  341. package/src/components/ui/panels/roof-panel.tsx +0 -280
  342. package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
  343. package/src/components/ui/panels/slab-panel.tsx +0 -250
  344. package/src/components/ui/panels/spawn-panel.tsx +0 -161
  345. package/src/components/ui/panels/stair-panel.tsx +0 -577
  346. package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
  347. package/src/components/ui/panels/wall-panel.tsx +0 -273
  348. package/src/components/ui/panels/window-panel.tsx +0 -968
  349. package/src/contexts/presets-context.tsx +0 -121
  350. package/src/hooks/use-contextual-tools.ts +0 -61
@@ -0,0 +1,62 @@
1
+ 'use client'
2
+
3
+ import type { FloorplanPalette } from '@pascal-app/core'
4
+ import { createContext, type ReactNode, useContext, useMemo } from 'react'
5
+
6
+ /**
7
+ * Per-frame render context shared between the legacy `floorplan-panel.tsx`
8
+ * and the registry-driven `<FloorplanRegistryLayer>`.
9
+ *
10
+ * The legacy panel is the authoritative owner of the floor-plan SVG —
11
+ * it computes `unitsPerPixel` from the viewBox / surface size, mounts the
12
+ * pan/zoom `<g>`, and knows the active theme. The registry layer is mounted
13
+ * inside the same `<g>`, so anything it draws shares the same coordinate
14
+ * system; this context plumbs through the bits it can't recompute on its
15
+ * own without re-implementing the legacy's resize / theme logic.
16
+ *
17
+ * Once `floorplan-panel.tsx` is fully migrated (Phase 6), this provider
18
+ * moves into a kind-agnostic 2D editor shell and the context loses the
19
+ * "legacy bridge" connotation.
20
+ */
21
+ export type FloorplanRenderContextValue = {
22
+ /** SVG units per screen pixel — used to keep handle radii consistent at any zoom. */
23
+ unitsPerPixel: number
24
+ /** Themed palette mirroring the legacy `FloorplanPalette` accent slots. */
25
+ palette: FloorplanPalette
26
+ /** SVG `<pattern>` id mounted in `<defs>` by the legacy panel for selection hatch fills. */
27
+ hatchPatternId: string
28
+ /**
29
+ * Rotation (degrees) applied to the registry layer's parent `<g>` by the
30
+ * legacy panel — 90° by default, adjusted by building rotation. Renderers
31
+ * that emit text labels use this to keep their final on-screen orientation
32
+ * readable instead of mirroring whatever the parent rotation is.
33
+ */
34
+ sceneRotationDeg: number
35
+ }
36
+
37
+ const FloorplanRenderContext = createContext<FloorplanRenderContextValue | null>(null)
38
+
39
+ export function FloorplanRenderProvider({
40
+ children,
41
+ unitsPerPixel,
42
+ palette,
43
+ hatchPatternId,
44
+ sceneRotationDeg,
45
+ }: FloorplanRenderContextValue & { children: ReactNode }) {
46
+ const value = useMemo<FloorplanRenderContextValue>(
47
+ () => ({ unitsPerPixel, palette, hatchPatternId, sceneRotationDeg }),
48
+ [unitsPerPixel, palette, hatchPatternId, sceneRotationDeg],
49
+ )
50
+ return <FloorplanRenderContext.Provider value={value}>{children}</FloorplanRenderContext.Provider>
51
+ }
52
+
53
+ /**
54
+ * Read the active render context. Returns `null` when called outside a
55
+ * provider — the registry layer treats this as "render statically, skip
56
+ * theme-aware chrome and interactive handles". This makes the layer
57
+ * usable in isolation tests + future editor shells without bringing the
58
+ * whole legacy panel along.
59
+ */
60
+ export function useFloorplanRender(): FloorplanRenderContextValue | null {
61
+ return useContext(FloorplanRenderContext)
62
+ }
@@ -0,0 +1,77 @@
1
+ 'use client'
2
+
3
+ import { useWallSnapIndicator, type WallSnapKind } from '@pascal-app/editor'
4
+ import { memo } from 'react'
5
+ import { useFloorplanRender } from './floorplan-render-context'
6
+
7
+ /**
8
+ * "Magnetic" wall-snap beacon for the 2D floor plan — the top-down twin of the
9
+ * 3D `WallSnapBeaconLayer`. Subscribes to the shared `useWallSnapIndicator`
10
+ * store (published by the floor-plan wall draft + endpoint-move handlers) and
11
+ * draws a marker at the snap point whose shape tells you *what* it caught,
12
+ * matching the 3D glyphs:
13
+ *
14
+ * endpoint (corner) → square midpoint → triangle
15
+ * intersection → ✕ cross wall body (edge) → circle
16
+ *
17
+ * Indigo to match the 3D beacon, except the corner (endpoint) square which is
18
+ * green to match the alignment guides. Sizes are pixel-budgeted via
19
+ * `unitsPerPixel` so the marker stays a
20
+ * constant size on screen at any zoom. Mounted inside the `data-floorplan-scene`
21
+ * group so coordinates are world meters (XZ) 1:1, like the alignment guides.
22
+ */
23
+ const COLOR = '#6366f1' // indigo-500 — matches the 3D beacon
24
+ const ENDPOINT_COLOR = '#22c55e' // green-500 — corner (endpoint) snap accent
25
+
26
+ export const FloorplanSnapBeaconLayer = memo(function FloorplanSnapBeaconLayer() {
27
+ const point = useWallSnapIndicator((s) => s.point)
28
+ const ctx = useFloorplanRender()
29
+
30
+ if (!point) return null
31
+
32
+ const upp = ctx?.unitsPerPixel ?? 0.01
33
+ const m = 6 * upp // base half-size of the glyph in world meters
34
+ const stroke = 1.5 * upp
35
+
36
+ return (
37
+ <g pointerEvents="none">
38
+ <SnapMarker color={COLOR} kind={point.kind} m={m} stroke={stroke} x={point.x} z={point.z} />
39
+ </g>
40
+ )
41
+ })
42
+
43
+ function SnapMarker({
44
+ color,
45
+ kind,
46
+ m,
47
+ stroke,
48
+ x,
49
+ z,
50
+ }: {
51
+ color: string
52
+ kind: WallSnapKind
53
+ m: number
54
+ stroke: number
55
+ x: number
56
+ z: number
57
+ }) {
58
+ if (kind === 'endpoint') {
59
+ return <rect fill={ENDPOINT_COLOR} height={m * 2} width={m * 2} x={x - m} y={z - m} />
60
+ }
61
+ if (kind === 'midpoint') {
62
+ const t = m * 1.3
63
+ const points = `${x},${z - t} ${x - t},${z + t} ${x + t},${z + t}`
64
+ return <polygon fill={color} points={points} />
65
+ }
66
+ if (kind === 'intersection') {
67
+ const c = m * 1.4
68
+ return (
69
+ <g stroke={color} strokeLinecap="round" strokeWidth={stroke * 2}>
70
+ <line x1={x - c} x2={x + c} y1={z - c} y2={z + c} />
71
+ <line x1={x - c} x2={x + c} y1={z + c} y2={z - c} />
72
+ </g>
73
+ )
74
+ }
75
+ // 'wall' (edge / along-wall) → circle
76
+ return <circle cx={x} cy={z} fill={color} r={m} />
77
+ }
@@ -0,0 +1,47 @@
1
+ 'use client'
2
+
3
+ import { memo } from 'react'
4
+ import { useWallMoveGhosts } from '../../store/use-wall-move-ghosts'
5
+
6
+ /**
7
+ * Renders translucent dashed previews of bridge walls that the wall
8
+ * junction planner will insert on commit. Mirrors the 3D
9
+ * `GhostWallPreviewMesh` so 2D and 3D show the same intent mid-drag.
10
+ *
11
+ * Subscribes to `useWallMoveGhosts.bridges`; writes happen inside
12
+ * `wallFloorplanMoveTarget.apply` (cleared on `commit` and by the move
13
+ * overlay's unmount cleanup as a safety net).
14
+ */
15
+ export const FloorplanWallMoveGhostLayer = memo(function FloorplanWallMoveGhostLayer() {
16
+ const bridges = useWallMoveGhosts((s) => s.bridges)
17
+
18
+ if (bridges.length === 0) return null
19
+
20
+ return (
21
+ <g pointerEvents="none">
22
+ {bridges.map((bridge) => {
23
+ // Draw the bridge as a thick stroke at the wall's plan-space
24
+ // thickness — same convention as the actual wall renderer.
25
+ // Dashes scale with thickness so they read consistently no
26
+ // matter the wall size or viewport zoom (no `non-scaling-stroke`
27
+ // here; we want the line to be N meters thick, not N pixels).
28
+ const dash = bridge.thickness * 1.4
29
+ const gap = bridge.thickness * 0.9
30
+ return (
31
+ <line
32
+ key={bridge.id}
33
+ opacity={0.45}
34
+ stroke={bridge.color}
35
+ strokeDasharray={`${dash} ${gap}`}
36
+ strokeLinecap="butt"
37
+ strokeWidth={bridge.thickness}
38
+ x1={bridge.start[0]}
39
+ x2={bridge.end[0]}
40
+ y1={bridge.start[1]}
41
+ y2={bridge.end[1]}
42
+ />
43
+ )
44
+ })}
45
+ </g>
46
+ )
47
+ })
@@ -0,0 +1,266 @@
1
+ 'use client'
2
+
3
+ import { type FloorplanGeometry, loadAssetUrl } from '@pascal-app/core'
4
+ import { memo, useEffect, useState } from 'react'
5
+
6
+ /**
7
+ * Pure-data → SVG converter. Walks a `FloorplanGeometry` tree returned by
8
+ * `def.floorplan(node, ctx)` and emits the matching React-SVG nodes.
9
+ *
10
+ * Coordinates are level-local meters. The wrapping floor-plan panel
11
+ * applies the world→SVG transform via its viewBox, so kinds emit
12
+ * geometry in the same units they reason about in 3D.
13
+ *
14
+ * Group transforms compose: `transform={translate(x y) rotate(deg)}`.
15
+ * Rotations are radians at the data layer (consistent with three.js
16
+ * conventions used by `def.geometry`) and converted to degrees for SVG
17
+ * here — kinds never touch units.
18
+ *
19
+ * Styling props map straight onto SVG attributes. Builders that need
20
+ * theme colors should declare them inline or expose them as registry
21
+ * tokens later (deferred until a real need surfaces — AI-authored kinds
22
+ * can pick safe defaults today).
23
+ */
24
+ export const FloorplanGeometryRenderer = memo(function FloorplanGeometryRenderer({
25
+ geometry,
26
+ pointerEventsOverride,
27
+ }: {
28
+ geometry: FloorplanGeometry
29
+ pointerEventsOverride?: string
30
+ }) {
31
+ return renderNode(geometry, 0, pointerEventsOverride)
32
+ })
33
+
34
+ function styleAttrs(
35
+ g: FloorplanGeometry & { kind: Exclude<FloorplanGeometry['kind'], 'group'> },
36
+ pointerEventsOverride?: string,
37
+ ) {
38
+ // Shared SVG attribute mapping for any styled primitive. Keeps the per-
39
+ // primitive switch arms terse and ensures new style fields land
40
+ // everywhere at once. `as any` avoids re-asserting every variant
41
+ // includes the style fields — they all do, except `group` (which is
42
+ // filtered out by the caller's type bound).
43
+ const s = g as unknown as {
44
+ fill?: string
45
+ fillOpacity?: number
46
+ stroke?: string
47
+ strokeWidth?: number
48
+ strokeDasharray?: string
49
+ strokeLinecap?: 'butt' | 'round' | 'square'
50
+ strokeLinejoin?: 'miter' | 'round' | 'bevel'
51
+ strokeOpacity?: number
52
+ opacity?: number
53
+ vectorEffect?: 'non-scaling-stroke'
54
+ pointerEvents?: string
55
+ cursor?: string
56
+ }
57
+ return {
58
+ fill: s.fill ?? 'none',
59
+ fillOpacity: s.fillOpacity,
60
+ stroke: s.stroke,
61
+ strokeWidth: s.strokeWidth,
62
+ strokeDasharray: s.strokeDasharray,
63
+ strokeLinecap: s.strokeLinecap,
64
+ strokeLinejoin: s.strokeLinejoin,
65
+ strokeOpacity: s.strokeOpacity,
66
+ opacity: s.opacity,
67
+ vectorEffect: s.vectorEffect,
68
+ pointerEvents: pointerEventsOverride ?? s.pointerEvents,
69
+ style: s.cursor ? { cursor: s.cursor } : undefined,
70
+ }
71
+ }
72
+
73
+ function renderNode(
74
+ g: FloorplanGeometry,
75
+ keyHint: number,
76
+ pointerEventsOverride?: string,
77
+ ): React.ReactElement | null {
78
+ switch (g.kind) {
79
+ case 'path':
80
+ return <path d={g.d} key={keyHint} {...styleAttrs(g, pointerEventsOverride)} />
81
+
82
+ case 'polygon':
83
+ return (
84
+ <polygon
85
+ key={keyHint}
86
+ points={pointsToAttr(g.points)}
87
+ {...styleAttrs(g, pointerEventsOverride)}
88
+ />
89
+ )
90
+
91
+ case 'polyline':
92
+ return (
93
+ <polyline
94
+ key={keyHint}
95
+ points={pointsToAttr(g.points)}
96
+ {...styleAttrs(g, pointerEventsOverride)}
97
+ />
98
+ )
99
+
100
+ case 'rect':
101
+ return (
102
+ <rect
103
+ height={g.height}
104
+ key={keyHint}
105
+ rx={g.rx}
106
+ ry={g.ry}
107
+ width={g.width}
108
+ x={g.x}
109
+ y={g.y}
110
+ {...styleAttrs(g, pointerEventsOverride)}
111
+ />
112
+ )
113
+
114
+ case 'circle':
115
+ return (
116
+ <circle
117
+ cx={g.cx}
118
+ cy={g.cy}
119
+ key={keyHint}
120
+ r={g.r}
121
+ {...styleAttrs(g, pointerEventsOverride)}
122
+ />
123
+ )
124
+
125
+ case 'line':
126
+ return (
127
+ <line
128
+ key={keyHint}
129
+ x1={g.x1}
130
+ x2={g.x2}
131
+ y1={g.y1}
132
+ y2={g.y2}
133
+ {...styleAttrs(g, pointerEventsOverride)}
134
+ />
135
+ )
136
+
137
+ case 'text':
138
+ return (
139
+ <text
140
+ dominantBaseline={g.dominantBaseline ?? 'middle'}
141
+ fill={g.fill ?? '#171717'}
142
+ fontFamily={g.fontFamily}
143
+ fontSize={g.fontSize}
144
+ fontWeight={g.fontWeight}
145
+ key={keyHint}
146
+ opacity={g.opacity}
147
+ paintOrder={g.paintOrder}
148
+ stroke={g.stroke}
149
+ strokeLinecap={g.stroke ? 'round' : undefined}
150
+ strokeLinejoin={g.stroke ? 'round' : undefined}
151
+ strokeWidth={g.strokeWidth}
152
+ textAnchor={g.textAnchor ?? 'start'}
153
+ pointerEvents={pointerEventsOverride}
154
+ x={g.x}
155
+ y={g.y}
156
+ >
157
+ {g.text}
158
+ </text>
159
+ )
160
+
161
+ case 'image':
162
+ return (
163
+ <FloorplanImage
164
+ center={g.center}
165
+ height={g.height}
166
+ key={keyHint}
167
+ opacity={g.opacity}
168
+ preserveAspectRatio={g.preserveAspectRatio ?? 'xMidYMid meet'}
169
+ rotation={g.rotation ?? 0}
170
+ url={g.url}
171
+ width={g.width}
172
+ />
173
+ )
174
+
175
+ case 'group': {
176
+ const transform = formatTransform(g.transform)
177
+ return (
178
+ <g key={keyHint} transform={transform}>
179
+ {g.children.map((child, i) => renderNode(child, i, pointerEventsOverride))}
180
+ </g>
181
+ )
182
+ }
183
+
184
+ // The interactive primitives (hatch / hit-line / endpoint-handle /
185
+ // dimension-label) need the SVG context + theme palette + units-per-
186
+ // pixel that only the registry layer has access to. They're rendered
187
+ // by `floorplan-registry-layer.tsx`'s interactive walker instead. If
188
+ // a caller routes one of these through this pure renderer it
189
+ // silently drops — the static renderer is for static output.
190
+ default:
191
+ return null
192
+ }
193
+ }
194
+
195
+ function pointsToAttr(points: readonly (readonly [number, number])[]): string {
196
+ return points.map(([x, y]) => `${x},${y}`).join(' ')
197
+ }
198
+
199
+ function formatTransform(t?: {
200
+ translate?: readonly [number, number]
201
+ rotate?: number
202
+ }): string | undefined {
203
+ if (!t) return undefined
204
+ const parts: string[] = []
205
+ if (t.translate) parts.push(`translate(${t.translate[0]} ${t.translate[1]})`)
206
+ if (t.rotate !== undefined) parts.push(`rotate(${(t.rotate * 180) / Math.PI})`)
207
+ return parts.length > 0 ? parts.join(' ') : undefined
208
+ }
209
+
210
+ /**
211
+ * `image` primitive renderer. Resolves the URL asynchronously via
212
+ * `loadAssetUrl` (handles CDN / Supabase storage) and renders an SVG
213
+ * `<image>` centered at `center`, rotated around it, sized in plan-local
214
+ * metres. While the resolution is in flight, renders nothing.
215
+ */
216
+ function FloorplanImage({
217
+ url,
218
+ center,
219
+ width,
220
+ height,
221
+ rotation,
222
+ preserveAspectRatio,
223
+ opacity,
224
+ }: {
225
+ url: string
226
+ center: readonly [number, number]
227
+ width: number
228
+ height: number
229
+ rotation: number
230
+ preserveAspectRatio: string
231
+ opacity?: number
232
+ }) {
233
+ const [resolvedUrl, setResolvedUrl] = useState<string | null>(null)
234
+ useEffect(() => {
235
+ if (!url) {
236
+ setResolvedUrl(null)
237
+ return
238
+ }
239
+ let cancelled = false
240
+ setResolvedUrl(null)
241
+ loadAssetUrl(url).then((next) => {
242
+ if (!cancelled) setResolvedUrl(next)
243
+ })
244
+ return () => {
245
+ cancelled = true
246
+ }
247
+ }, [url])
248
+ if (!resolvedUrl) return null
249
+ const rotationDeg = (rotation * 180) / Math.PI
250
+ return (
251
+ <g
252
+ pointerEvents="none"
253
+ transform={`translate(${center[0]} ${center[1]}) rotate(${rotationDeg})`}
254
+ >
255
+ <image
256
+ height={height}
257
+ href={resolvedUrl}
258
+ opacity={opacity}
259
+ preserveAspectRatio={preserveAspectRatio}
260
+ width={width}
261
+ x={-width / 2}
262
+ y={-height / 2}
263
+ />
264
+ </g>
265
+ )
266
+ }
@@ -0,0 +1,15 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { resolveFloorplanLabelAngle } from './floorplan-label-angle'
3
+
4
+ describe('resolveFloorplanLabelAngle', () => {
5
+ test('keeps segment labels readable while preserving their screen direction', () => {
6
+ expect(resolveFloorplanLabelAngle(0, 0)).toBe(0)
7
+ expect(resolveFloorplanLabelAngle(Math.PI, 0)).toBe(0)
8
+ expect(resolveFloorplanLabelAngle(Math.PI / 2, 90)).toBe(-90)
9
+ })
10
+
11
+ test('counter-rotates aggregate labels to remain horizontal', () => {
12
+ expect(resolveFloorplanLabelAngle(0, 90, true)).toBe(-90)
13
+ expect(resolveFloorplanLabelAngle(Math.PI / 3, -35, true)).toBe(35)
14
+ })
15
+ })
@@ -0,0 +1,14 @@
1
+ export function resolveFloorplanLabelAngle(
2
+ angleRadians: number,
3
+ sceneRotationDeg: number,
4
+ screenUpright = false,
5
+ ): number {
6
+ if (screenUpright) return -sceneRotationDeg
7
+
8
+ let localAngleDeg = (angleRadians * 180) / Math.PI
9
+ let screenAngleDeg = localAngleDeg + sceneRotationDeg
10
+ screenAngleDeg = ((((screenAngleDeg + 180) % 360) + 360) % 360) - 180
11
+ if (screenAngleDeg > 90) localAngleDeg -= 180
12
+ else if (screenAngleDeg <= -90) localAngleDeg += 180
13
+ return ((((localAngleDeg + 180) % 360) + 360) % 360) - 180
14
+ }