@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,428 @@
1
+ import { Icon } from '@iconify/react'
2
+ import type { ToolHint } from '@pascal-app/core'
3
+ import { Fragment, useSyncExternalStore } from 'react'
4
+ import {
5
+ CONTINUATION_PROFILES,
6
+ type ContinuationContext,
7
+ } from '../../../lib/continuation'
8
+ import type { ContextualShortcutHint } from '../../../lib/contextual-help'
9
+ import { hasActivePaintMaterial } from '../../../lib/material-paint'
10
+ import { paintScopeLabel, type PaintScope } from '../../../lib/paint-scope'
11
+ import { sfxEmitter } from '../../../lib/sfx-bus'
12
+ import {
13
+ cycleSnappingModeIn,
14
+ resolveSnapFlags,
15
+ type SnapContext,
16
+ } from '../../../lib/snapping-mode'
17
+ import { cn } from '../../../lib/utils'
18
+ import useEditor, { type GridSnapStep } from '../../../store/use-editor'
19
+ import useFenceCurveDraft from '../../../store/use-fence-curve-draft'
20
+ import { ShortcutToken } from '../primitives/shortcut-token'
21
+ import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip'
22
+
23
+ // One muted container holds every row — passive key hints and interactive chips
24
+ // alike — so the HUD reads as a single panel, not a stack of floating pills. The
25
+ // background is near-opaque (`bg-background/95`) with a single backdrop blur so
26
+ // active rows stay readable over the 3D scene even while a modifier is held.
27
+ // A 2-track grid: column 1 sizes to `max-content` (the widest key across ALL
28
+ // rows), column 2 (`1fr`) is the label. Every row is a subgrid sharing those
29
+ // tracks, so labels align even when keys differ in width (⌘ vs Shift) or wrap to
30
+ // two lines. Near-opaque bg + single backdrop blur keeps active rows readable.
31
+ const CONTAINER_CLASS =
32
+ 'pointer-events-none fixed top-1/2 right-4 z-40 grid max-w-[260px] -translate-y-1/2 grid-cols-[max-content_1fr] gap-x-2.5 gap-y-1.5 rounded-lg border border-border bg-background/95 px-3 py-2.5 shadow-lg backdrop-blur-md'
33
+
34
+ const TOKEN_CLASS = 'h-5 px-1.5 text-[10px]'
35
+
36
+ // Each row spans both columns as its own subgrid, inheriting the container's
37
+ // tracks so its key/label cells land on the shared column lines.
38
+ const ROW_CLASS = 'col-span-2 grid grid-cols-subgrid'
39
+
40
+ // The key cell (column 1). `items-center` centres the token; the row's
41
+ // `items-start` keeps it on the label's first line when the label wraps.
42
+ const KEY_CELL_CLASS = 'flex items-center gap-1'
43
+
44
+ // Keys pressed together join with "+"; an entry that is itself an array is a
45
+ // group of alternatives and joins with "/" — so [['Cmd/Ctrl', 'Shift'],
46
+ // 'Left click'] reads "⌘ / ⇧ + click".
47
+ function ShortcutSequence({
48
+ active = false,
49
+ keys,
50
+ }: {
51
+ active?: boolean
52
+ keys: Array<string | string[]>
53
+ }) {
54
+ return (
55
+ <div className={KEY_CELL_CLASS}>
56
+ {keys.map((entry, index) => (
57
+ <Fragment key={`${String(entry)}-${index}`}>
58
+ {index > 0 ? (
59
+ <span className="font-medium text-[12px] text-muted-foreground/80 leading-none">
60
+ +
61
+ </span>
62
+ ) : null}
63
+ {Array.isArray(entry) ? (
64
+ entry.map((alternative, altIndex) => (
65
+ <Fragment key={`${alternative}-${altIndex}`}>
66
+ {altIndex > 0 ? (
67
+ <span className="text-[9px] text-muted-foreground/70">/</span>
68
+ ) : null}
69
+ <ShortcutToken
70
+ className={cn(TOKEN_CLASS, active && 'border-white bg-white text-black shadow-sm')}
71
+ value={alternative}
72
+ />
73
+ </Fragment>
74
+ ))
75
+ ) : (
76
+ <ShortcutToken
77
+ className={cn(TOKEN_CLASS, active && 'border-white bg-white text-black shadow-sm')}
78
+ value={entry}
79
+ />
80
+ )}
81
+ </Fragment>
82
+ ))}
83
+ </div>
84
+ )
85
+ }
86
+
87
+ // Shared single-line chip row (key cell + icon/label cell). Rendered either as a
88
+ // passive row (no `onClick`) or a clickable button. The outer container is
89
+ // `pointer-events-none`, so clickable chips opt back in.
90
+ function ChipRow({
91
+ ariaLabel,
92
+ disabled = false,
93
+ icon,
94
+ label,
95
+ onClick,
96
+ shortcut,
97
+ tooltip,
98
+ }: {
99
+ ariaLabel?: string
100
+ disabled?: boolean
101
+ icon?: string
102
+ label: string
103
+ onClick?: () => void
104
+ shortcut?: string
105
+ tooltip?: string
106
+ }) {
107
+ const body = (
108
+ <>
109
+ <span className={KEY_CELL_CLASS}>
110
+ {shortcut ? <ShortcutToken className={TOKEN_CLASS} value={shortcut} /> : null}
111
+ </span>
112
+ <span className="flex min-w-0 items-center gap-1.5 text-muted-foreground text-xs">
113
+ {icon ? <Icon className="shrink-0" height={13} icon={icon} width={13} /> : null}
114
+ <span className="truncate">{label}</span>
115
+ </span>
116
+ </>
117
+ )
118
+
119
+ if (!onClick) {
120
+ return (
121
+ <div className={cn(ROW_CLASS, 'items-center', disabled && 'opacity-45 saturate-0')}>{body}</div>
122
+ )
123
+ }
124
+
125
+ const button = (
126
+ <button
127
+ aria-label={ariaLabel ?? label}
128
+ className={cn(
129
+ ROW_CLASS,
130
+ 'pointer-events-auto cursor-pointer items-center rounded-md text-left transition-colors hover:bg-muted/60',
131
+ disabled && 'opacity-45 saturate-0',
132
+ )}
133
+ onClick={onClick}
134
+ type="button"
135
+ >
136
+ {body}
137
+ </button>
138
+ )
139
+
140
+ if (!tooltip) return button
141
+ return (
142
+ <Tooltip>
143
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
144
+ <TooltipContent side="left">{tooltip}</TooltipContent>
145
+ </Tooltip>
146
+ )
147
+ }
148
+
149
+ const SNAPPING_MODE_ICONS = {
150
+ grid: 'lucide:grid-2x2',
151
+ lines: 'lucide:magnet',
152
+ angles: 'lucide:triangle',
153
+ off: 'lucide:ban',
154
+ } as const
155
+
156
+ const SNAPPING_MODE_LABELS = {
157
+ grid: 'Grid',
158
+ lines: 'Lines',
159
+ angles: 'Angles',
160
+ off: 'Off',
161
+ } as const
162
+
163
+ const GRID_SNAP_STEPS: GridSnapStep[] = [0.5, 0.25, 0.1, 0.05]
164
+
165
+ function nextGridSnapStep(step: GridSnapStep): GridSnapStep {
166
+ const index = GRID_SNAP_STEPS.indexOf(step)
167
+ return GRID_SNAP_STEPS[(index + 1) % GRID_SNAP_STEPS.length] ?? GRID_SNAP_STEPS[0]!
168
+ }
169
+
170
+ // The active interaction's snapping controls, scoped to its context (wall / item
171
+ // / polygon) so each action shows only the modes that make sense for it.
172
+ function SnappingChips({ context }: { context: SnapContext }) {
173
+ const snappingMode = useEditor((s) => s.snappingModeByContext[context])
174
+ const setSnappingMode = useEditor((s) => s.setSnappingMode)
175
+ const gridSnapStep = useEditor((s) => s.gridSnapStep)
176
+ const setGridSnapStep = useEditor((s) => s.setGridSnapStep)
177
+
178
+ const gridActive = resolveSnapFlags(snappingMode).grid
179
+
180
+ return (
181
+ <>
182
+ <ChipRow
183
+ ariaLabel={`Snapping: ${SNAPPING_MODE_LABELS[snappingMode]}`}
184
+ icon={SNAPPING_MODE_ICONS[snappingMode]}
185
+ label={`Snapping: ${SNAPPING_MODE_LABELS[snappingMode]}`}
186
+ onClick={() => {
187
+ setSnappingMode(context, cycleSnappingModeIn(context, snappingMode))
188
+ sfxEmitter.emit('sfx:grid-snap')
189
+ }}
190
+ shortcut="Shift"
191
+ tooltip="Snapping mode — click or press Shift to cycle"
192
+ />
193
+ {gridActive ? (
194
+ <ChipRow
195
+ ariaLabel={`Grid step: ${gridSnapStep.toFixed(2)} m`}
196
+ label={`Grid: ${gridSnapStep.toFixed(2)} m`}
197
+ onClick={() => {
198
+ setGridSnapStep(nextGridSnapStep(gridSnapStep))
199
+ sfxEmitter.emit('sfx:grid-snap')
200
+ }}
201
+ shortcut="Ctrl"
202
+ tooltip="Grid step — click or tap Ctrl to cycle"
203
+ />
204
+ ) : null}
205
+ </>
206
+ )
207
+ }
208
+
209
+ // A kind-owned live mode chip declared on a `ToolHint` (`hint.chip`) — the
210
+ // registry counterpart of the snapping / continuation chips above: shows the
211
+ // current value's label, and clicking the row (or the hint's key, handled by
212
+ // the tool itself) cycles it.
213
+ function ToolHintChipRow({ hint }: { hint: ToolHint & { chip: NonNullable<ToolHint['chip']> } }) {
214
+ const { chip } = hint
215
+ const value = useSyncExternalStore(chip.subscribe, chip.value, chip.value)
216
+ const label = chip.labels[value] ?? hint.label
217
+ return (
218
+ <ChipRow
219
+ ariaLabel={label}
220
+ icon={chip.icons?.[value]}
221
+ label={label}
222
+ onClick={chip.cycle}
223
+ shortcut={hint.key}
224
+ tooltip={chip.tooltip}
225
+ />
226
+ )
227
+ }
228
+
229
+ function ContinuationChip({ context }: { context: ContinuationContext }) {
230
+ const mode = useEditor((s) => s.getContinuation(context))
231
+ const cycleContinuation = useEditor((s) => s.cycleContinuation)
232
+ const profile = CONTINUATION_PROFILES[context]
233
+ const label = profile.labels[mode] ?? mode
234
+ const icon = profile.icons[mode] ?? 'lucide:repeat'
235
+
236
+ return (
237
+ <ChipRow
238
+ ariaLabel={`Continuation: ${label}`}
239
+ icon={icon}
240
+ label={label}
241
+ onClick={() => cycleContinuation(context)}
242
+ shortcut="C"
243
+ tooltip="Continuation — click or press C to cycle"
244
+ />
245
+ )
246
+ }
247
+
248
+ function FenceContinuationChips() {
249
+ const mode = useEditor((s) => s.getContinuation('fence'))
250
+ const setContinuation = useEditor((s) => s.setContinuation)
251
+ const curveStarted = useFenceCurveDraft((s) => s.pointCount > 0)
252
+
253
+ const isCurved = mode === 'curved'
254
+ const straightMode = isCurved ? 'continuous' : mode
255
+ const straightLabel = straightMode === 'single' ? 'Straight: Single' : 'Straight: Continuous'
256
+ const straightIcon = straightMode === 'single' ? 'lucide:minus' : 'lucide:waypoints'
257
+ const typeLabel = isCurved ? 'Type: Curved' : 'Type: Straight'
258
+ const typeIcon = isCurved ? 'lucide:spline' : 'lucide:minus'
259
+
260
+ return (
261
+ <>
262
+ <ChipRow
263
+ ariaLabel={`Fence type: ${isCurved ? 'Curved' : 'Straight'}`}
264
+ icon={typeIcon}
265
+ label={typeLabel}
266
+ onClick={() => setContinuation('fence', isCurved ? 'continuous' : 'curved')}
267
+ shortcut="T"
268
+ tooltip="Fence type — click or press T to switch between straight and curved"
269
+ />
270
+ <ChipRow
271
+ ariaLabel={`Fence continuation: ${straightLabel}`}
272
+ disabled={isCurved}
273
+ icon={straightIcon}
274
+ label={straightLabel}
275
+ onClick={
276
+ isCurved
277
+ ? undefined
278
+ : () => setContinuation('fence', straightMode === 'single' ? 'continuous' : 'single')
279
+ }
280
+ shortcut="C"
281
+ tooltip={
282
+ isCurved
283
+ ? 'Straight continuation is unavailable while curved fence type is active'
284
+ : 'Straight fence continuation — click or press C to toggle'
285
+ }
286
+ />
287
+ {/* Curved fences are committed by a closing gesture rather than per-click,
288
+ so the finish keys aren't discoverable on their own — surface them, but
289
+ only once the user has placed a point and a curve is actually in flight. */}
290
+ {isCurved && curveStarted ? (
291
+ <ChipRow
292
+ icon="lucide:circle-check"
293
+ label="Finish curve (or double-click)"
294
+ shortcut="Enter"
295
+ />
296
+ ) : null}
297
+ </>
298
+ )
299
+ }
300
+
301
+ const PAINT_SCOPE_ICONS: Record<PaintScope, string> = {
302
+ single: 'lucide:square',
303
+ object: 'lucide:box',
304
+ matching: 'lucide:copy',
305
+ room: 'lucide:scan',
306
+ }
307
+
308
+ // The painter's application-scope chip. Driven entirely by the hovered node's
309
+ // derived `paintHover` (scopes + labels), so it works for any kind without a
310
+ // per-target table.
311
+ function PaintScopeChip() {
312
+ // What the cursor is over (that's what the next click paints). `null` when not
313
+ // over a paintable surface — including an item with no slots.
314
+ const paintHover = useEditor((s) => s.paintHover)
315
+ const paintScope = useEditor((s) => s.paintScope)
316
+ const cyclePaintScope = useEditor((s) => s.cyclePaintScope)
317
+ const activePaintMaterial = useEditor((s) => s.activePaintMaterial)
318
+ const paintEraser = useEditor((s) => s.paintEraser)
319
+
320
+ // Nothing to paint with yet (no material picked, not erasing) → the first step
321
+ // is choosing a material, so say that before anything about scope or hovering.
322
+ if (!(paintEraser || hasActivePaintMaterial(activePaintMaterial))) {
323
+ return <ChipRow icon="lucide:palette" label="Select a material to paint" />
324
+ }
325
+
326
+ // Not over anything paintable → guide the user to hover, still teaching Shift.
327
+ if (!paintHover) {
328
+ return (
329
+ <ChipRow icon="lucide:mouse-pointer-click" label="Hover a surface to paint" shortcut="Shift" />
330
+ )
331
+ }
332
+
333
+ const { scopes } = paintHover
334
+ // A scope carried over from another node (the mode is global) falls back to
335
+ // the narrowest for both display and — via the apply-time resolver — behaviour.
336
+ const effective: PaintScope = scopes.includes(paintScope) ? paintScope : 'single'
337
+
338
+ // Paintable but with no scope choice (roof, a one-slot node, …) → a passive
339
+ // row that still names the surface, so the user always sees what they'll paint.
340
+ if (scopes.length <= 1) {
341
+ return (
342
+ <ChipRow
343
+ icon={PAINT_SCOPE_ICONS[effective]}
344
+ label={`Paint: ${paintScopeLabel(effective, paintHover)}`}
345
+ />
346
+ )
347
+ }
348
+
349
+ return (
350
+ <ChipRow
351
+ ariaLabel={`Paint scope: ${paintScopeLabel(effective, paintHover)}`}
352
+ icon={PAINT_SCOPE_ICONS[effective]}
353
+ label={`Paint: ${paintScopeLabel(effective, paintHover)}`}
354
+ onClick={() => cyclePaintScope()}
355
+ shortcut="Shift"
356
+ tooltip="Paint scope — click or press Shift to cycle"
357
+ />
358
+ )
359
+ }
360
+
361
+ export function ContextualHelperPanel({
362
+ hints,
363
+ chipHints = [],
364
+ snapContext = null,
365
+ showPaintScope = false,
366
+ continuationContext = null,
367
+ }: {
368
+ hints: ContextualShortcutHint[]
369
+ // Kind-owned live mode chips (`ToolHint.chip`), rendered alongside the
370
+ // snapping / continuation chips.
371
+ chipHints?: ToolHint[]
372
+ // The active snapping context drives the snapping chips (which mode set). Null
373
+ // → no snapping chips for this interaction.
374
+ snapContext?: SnapContext | null
375
+ showPaintScope?: boolean
376
+ continuationContext?: ContinuationContext | null
377
+ }) {
378
+ if (
379
+ hints.length === 0 &&
380
+ chipHints.length === 0 &&
381
+ !snapContext &&
382
+ !showPaintScope &&
383
+ !continuationContext
384
+ )
385
+ return null
386
+
387
+ return (
388
+ <div className={CONTAINER_CLASS}>
389
+ {snapContext ? <SnappingChips context={snapContext} /> : null}
390
+ {continuationContext === 'fence' ? <FenceContinuationChips /> : null}
391
+ {continuationContext && continuationContext !== 'fence' ? (
392
+ <ContinuationChip context={continuationContext} />
393
+ ) : null}
394
+ {chipHints.map((hint) =>
395
+ hint.chip ? (
396
+ <ToolHintChipRow
397
+ hint={hint as ToolHint & { chip: NonNullable<ToolHint['chip']> }}
398
+ key={`${hint.key}:${hint.label}`}
399
+ />
400
+ ) : null,
401
+ )}
402
+ {showPaintScope ? <PaintScopeChip /> : null}
403
+ {hints.map((hint) => (
404
+ <div
405
+ className={cn(ROW_CLASS, 'items-start')}
406
+ key={`${hint.keys.join('+')}:${hint.label}`}
407
+ >
408
+ <ShortcutSequence active={hint.active} keys={hint.keys} />
409
+ <div className="min-w-0">
410
+ <div
411
+ className={cn(
412
+ 'text-xs leading-5',
413
+ hint.active ? 'font-medium text-white' : 'text-muted-foreground',
414
+ )}
415
+ >
416
+ {hint.label}
417
+ </div>
418
+ {hint.subtitle ? (
419
+ <div className="text-[10px] text-muted-foreground/70 leading-snug">
420
+ {hint.subtitle}
421
+ </div>
422
+ ) : null}
423
+ </div>
424
+ </div>
425
+ ))}
426
+ </div>
427
+ )
428
+ }