@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
@@ -2,6 +2,12 @@
2
2
 
3
3
  import { useScene } from '@pascal-app/core'
4
4
  import { useCallback, useEffect, useRef, useState } from 'react'
5
+ import {
6
+ lingoUnitSpec,
7
+ measurementHint,
8
+ parseMeasurement,
9
+ } from '../../../lib/measurement-parser'
10
+ import { useLinearDisplay } from '../../../lib/use-linear-display'
5
11
  import { cn } from '../../../lib/utils'
6
12
 
7
13
  interface SliderControlProps {
@@ -59,10 +65,17 @@ export function SliderControl({
59
65
  unit = '',
60
66
  restoreOnCommit = true,
61
67
  }: SliderControlProps) {
68
+ // Display/storage conversion so the value honors the metric/imperial toggle.
69
+ // `value`, `onChange`, `onCommit`, `min`/`max`/`clamp` are always in the
70
+ // stored unit (meters for `unit === 'm'`); the step, drag deltas, text field
71
+ // and rendered number are in the DISPLAY unit (feet when imperial). For
72
+ // metric and non-length units these conversions are the identity.
73
+ const { isImperial, displayUnit, toDisplay, toStored } = useLinearDisplay(unit, precision)
74
+
62
75
  const [isEditing, setIsEditing] = useState(false)
63
76
  const [isDragging, setIsDragging] = useState(false)
64
77
  const [isHovered, setIsHovered] = useState(false)
65
- const [inputValue, setInputValue] = useState(value.toFixed(precision))
78
+ const [inputValue, setInputValue] = useState(toDisplay(value).toFixed(precision))
66
79
 
67
80
  const dragRef = useRef<{
68
81
  // Original value at drag start — preserved across modifier re-anchors so
@@ -80,12 +93,23 @@ export function SliderControl({
80
93
  valueRef.current = value
81
94
 
82
95
  const clamp = useCallback((val: number) => Math.min(Math.max(val, min), max), [min, max])
96
+ // Apply a signed display-unit delta to a stored value, rounding in the
97
+ // display unit and clamping in the stored unit.
98
+ const applyDisplayDelta = useCallback(
99
+ (storedValue: number, displayDelta: number, displayStep: number) =>
100
+ clamp(
101
+ toStored(
102
+ Number.parseFloat((toDisplay(storedValue) + displayDelta).toFixed(stepPrecision(displayStep))),
103
+ ),
104
+ ),
105
+ [clamp, toDisplay, toStored],
106
+ )
83
107
 
84
108
  useEffect(() => {
85
109
  if (!isEditing) {
86
- setInputValue(value.toFixed(precision))
110
+ setInputValue(toDisplay(value).toFixed(precision))
87
111
  }
88
- }, [value, precision, isEditing])
112
+ }, [value, precision, isEditing, toDisplay])
89
113
 
90
114
  // Wheel support on the label
91
115
  useEffect(() => {
@@ -96,14 +120,13 @@ export function SliderControl({
96
120
  e.preventDefault()
97
121
  const direction = e.deltaY < 0 ? 1 : -1
98
122
  const s = getAdjustedStep(step, e)
99
- const newValue = clamp(valueRef.current + direction * s)
100
- const final = Number.parseFloat(newValue.toFixed(stepPrecision(s)))
123
+ const final = applyDisplayDelta(valueRef.current, direction * s, s)
101
124
  if (final !== valueRef.current) onChange(final)
102
125
  onCommit?.(final)
103
126
  }
104
127
  el.addEventListener('wheel', handleWheel, { passive: false })
105
128
  return () => el.removeEventListener('wheel', handleWheel)
106
- }, [isEditing, step, clamp, onChange, onCommit])
129
+ }, [isEditing, step, applyDisplayDelta, onChange, onCommit])
107
130
 
108
131
  // Arrow key support while hovered
109
132
  useEffect(() => {
@@ -115,15 +138,14 @@ export function SliderControl({
115
138
  if (direction !== 0) {
116
139
  e.preventDefault()
117
140
  const s = getAdjustedStep(step, e)
118
- const newValue = clamp(valueRef.current + direction * s)
119
- const final = Number.parseFloat(newValue.toFixed(stepPrecision(s)))
141
+ const final = applyDisplayDelta(valueRef.current, direction * s, s)
120
142
  if (final !== valueRef.current) onChange(final)
121
143
  onCommit?.(final)
122
144
  }
123
145
  }
124
146
  window.addEventListener('keydown', handleKeyDown)
125
147
  return () => window.removeEventListener('keydown', handleKeyDown)
126
- }, [isHovered, isEditing, step, clamp, onChange, onCommit])
148
+ }, [isHovered, isEditing, step, applyDisplayDelta, onChange, onCommit])
127
149
 
128
150
  const handleLabelPointerDown = useCallback(
129
151
  (e: React.PointerEvent<HTMLDivElement>) => {
@@ -160,15 +182,13 @@ export function SliderControl({
160
182
  const dx = e.clientX - anchorX
161
183
  const s = step * multiplier
162
184
  // 4 px per step at default sensitivity
163
- const newValue = clamp(
164
- Number.parseFloat((anchorValue + (dx / 4) * s).toFixed(stepPrecision(s))),
165
- )
185
+ const newValue = applyDisplayDelta(anchorValue, (dx / 4) * s, s)
166
186
  if (newValue !== valueRef.current) {
167
187
  valueRef.current = newValue
168
188
  onChange(newValue)
169
189
  }
170
190
  },
171
- [step, clamp, onChange],
191
+ [step, applyDisplayDelta, onChange],
172
192
  )
173
193
 
174
194
  const handleLabelPointerUp = useCallback(
@@ -195,49 +215,65 @@ export function SliderControl({
195
215
 
196
216
  const handleValueClick = useCallback(() => {
197
217
  setIsEditing(true)
198
- setInputValue(value.toFixed(precision))
199
- }, [value, precision])
218
+ setInputValue(toDisplay(value).toFixed(precision))
219
+ }, [value, precision, toDisplay])
200
220
 
201
221
  const submitValue = useCallback(() => {
202
- const numValue = Number.parseFloat(inputValue)
203
- if (Number.isNaN(numValue)) {
204
- setInputValue(value.toFixed(precision))
222
+ const spec = lingoUnitSpec(unit)
223
+ let stored = spec
224
+ ? parseMeasurement(inputValue, spec, {
225
+ bareUnit: isImperial ? 'ft' : spec.unitId,
226
+ system: isImperial ? 'us' : 'metric',
227
+ })
228
+ : null
229
+ if (stored === null) {
230
+ // Fallback: a bare number typed in the DISPLAY unit → convert to stored.
231
+ const numValue = Number.parseFloat(inputValue)
232
+ stored = Number.isFinite(numValue) ? toStored(numValue) : null
233
+ }
234
+ if (stored === null) {
235
+ setInputValue(toDisplay(value).toFixed(precision))
205
236
  } else {
206
- const nextValue = clamp(Number.parseFloat(numValue.toFixed(precision)))
237
+ const nextValue = clamp(toStored(Number.parseFloat(toDisplay(stored).toFixed(precision))))
207
238
  onChange(nextValue)
208
239
  onCommit?.(nextValue)
209
240
  }
210
241
  setIsEditing(false)
211
- }, [inputValue, onChange, onCommit, clamp, precision, value])
242
+ }, [inputValue, unit, isImperial, onChange, onCommit, clamp, precision, value, toDisplay, toStored])
243
+
244
+ const spec = lingoUnitSpec(unit)
245
+ const hint =
246
+ isEditing && spec
247
+ ? measurementHint(inputValue, spec, {
248
+ bareUnit: isImperial ? 'ft' : spec.unitId,
249
+ system: isImperial ? 'us' : 'metric',
250
+ displayUnit: isImperial ? 'ft' : spec.unitId,
251
+ precision,
252
+ clamp,
253
+ })
254
+ : null
212
255
 
213
256
  const handleInputKeyDown = useCallback(
214
257
  (e: React.KeyboardEvent<HTMLInputElement>) => {
215
258
  if (e.key === 'Enter') {
216
259
  submitValue()
217
260
  } else if (e.key === 'Escape') {
218
- setInputValue(value.toFixed(precision))
261
+ setInputValue(toDisplay(value).toFixed(precision))
219
262
  setIsEditing(false)
220
- } else if (e.key === 'ArrowUp') {
263
+ } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
221
264
  e.preventDefault()
265
+ const direction = e.key === 'ArrowUp' ? 1 : -1
222
266
  const adjustedStep = getAdjustedStep(step, e)
223
- const newV = clamp(
224
- Number.parseFloat((value + adjustedStep).toFixed(stepPrecision(adjustedStep))),
225
- )
267
+ const newV = applyDisplayDelta(value, direction * adjustedStep, adjustedStep)
226
268
  onChange(newV)
227
- setInputValue(newV.toFixed(precision))
228
- } else if (e.key === 'ArrowDown') {
229
- e.preventDefault()
230
- const adjustedStep = getAdjustedStep(step, e)
231
- const newV = clamp(
232
- Number.parseFloat((value - adjustedStep).toFixed(stepPrecision(adjustedStep))),
233
- )
234
- onChange(newV)
235
- setInputValue(newV.toFixed(precision))
269
+ setInputValue(toDisplay(newV).toFixed(precision))
236
270
  }
237
271
  },
238
- [submitValue, value, precision, step, clamp, onChange],
272
+ [submitValue, value, precision, step, applyDisplayDelta, onChange, toDisplay],
239
273
  )
240
274
 
275
+ const displayValue = toDisplay(value)
276
+
241
277
  return (
242
278
  <div
243
279
  className={cn(
@@ -279,6 +315,11 @@ export function SliderControl({
279
315
  <div className="flex items-center text-xs">
280
316
  {isEditing ? (
281
317
  <>
318
+ {hint && (
319
+ <span className="mr-1 shrink-0 whitespace-nowrap text-[10px] text-muted-foreground/50 tabular-nums">
320
+ {hint}
321
+ </span>
322
+ )}
282
323
  <input
283
324
  autoFocus
284
325
  className="w-14 bg-transparent p-0 text-right font-mono text-foreground outline-none selection:bg-primary/30"
@@ -288,7 +329,16 @@ export function SliderControl({
288
329
  type="text"
289
330
  value={inputValue}
290
331
  />
291
- {unit && <span className="ml-[1px] text-muted-foreground">{unit}</span>}
332
+ {displayUnit && (
333
+ <span
334
+ className={cn(
335
+ 'ml-[1px] transition-opacity duration-150',
336
+ hint ? 'opacity-0' : 'text-muted-foreground/40',
337
+ )}
338
+ >
339
+ {displayUnit}
340
+ </span>
341
+ )}
292
342
  </>
293
343
  ) : (
294
344
  <div
@@ -296,9 +346,9 @@ export function SliderControl({
296
346
  onClick={handleValueClick}
297
347
  >
298
348
  <span className="font-mono tabular-nums tracking-tight" suppressHydrationWarning>
299
- {Number(value.toFixed(precision)).toFixed(precision)}
349
+ {Number(displayValue.toFixed(precision)).toFixed(precision)}
300
350
  </span>
301
- {unit && <span className="ml-[1px] text-muted-foreground">{unit}</span>}
351
+ {displayUnit && <span className="ml-[1px] text-muted-foreground">{displayUnit}</span>}
302
352
  </div>
303
353
  )}
304
354
  </div>
@@ -26,7 +26,7 @@ import {
26
26
  useScene,
27
27
  } from '@pascal-app/core'
28
28
  import { useViewer } from '@pascal-app/viewer'
29
- import { Copy, GripVertical, MoreVertical, Plus, Trash2 } from 'lucide-react'
29
+ import { ClipboardPaste, Copy, GripVertical, MoreVertical, Plus, Trash2 } from 'lucide-react'
30
30
  import {
31
31
  type ButtonHTMLAttributes,
32
32
  type CSSProperties,
@@ -34,13 +34,21 @@ import {
34
34
  useEffect,
35
35
  useRef,
36
36
  useState,
37
+ useSyncExternalStore,
37
38
  } from 'react'
38
39
  import { useShallow } from 'zustand/react/shallow'
39
40
  import {
40
41
  buildLevelDuplicateCreateOps,
41
42
  type LevelDuplicatePreset,
42
43
  } from '../../lib/level-duplication'
44
+ import { getDefaultLevelName, getLevelDisplayName } from '@pascal-app/core'
43
45
  import { deleteLevelWithFallbackSelection } from '../../lib/level-selection'
46
+ import {
47
+ getEditorClipboardSnapshot,
48
+ pasteEditorClipboardToLevel,
49
+ subscribeEditorClipboard,
50
+ } from '../../lib/scene-clipboard'
51
+ import { sfxEmitter } from '../../lib/sfx-bus'
44
52
  import { cn } from '../../lib/utils'
45
53
  import { LevelDuplicateDialog } from './level-duplicate-dialog'
46
54
  import {
@@ -53,10 +61,6 @@ import {
53
61
  } from './primitives/dialog'
54
62
  import { Popover, PopoverContent, PopoverTrigger } from './primitives/popover'
55
63
 
56
- function getLevelDisplayLabel(level: LevelNode) {
57
- return level.name || `Level ${level.level}`
58
- }
59
-
60
64
  // ── Inline rename input for a level row ─────────────────────────────────────
61
65
 
62
66
  function LevelInlineRename({
@@ -69,7 +73,7 @@ function LevelInlineRename({
69
73
  onStopEditing: () => void
70
74
  }) {
71
75
  const updateNode = useScene((s) => s.updateNode)
72
- const defaultName = `Level ${level.level}`
76
+ const defaultName = getDefaultLevelName(level.level)
73
77
  const [value, setValue] = useState(level.name || '')
74
78
  const inputRef = useRef<HTMLInputElement>(null)
75
79
 
@@ -126,6 +130,7 @@ function LevelRow({
126
130
  dragHandleRef,
127
131
  onSelect,
128
132
  onDuplicate,
133
+ onPaste,
129
134
  onRequestDelete,
130
135
  }: {
131
136
  level: LevelNode
@@ -135,6 +140,7 @@ function LevelRow({
135
140
  dragHandleRef?: (element: HTMLButtonElement | null) => void
136
141
  onSelect: () => void
137
142
  onDuplicate: (preset?: LevelDuplicatePreset) => void
143
+ onPaste?: () => void
138
144
  onRequestDelete: () => void
139
145
  }) {
140
146
  const [duplicateDialogOpen, setDuplicateDialogOpen] = useState(false)
@@ -160,7 +166,7 @@ function LevelRow({
160
166
  >
161
167
  <button
162
168
  {...dragHandleProps}
163
- aria-label={`Reorder ${getLevelDisplayLabel(level)}`}
169
+ aria-label={`Reorder ${getLevelDisplayName(level)}`}
164
170
  className={cn(
165
171
  'ml-0.5 flex h-6 w-4 shrink-0 cursor-grab touch-none items-center justify-center rounded-md text-muted-foreground/35 opacity-0 transition-colors hover:bg-white/5 hover:text-foreground focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 group-hover/level:opacity-100',
166
172
  isDragging && 'cursor-grabbing opacity-100',
@@ -183,10 +189,10 @@ function LevelRow({
183
189
  e.stopPropagation()
184
190
  setIsEditing(true)
185
191
  }}
186
- title={getLevelDisplayLabel(level)}
192
+ title={getLevelDisplayName(level)}
187
193
  type="button"
188
194
  >
189
- <span className="truncate">{getLevelDisplayLabel(level)}</span>
195
+ <span className="truncate">{getLevelDisplayName(level)}</span>
190
196
  </button>
191
197
 
192
198
  {/* Vertical three-dot menu — inside the pill */}
@@ -223,6 +229,19 @@ function LevelRow({
223
229
  <Copy className="h-3 w-3" />
224
230
  Duplicate with options...
225
231
  </button>
232
+ {onPaste && (
233
+ <button
234
+ className="flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-muted-foreground text-xs transition-colors hover:bg-white/10 hover:text-foreground"
235
+ onClick={(e) => {
236
+ e.stopPropagation()
237
+ onPaste()
238
+ }}
239
+ type="button"
240
+ >
241
+ <ClipboardPaste className="h-3 w-3" />
242
+ Paste copied selection
243
+ </button>
244
+ )}
226
245
  <button
227
246
  className="flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-muted-foreground text-xs transition-colors hover:bg-white/10 hover:text-red-400"
228
247
  onClick={(e) => {
@@ -256,12 +275,14 @@ function SortableLevelRow({
256
275
  isSelected,
257
276
  onSelect,
258
277
  onDuplicate,
278
+ onPaste,
259
279
  onRequestDelete,
260
280
  }: {
261
281
  level: LevelNode
262
282
  isSelected: boolean
263
283
  onSelect: () => void
264
284
  onDuplicate: (preset?: LevelDuplicatePreset) => void
285
+ onPaste?: () => void
265
286
  onRequestDelete: () => void
266
287
  }) {
267
288
  const {
@@ -291,6 +312,7 @@ function SortableLevelRow({
291
312
  isSelected={isSelected}
292
313
  level={level}
293
314
  onDuplicate={onDuplicate}
315
+ onPaste={onPaste}
294
316
  onRequestDelete={onRequestDelete}
295
317
  onSelect={onSelect}
296
318
  />
@@ -310,6 +332,11 @@ export function FloatingLevelSelector() {
310
332
 
311
333
  const [deletingLevel, setDeletingLevel] = useState<LevelNode | null>(null)
312
334
  const [draggingLevelId, setDraggingLevelId] = useState<string | null>(null)
335
+ const clipboardSnapshot = useSyncExternalStore(
336
+ subscribeEditorClipboard,
337
+ getEditorClipboardSnapshot,
338
+ getEditorClipboardSnapshot,
339
+ )
313
340
  const sensors = useSensors(
314
341
  useSensor(PointerSensor, {
315
342
  activationConstraint: { distance: 4 },
@@ -424,6 +451,13 @@ export function FloatingLevelSelector() {
424
451
  [createNodes, levels, resolvedBuildingId, setSelection, updateNodes],
425
452
  )
426
453
 
454
+ const handlePasteToLevel = useCallback((level: LevelNode) => {
455
+ const result = pasteEditorClipboardToLevel(level.id)
456
+ if (result?.pastedIds.length) {
457
+ sfxEmitter.emit('sfx:item-place')
458
+ }
459
+ }, [])
460
+
427
461
  const handleDragStart = useCallback((event: DragStartEvent) => {
428
462
  setDraggingLevelId(String(event.active.id))
429
463
  }, [])
@@ -523,6 +557,9 @@ export function FloatingLevelSelector() {
523
557
  isSelected={isSelected}
524
558
  level={level}
525
559
  onDuplicate={(preset) => handleDuplicateLevel(level, preset)}
560
+ onPaste={
561
+ clipboardSnapshot ? () => handlePasteToLevel(level) : undefined
562
+ }
526
563
  onRequestDelete={() => setDeletingLevel(level)}
527
564
  onSelect={() =>
528
565
  setSelection(
@@ -559,7 +596,7 @@ export function FloatingLevelSelector() {
559
596
  <DialogTitle>Delete level</DialogTitle>
560
597
  <DialogDescription>
561
598
  Are you sure you want to delete{' '}
562
- <strong>{deletingLevel ? getLevelDisplayLabel(deletingLevel) : ''}</strong>? All
599
+ <strong>{deletingLevel ? getLevelDisplayName(deletingLevel) : ''}</strong>? All
563
600
  walls, floors, and objects on this level will be permanently removed.
564
601
  </DialogDescription>
565
602
  </DialogHeader>
@@ -1,32 +1,19 @@
1
- import { ShortcutToken } from '../primitives/shortcut-token'
1
+ import { ContextualHelperPanel } from './contextual-helper-panel'
2
2
 
3
3
  interface BuildingHelperProps {
4
4
  showRotate?: boolean
5
5
  }
6
6
 
7
+ // Rotate is one hint with both keys (R / T) — never two separate
8
+ // counterclockwise / clockwise rows — to match every other placement helper.
7
9
  export function BuildingHelper({ showRotate }: BuildingHelperProps) {
8
10
  return (
9
- <div className="pointer-events-none fixed top-1/2 right-4 z-40 flex -translate-y-1/2 flex-col gap-2 rounded-lg border border-border bg-background/95 px-4 py-3 shadow-lg backdrop-blur-md">
10
- <div className="flex items-center gap-2 text-sm">
11
- <ShortcutToken value="Left click" />
12
- <span className="text-muted-foreground">Place building</span>
13
- </div>
14
- {showRotate && (
15
- <>
16
- <div className="flex items-center gap-2 text-sm">
17
- <ShortcutToken value="R" />
18
- <span className="text-muted-foreground">Rotate counterclockwise</span>
19
- </div>
20
- <div className="flex items-center gap-2 text-sm">
21
- <ShortcutToken value="T" />
22
- <span className="text-muted-foreground">Rotate clockwise</span>
23
- </div>
24
- </>
25
- )}
26
- <div className="flex items-center gap-2 text-sm">
27
- <ShortcutToken value="Esc" />
28
- <span className="text-muted-foreground">Cancel</span>
29
- </div>
30
- </div>
11
+ <ContextualHelperPanel
12
+ hints={[
13
+ { keys: ['Left click'], label: 'Place building' },
14
+ ...(showRotate ? [{ keys: ['R', 'T'], label: 'Rotate' }] : []),
15
+ { keys: ['Esc'], label: 'Cancel' },
16
+ ]}
17
+ />
31
18
  )
32
19
  }