@pascal-app/editor 0.9.1 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (366) hide show
  1. package/package.json +14 -9
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +10 -2
  3. package/src/components/editor/bake-exporter.tsx +49 -0
  4. package/src/components/editor/bake-thumbnail.tsx +95 -0
  5. package/src/components/editor/camera-dragging-lifecycle.test.ts +49 -0
  6. package/src/components/editor/camera-dragging-lifecycle.ts +41 -0
  7. package/src/components/editor/custom-camera-controls.tsx +332 -199
  8. package/src/components/editor/delete-confirmation-dialog.tsx +54 -0
  9. package/src/components/editor/editor-layout-mobile.tsx +5 -0
  10. package/src/components/editor/editor-layout-v2.tsx +18 -0
  11. package/src/components/editor/elevation-3d-guide-layer.tsx +120 -0
  12. package/src/components/editor/export-manager.tsx +71 -63
  13. package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
  14. package/src/components/editor/first-person/build-collider-world.test.ts +102 -5
  15. package/src/components/editor/first-person/build-collider-world.ts +52 -5
  16. package/src/components/editor/first-person/terrain-collider.test.ts +132 -0
  17. package/src/components/editor/first-person/terrain-collider.ts +212 -0
  18. package/src/components/editor/first-person-controls.tsx +410 -136
  19. package/src/components/editor/floating-action-menu.tsx +435 -64
  20. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  21. package/src/components/editor/floorplan-background-selection.test.ts +72 -0
  22. package/src/components/editor/floorplan-background-selection.ts +7 -20
  23. package/src/components/editor/floorplan-camera-sync.test.ts +249 -0
  24. package/src/components/editor/floorplan-camera-sync.ts +213 -0
  25. package/src/components/editor/floorplan-mode-coordinator.tsx +82 -0
  26. package/src/components/editor/floorplan-navigation-presentation.test.ts +142 -0
  27. package/src/components/editor/floorplan-navigation-presentation.ts +156 -0
  28. package/src/components/editor/floorplan-panel.tsx +2517 -1492
  29. package/src/components/editor/grid.tsx +166 -39
  30. package/src/components/editor/group-actions.ts +597 -0
  31. package/src/components/editor/group-floating-action-menu.tsx +130 -0
  32. package/src/components/editor/group-move-3d.ts +397 -0
  33. package/src/components/editor/group-rotate-handle.tsx +102 -64
  34. package/src/components/editor/group-selection-box-3d.tsx +173 -0
  35. package/src/components/editor/group-transform-shared.test.ts +192 -1
  36. package/src/components/editor/group-transform-shared.ts +240 -7
  37. package/src/components/editor/handles/handle-arrow.tsx +81 -23
  38. package/src/components/editor/handles/handle-drag-history.test.ts +96 -0
  39. package/src/components/editor/handles/handle-drag-history.ts +14 -0
  40. package/src/components/editor/handles/preview-overrides.test.ts +26 -0
  41. package/src/components/editor/handles/preview-overrides.ts +13 -0
  42. package/src/components/editor/handles/resize-snap.test.ts +52 -0
  43. package/src/components/editor/handles/resize-snap.ts +23 -0
  44. package/src/components/editor/handles/use-handle-drag.ts +49 -6
  45. package/src/components/editor/index.tsx +289 -35
  46. package/src/components/editor/measurement-pill.tsx +68 -23
  47. package/src/components/editor/node-action-menu.tsx +14 -1
  48. package/src/components/editor/node-arrow-handles.tsx +409 -180
  49. package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
  50. package/src/components/editor/quick-measurement-card.tsx +76 -0
  51. package/src/components/editor/quick-measurement-hud.tsx +34 -0
  52. package/src/components/editor/riser-diagram-panel.tsx +137 -0
  53. package/src/components/editor/selection-manager.tsx +814 -466
  54. package/src/components/editor/site-edge-labels.tsx +28 -6
  55. package/src/components/editor/slab-hole-highlights.tsx +13 -5
  56. package/src/components/editor/snapshot-capture-overlay.tsx +258 -115
  57. package/src/components/editor/three-context-bridge.ts +22 -0
  58. package/src/components/editor/thumbnail-generator.tsx +118 -253
  59. package/src/components/editor/use-floorplan-background-placement.ts +113 -45
  60. package/src/components/editor/use-floorplan-hit-testing.test.ts +82 -0
  61. package/src/components/editor/use-floorplan-hit-testing.ts +144 -161
  62. package/src/components/editor/use-floorplan-scene-data.ts +5 -5
  63. package/src/components/editor/use-mesh-settle-epoch.ts +27 -0
  64. package/src/components/editor/wall-measurement-label.tsx +10 -28
  65. package/src/components/editor/wall-move-side-handles.tsx +229 -39
  66. package/src/components/editor/wall-opening-highlights.tsx +63 -32
  67. package/src/components/editor/wall-snap-beacon-layer.tsx +162 -6
  68. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +9 -5
  69. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +2 -1
  70. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +40 -9
  71. package/src/components/editor-2d/floorplan-cursor-indicator-position.test.ts +31 -0
  72. package/src/components/editor-2d/floorplan-cursor-indicator-position.ts +39 -0
  73. package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
  74. package/src/components/editor-2d/floorplan-group-move.tsx +744 -0
  75. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
  76. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1672 -0
  77. package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
  78. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +72 -0
  79. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +219 -20
  80. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +327 -59
  81. package/src/components/editor-2d/floorplan-render-context.test.ts +27 -0
  82. package/src/components/editor-2d/floorplan-render-context.tsx +113 -8
  83. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +5 -3
  84. package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +285 -0
  85. package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +516 -0
  86. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +236 -0
  87. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +618 -0
  88. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +369 -0
  89. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +431 -32
  90. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +89 -0
  91. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +106 -0
  92. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +94 -26
  93. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +587 -0
  94. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +2280 -578
  95. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +20 -10
  96. package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
  97. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +425 -61
  98. package/src/components/systems/ceiling/ceiling-system.tsx +97 -4
  99. package/src/components/systems/roof/roof-edit-system.tsx +1768 -13
  100. package/src/components/systems/selection-affordance-manager.tsx +38 -0
  101. package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
  102. package/src/components/systems/zone/zone-system.tsx +42 -13
  103. package/src/components/tools/elevator/elevator-tool.tsx +34 -8
  104. package/src/components/tools/elevator/move-elevator-tool.tsx +13 -2
  105. package/src/components/tools/fence/fence-drafting.ts +107 -8
  106. package/src/components/tools/item/move-tool.tsx +17 -13
  107. package/src/components/tools/item/placement-math.test.ts +13 -1
  108. package/src/components/tools/item/placement-math.ts +28 -2
  109. package/src/components/tools/item/placement-strategies.ts +246 -5
  110. package/src/components/tools/item/placement-types.ts +13 -1
  111. package/src/components/tools/item/use-draft-node.ts +146 -57
  112. package/src/components/tools/item/use-placement-coordinator.tsx +813 -125
  113. package/src/components/tools/registry/move-registry-node-tool.tsx +636 -103
  114. package/src/components/tools/roof/roof-tool.tsx +336 -94
  115. package/src/components/tools/select/box-select-state.ts +18 -7
  116. package/src/components/tools/select/box-select-tool.tsx +176 -40
  117. package/src/components/tools/select/marquee-geometry.test.ts +161 -0
  118. package/src/components/tools/select/marquee-geometry.ts +155 -0
  119. package/src/components/tools/select/plane-box-select-tool.tsx +1 -8
  120. package/src/components/tools/select/select-candidates.ts +1 -1
  121. package/src/components/tools/shared/cursor-sphere.tsx +62 -26
  122. package/src/components/tools/shared/drag-bounding-box.tsx +28 -4
  123. package/src/components/tools/shared/facing-indicator.tsx +87 -0
  124. package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
  125. package/src/components/tools/shared/floor-stack-preview.ts +4 -0
  126. package/src/components/tools/shared/horizontal-construction-plane.ts +123 -0
  127. package/src/components/tools/shared/placement-box.tsx +183 -1
  128. package/src/components/tools/shared/pointer-support-cap.test.ts +98 -0
  129. package/src/components/tools/shared/pointer-support-cap.ts +273 -0
  130. package/src/components/tools/shared/polygon-editor.tsx +276 -35
  131. package/src/components/tools/site/site-boundary-editor.tsx +101 -42
  132. package/src/components/tools/site/stroke-pointer-ownership.test.ts +91 -0
  133. package/src/components/tools/site/stroke-pointer-ownership.ts +57 -0
  134. package/src/components/tools/site/terrain-brush-cursor.tsx +247 -0
  135. package/src/components/tools/site/terrain-sculpt-grid.tsx +192 -0
  136. package/src/components/tools/site/terrain-sculpt-tool.tsx +356 -0
  137. package/src/components/tools/stair/stair-click-guard.test.ts +58 -0
  138. package/src/components/tools/stair/stair-click-guard.ts +72 -0
  139. package/src/components/tools/stair/stair-defaults.ts +1 -0
  140. package/src/components/tools/stair/stair-tool.tsx +164 -42
  141. package/src/components/tools/tool-manager.tsx +149 -30
  142. package/src/components/tools/wall/wall-drafting.test.ts +447 -0
  143. package/src/components/tools/wall/wall-drafting.ts +304 -108
  144. package/src/components/tools/wall/wall-snap-geometry.test.ts +52 -0
  145. package/src/components/tools/wall/wall-snap-geometry.ts +137 -9
  146. package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
  147. package/src/components/tools/zone/zone-tool.tsx +98 -88
  148. package/src/components/ui/action-menu/camera-actions.tsx +24 -17
  149. package/src/components/ui/action-menu/control-modes.tsx +44 -94
  150. package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
  151. package/src/components/ui/action-menu/index.tsx +2 -4
  152. package/src/components/ui/action-menu/measurement-control.tsx +267 -0
  153. package/src/components/ui/action-menu/structure-tools.tsx +22 -13
  154. package/src/components/ui/action-menu/view-toggles.tsx +42 -90
  155. package/src/components/ui/command-palette/editor-commands.tsx +16 -3
  156. package/src/components/ui/command-palette/index.tsx +4 -3
  157. package/src/components/ui/controls/material-paint-panel.tsx +93 -18
  158. package/src/components/ui/controls/material-picker.tsx +162 -152
  159. package/src/components/ui/controls/material-properties-editor.tsx +108 -0
  160. package/src/components/ui/controls/metric-control.tsx +118 -44
  161. package/src/components/ui/controls/scene-material-list.tsx +247 -0
  162. package/src/components/ui/controls/slider-control.tsx +88 -38
  163. package/src/components/ui/controls/terrain-sculpt-panel.tsx +193 -0
  164. package/src/components/ui/floating-level-selector.tsx +74 -20
  165. package/src/components/ui/helpers/building-helper.tsx +10 -23
  166. package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
  167. package/src/components/ui/helpers/helper-manager.tsx +273 -14
  168. package/src/components/ui/helpers/item-helper.tsx +28 -32
  169. package/src/components/ui/helpers/registered-tool-helper.tsx +45 -11
  170. package/src/components/ui/helpers/roof-helper.tsx +10 -12
  171. package/src/components/ui/icon-ref.tsx +47 -0
  172. package/src/components/ui/item-catalog/catalog-items.tsx +39 -1
  173. package/src/components/ui/item-catalog/item-catalog.tsx +13 -36
  174. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  175. package/src/components/ui/panels/multi-selection-panel.tsx +57 -0
  176. package/src/components/ui/panels/node-display.ts +17 -17
  177. package/src/components/ui/panels/panel-manager.tsx +36 -14
  178. package/src/components/ui/panels/panel-wrapper.tsx +24 -3
  179. package/src/components/ui/panels/parametric-inspector.tsx +19 -4
  180. package/src/components/ui/panels/reference-panel.tsx +54 -19
  181. package/src/components/ui/panels/selection-breakdown.test.ts +24 -0
  182. package/src/components/ui/panels/selection-breakdown.ts +20 -0
  183. package/src/components/ui/primitives/shortcut-token.tsx +39 -3
  184. package/src/components/ui/primitives/sidebar.tsx +1 -0
  185. package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
  186. package/src/components/ui/sidebar/icon-rail.tsx +50 -31
  187. package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
  188. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +82 -41
  189. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +76 -6
  190. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +12 -4
  191. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +2 -1
  192. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -3
  193. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +10 -7
  194. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
  195. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
  196. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +10 -7
  197. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -1
  198. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +1 -1
  199. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +10 -7
  200. package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -17
  201. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
  202. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
  203. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +72 -25
  204. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
  205. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +12 -8
  206. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +4 -3
  207. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +10 -7
  208. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
  209. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
  210. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +44 -3
  211. package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
  212. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
  213. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
  214. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
  215. package/src/components/ui/sidebar/tab-bar.tsx +42 -28
  216. package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
  217. package/src/components/ui/snap-target-badge.test.tsx +40 -0
  218. package/src/components/ui/snap-target-badge.tsx +88 -0
  219. package/src/components/viewer/viewer-controls-bar.tsx +466 -0
  220. package/src/components/viewer/viewer-scene-header.tsx +235 -0
  221. package/src/components/viewer-overlay.tsx +36 -596
  222. package/src/components/viewer-zone-system.tsx +6 -1
  223. package/src/components/walkthrough-hud.tsx +111 -0
  224. package/src/hooks/use-auto-save.test.ts +14 -0
  225. package/src/hooks/use-auto-save.ts +54 -7
  226. package/src/hooks/use-ceiling-events.ts +3 -2
  227. package/src/hooks/use-drag-action.ts +4 -1
  228. package/src/hooks/use-grid-events.ts +24 -0
  229. package/src/hooks/use-keyboard.ts +438 -70
  230. package/src/index.tsx +355 -9
  231. package/src/lib/active-placement-surface.test.ts +24 -0
  232. package/src/lib/active-placement-surface.ts +42 -0
  233. package/src/lib/camera-pose.test.ts +237 -0
  234. package/src/lib/camera-pose.ts +172 -0
  235. package/src/lib/ceiling-plan-snap.ts +24 -0
  236. package/src/lib/contextual-help.test.ts +112 -0
  237. package/src/lib/contextual-help.ts +144 -0
  238. package/src/lib/continuation.ts +64 -0
  239. package/src/lib/direct-manipulation.test.ts +275 -0
  240. package/src/lib/direct-manipulation.ts +118 -0
  241. package/src/lib/door-interaction.ts +1 -1
  242. package/src/lib/editor-api.ts +23 -35
  243. package/src/lib/elevation-guides.test.ts +107 -0
  244. package/src/lib/elevation-guides.ts +236 -0
  245. package/src/lib/floorplan/annotation-visibility.test.ts +327 -0
  246. package/src/lib/floorplan/annotation-visibility.ts +133 -0
  247. package/src/lib/floorplan/apply-alignment.test.ts +25 -0
  248. package/src/lib/floorplan/apply-alignment.ts +18 -12
  249. package/src/lib/floorplan/drawing-coordination.test.ts +46 -0
  250. package/src/lib/floorplan/drawing-coordination.ts +13 -0
  251. package/src/lib/floorplan/floorplan-export.test.ts +319 -0
  252. package/src/lib/floorplan/floorplan-export.tsx +978 -0
  253. package/src/lib/floorplan/floorplan-extension.test.ts +43 -0
  254. package/src/lib/floorplan/floorplan-extension.ts +181 -0
  255. package/src/lib/floorplan/floorplan-mode.test.ts +120 -0
  256. package/src/lib/floorplan/floorplan-mode.ts +73 -0
  257. package/src/lib/floorplan/floorplan-pdfkit-document.ts +194 -0
  258. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +107 -0
  259. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +680 -0
  260. package/src/lib/floorplan/geometry.ts +52 -0
  261. package/src/lib/floorplan/index.ts +3 -0
  262. package/src/lib/floorplan/items.ts +5 -2
  263. package/src/lib/floorplan/plan-coords.ts +21 -0
  264. package/src/lib/fresh-planar-placement.test.ts +283 -3
  265. package/src/lib/fresh-planar-placement.ts +85 -1
  266. package/src/lib/glb-export.test.ts +556 -0
  267. package/src/lib/glb-export.ts +1071 -0
  268. package/src/lib/ground-surface.ts +95 -0
  269. package/src/lib/history.test.ts +96 -0
  270. package/src/lib/history.ts +31 -0
  271. package/src/lib/interaction/hot-set.test.ts +133 -0
  272. package/src/lib/interaction/hot-set.ts +67 -0
  273. package/src/lib/interaction/overlay-policy.test.ts +52 -0
  274. package/src/lib/interaction/overlay-policy.ts +59 -0
  275. package/src/lib/interaction/scope.ts +243 -0
  276. package/src/lib/level-duplication.test.ts +2 -1
  277. package/src/lib/level-selection.ts +2 -2
  278. package/src/lib/material-paint.ts +36 -49
  279. package/src/lib/measurement-kind.test.ts +30 -0
  280. package/src/lib/measurement-kind.ts +19 -0
  281. package/src/lib/measurement-label.test.ts +47 -0
  282. package/src/lib/measurement-label.ts +68 -0
  283. package/src/lib/measurement-parser.ts +116 -0
  284. package/src/lib/measurements.test.ts +188 -0
  285. package/src/lib/measurements.ts +210 -0
  286. package/src/lib/paint-scope.test.ts +454 -0
  287. package/src/lib/paint-scope.ts +461 -0
  288. package/src/lib/placement-drag-release.ts +23 -0
  289. package/src/lib/planar-cursor-placement.test.ts +57 -0
  290. package/src/lib/plugin-panels.test.ts +19 -0
  291. package/src/lib/plugin-panels.ts +91 -0
  292. package/src/lib/quick-action-feedback.ts +33 -0
  293. package/src/lib/quick-action-nodes.test.ts +94 -0
  294. package/src/lib/quick-action-nodes.ts +47 -0
  295. package/src/lib/quick-measurement.test.ts +77 -0
  296. package/src/lib/quick-measurement.ts +109 -0
  297. package/src/lib/roof-duplication.ts +6 -6
  298. package/src/lib/roof-hover-outline-proxy.ts +22 -0
  299. package/src/lib/roof-wall-hit.ts +164 -0
  300. package/src/lib/scene-clipboard.test.ts +325 -0
  301. package/src/lib/scene-clipboard.ts +310 -21
  302. package/src/lib/scene.ts +28 -5
  303. package/src/lib/selection-routing.test.ts +313 -0
  304. package/src/lib/selection-routing.ts +180 -0
  305. package/src/lib/sfx/index.ts +1 -0
  306. package/src/lib/sfx/movement-tick.test.ts +65 -0
  307. package/src/lib/sfx/movement-tick.ts +18 -0
  308. package/src/lib/sfx-bus.ts +82 -14
  309. package/src/lib/sfx-player.test.ts +266 -0
  310. package/src/lib/sfx-player.ts +262 -69
  311. package/src/lib/site-boundary.test.ts +53 -0
  312. package/src/lib/site-boundary.ts +27 -0
  313. package/src/lib/slab-plan-snap.test.ts +93 -0
  314. package/src/lib/slab-plan-snap.ts +108 -0
  315. package/src/lib/snapping-mode.test.ts +165 -0
  316. package/src/lib/snapping-mode.ts +191 -0
  317. package/src/lib/stair-duplication.test.ts +103 -0
  318. package/src/lib/stair-duplication.ts +58 -86
  319. package/src/lib/stair-levels.ts +2 -0
  320. package/src/lib/surface-plan-snap.test.ts +71 -0
  321. package/src/lib/surface-plan-snap.ts +256 -0
  322. package/src/lib/terrain-sculpt.test.ts +429 -0
  323. package/src/lib/terrain-sculpt.ts +301 -0
  324. package/src/lib/terrain-verb-color.test.ts +51 -0
  325. package/src/lib/terrain-verb-color.ts +58 -0
  326. package/src/lib/touch-gesture-priority.test.ts +45 -0
  327. package/src/lib/touch-gesture-priority.ts +30 -0
  328. package/src/lib/use-linear-display.ts +40 -0
  329. package/src/lib/window-interaction.ts +1 -1
  330. package/src/lib/world-grid-snap.ts +37 -5
  331. package/src/store/camera-pose-store.test.ts +29 -0
  332. package/src/store/camera-pose-store.ts +27 -0
  333. package/src/store/live-draft-preview-stores.test.ts +136 -0
  334. package/src/store/navigation-sync-pose-store.test.ts +34 -0
  335. package/src/store/navigation-sync-pose-store.ts +27 -0
  336. package/src/store/terrain-sculpt-mode.test.ts +223 -0
  337. package/src/store/use-delete-confirmation.ts +27 -0
  338. package/src/store/use-direct-manipulation-feedback.ts +20 -0
  339. package/src/store/use-drawing-view.test.ts +23 -0
  340. package/src/store/use-drawing-view.ts +84 -0
  341. package/src/store/use-editor.tsx +665 -126
  342. package/src/store/use-elevation-guides.ts +40 -0
  343. package/src/store/use-facing-pose.ts +44 -0
  344. package/src/store/use-fence-curve-draft.ts +51 -0
  345. package/src/store/use-first-person-hud.ts +40 -0
  346. package/src/store/use-floorplan-annotation-visibility.ts +60 -0
  347. package/src/store/use-floorplan-draft-preview.ts +151 -0
  348. package/src/store/use-floorplan-marquee.ts +50 -0
  349. package/src/store/use-floorplan-mode.ts +110 -0
  350. package/src/store/use-interaction-scope.test.ts +206 -0
  351. package/src/store/use-interaction-scope.ts +140 -0
  352. package/src/store/use-measurement-draft.test.ts +559 -0
  353. package/src/store/use-measurement-draft.ts +570 -0
  354. package/src/store/use-opening-guides.ts +41 -0
  355. package/src/store/use-path-draft-preview.ts +90 -0
  356. package/src/store/use-placement-preview.ts +11 -3
  357. package/src/store/use-quick-measurement-hud.test.ts +53 -0
  358. package/src/store/use-quick-measurement-hud.ts +77 -0
  359. package/src/store/use-segment-draft-chain.ts +28 -0
  360. package/src/store/use-stair-build-preview.ts +53 -0
  361. package/src/store/use-wall-snap-indicator.ts +2 -0
  362. package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
  363. package/src/components/editor/group-move-handle.tsx +0 -316
  364. package/src/components/ui/panels/paint-panel.tsx +0 -163
  365. package/src/lib/floorplan/selection-tool.ts +0 -271
  366. package/src/lib/level-name.ts +0 -11
@@ -2,195 +2,205 @@
2
2
 
3
3
  import {
4
4
  getCatalogMaterialById,
5
+ getDynamicLibraryMaterials,
5
6
  getLibraryMaterialIdFromRef,
7
+ getLibraryMaterialsVersion,
6
8
  getMaterialsForCategory,
7
9
  MATERIAL_CATEGORIES,
8
- type MaterialSchema,
10
+ type MaterialCatalogItem,
11
+ type MaterialSource,
9
12
  type MaterialTarget,
13
+ subscribeLibraryMaterials,
10
14
  toLibraryMaterialRef,
11
15
  } from '@pascal-app/core'
12
- import { useEffect, useRef, useState } from 'react'
13
- import useEditor from '../../../store/use-editor'
16
+ import { Plus } from 'lucide-react'
17
+ import { useEffect, useMemo, useState, useSyncExternalStore } from 'react'
18
+ import { triggerSFX } from '../../../lib/sfx-bus'
14
19
 
15
- type MaterialPickerProps = {
16
- value?: MaterialSchema
20
+ export type MaterialSourceFilter = 'all' | MaterialSource
21
+
22
+ export type MaterialPickerProps = {
17
23
  selectedMaterialPreset?: string
18
- onChange?: (material: MaterialSchema) => void
19
24
  onSelectMaterialPreset?: (materialPreset: string) => void
20
25
  disabled?: boolean
21
26
  nodeType?: MaterialTarget
22
27
  hideSideControl?: boolean
28
+ onCreateMaterialRequest?: () => void
23
29
  }
24
30
 
31
+ const SOURCE_FILTERS: { id: MaterialSourceFilter; label: string }[] = [
32
+ { id: 'all', label: 'All' },
33
+ { id: 'pascal', label: 'Pascal' },
34
+ { id: 'mine', label: 'Mine' },
35
+ { id: 'workspace', label: 'Workspace' },
36
+ { id: 'community', label: 'Community' },
37
+ ]
38
+
25
39
  function getCategoryLabel(category: (typeof MATERIAL_CATEGORIES)[number]) {
26
- if (category === 'roof') return 'Roofing'
27
40
  return category.charAt(0).toUpperCase() + category.slice(1)
28
41
  }
29
42
 
43
+ function filterBySource(items: MaterialCatalogItem[], filter: MaterialSourceFilter) {
44
+ if (filter === 'all') return items
45
+ return items.filter((item) => (item.source ?? 'pascal') === filter)
46
+ }
47
+
48
+ /**
49
+ * Catalog material picker: a fixed row of category tabs and a source filter row
50
+ * over a scrollable grid of swatches. Scene-material creation lives in the
51
+ * scene-material section (the host's `+` action); `onCreateMaterialRequest` is
52
+ * the host's entry point for authoring a new *library* material.
53
+ */
30
54
  export function MaterialPicker({
31
- value,
32
55
  selectedMaterialPreset,
33
- onChange,
34
56
  onSelectMaterialPreset,
35
57
  disabled = false,
58
+ onCreateMaterialRequest,
36
59
  }: MaterialPickerProps) {
37
- const setPaintPanelOpen = useEditor((state) => state.setPaintPanelOpen)
38
- const [showCustom, setShowCustom] = useState<boolean>(!!value?.properties)
39
60
  const [selectedCategory, setSelectedCategory] = useState<(typeof MATERIAL_CATEGORIES)[number]>(
40
61
  MATERIAL_CATEGORIES[0],
41
62
  )
42
- const categoryScrollRef = useRef<HTMLDivElement>(null)
43
- const catalogItems =
44
- selectedCategory === 'other'
45
- ? getMaterialsForCategory('other')
46
- : getMaterialsForCategory(selectedCategory)
47
-
48
- useEffect(() => {
49
- setShowCustom(!!value?.properties && !selectedMaterialPreset)
50
- }, [selectedMaterialPreset, value?.properties])
63
+ const [sourceFilter, setSourceFilter] = useState<MaterialSourceFilter>('all')
64
+ // Version counter so host registrations/unregistrations re-render the picker.
65
+ const libraryVersion = useSyncExternalStore(
66
+ subscribeLibraryMaterials,
67
+ getLibraryMaterialsVersion,
68
+ getLibraryMaterialsVersion,
69
+ )
70
+ const hasWorkspaceMaterials = useMemo(
71
+ () => getDynamicLibraryMaterials().some((item) => item.source === 'workspace'),
72
+ [libraryVersion],
73
+ )
74
+ const visibleSourceFilters = SOURCE_FILTERS.filter(
75
+ (filter) => filter.id !== 'workspace' || hasWorkspaceMaterials,
76
+ )
77
+ const availableCategories = MATERIAL_CATEGORIES.filter(
78
+ (category) => getMaterialsForCategory(category).length > 0,
79
+ )
80
+ const catalogItems = filterBySource(getMaterialsForCategory(selectedCategory), sourceFilter)
51
81
 
82
+ // Keep the visible category in sync with the externally-selected catalog
83
+ // material (a `scene:` ref matches no catalog entry, so the tab stays put).
52
84
  useEffect(() => {
53
- if (!selectedMaterialPreset && value?.properties) {
54
- setSelectedCategory('other')
55
- return
56
- }
57
-
58
- const catalogId = getLibraryMaterialIdFromRef(selectedMaterialPreset) ?? value?.id ?? undefined
59
- const selectedCatalogEntry = getCatalogMaterialById(catalogId)
60
- if (selectedCatalogEntry?.category) {
61
- setSelectedCategory(selectedCatalogEntry.category)
62
- }
63
- }, [selectedMaterialPreset, value?.id])
64
-
65
- const selectedCatalogId =
66
- selectedMaterialPreset ?? (value?.id ? toLibraryMaterialRef(value.id) : undefined)
85
+ const catalogId = getLibraryMaterialIdFromRef(selectedMaterialPreset) ?? undefined
86
+ const entry = getCatalogMaterialById(catalogId)
87
+ if (entry?.category) setSelectedCategory(entry.category)
88
+ }, [selectedMaterialPreset])
67
89
 
68
90
  const handleCatalogSelect = (materialId: string) => {
69
91
  if (disabled) return
70
- setShowCustom(false)
71
- setPaintPanelOpen(false)
72
92
  onSelectMaterialPreset?.(toLibraryMaterialRef(materialId))
73
93
  }
74
94
 
75
- useEffect(() => {
76
- const container = categoryScrollRef.current
77
- if (!container) return
78
-
79
- const handleWheel = (event: WheelEvent) => {
80
- const deltaX = event.deltaX
81
- const deltaY = event.deltaY
82
- const nextScrollLeft = container.scrollLeft + deltaX + deltaY
83
-
84
- if (nextScrollLeft === container.scrollLeft) return
85
-
86
- event.preventDefault()
87
- container.scrollLeft = nextScrollLeft
88
- }
89
-
90
- container.addEventListener('wheel', handleWheel, { passive: false })
91
- return () => {
92
- container.removeEventListener('wheel', handleWheel)
93
- }
94
- }, [])
95
-
96
- const handleCustomOpen = () => {
97
- if (disabled) return
98
- setShowCustom(true)
99
- setPaintPanelOpen(true)
100
- onChange?.({
101
- preset: 'custom',
102
- properties: {
103
- color: value?.properties?.color || '#ffffff',
104
- roughness: value?.properties?.roughness ?? 0.5,
105
- metalness: value?.properties?.metalness ?? 0,
106
- opacity: value?.properties?.opacity ?? 1,
107
- transparent: value?.properties?.transparent ?? false,
108
- side: value?.properties?.side ?? 'front',
109
- },
110
- })
111
- }
112
-
113
95
  return (
114
- <div className={`min-w-0 space-y-3 ${disabled ? 'pointer-events-none opacity-50' : ''}`}>
115
- {(catalogItems.length > 0 || onChange) && (
116
- <div className="min-w-0 space-y-1">
117
- <div
118
- className="w-full max-w-full overflow-x-auto overflow-y-hidden"
119
- ref={categoryScrollRef}
120
- style={{ msOverflowStyle: 'none', scrollbarWidth: 'none' }}
96
+ <div
97
+ className={`flex h-full min-h-0 flex-col gap-2 ${disabled ? 'pointer-events-none opacity-50' : ''}`}
98
+ >
99
+ {/* Fixed category tabs — outside the scroll region. */}
100
+ <div className="flex shrink-0 flex-wrap gap-1">
101
+ {availableCategories.map((category) => (
102
+ <button
103
+ className={`rounded-full px-3 py-1 font-medium text-xs transition-colors ${
104
+ selectedCategory === category
105
+ ? 'bg-foreground text-background'
106
+ : 'text-muted-foreground hover:text-foreground'
107
+ }`}
108
+ key={category}
109
+ onClick={() => {
110
+ setSelectedCategory(category)
111
+ // Auto-select the first material in the category so the brush is
112
+ // immediately ready (and the swatch shows as selected).
113
+ const first = filterBySource(getMaterialsForCategory(category), sourceFilter)[0]
114
+ if (first) handleCatalogSelect(first.id)
115
+ }}
116
+ type="button"
121
117
  >
122
- <div className="flex min-w-max gap-1 pb-1">
123
- {MATERIAL_CATEGORIES.map((category) => (
124
- <button
125
- className={`shrink-0 px-2 font-medium text-[11px] uppercase tracking-[0.12em] transition-all ${
126
- selectedCategory === category
127
- ? 'bg-transparent text-foreground'
128
- : 'bg-transparent text-muted-foreground opacity-70 hover:text-foreground hover:opacity-100'
129
- }`}
130
- key={category}
131
- onClick={() => {
132
- setSelectedCategory(category)
133
- if (showCustom) {
134
- setShowCustom(false)
135
- }
136
- if (category !== 'other') {
137
- setPaintPanelOpen(false)
138
- }
139
- }}
140
- type="button"
141
- >
142
- {getCategoryLabel(category)}
143
- </button>
144
- ))}
145
- </div>
146
- </div>
147
- <div
148
- className="grid gap-1.5 pb-1"
149
- style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(72px, 1fr))' }}
118
+ {getCategoryLabel(category)}
119
+ </button>
120
+ ))}
121
+ </div>
122
+ {/* Fixed source filter tabs — underline style, matching the catalog
123
+ browse surfaces (Items / Rooms / Build / Search) rather than the
124
+ pill-button category row above. */}
125
+ <div className="flex shrink-0 items-center gap-4 px-1">
126
+ {visibleSourceFilters.map((filter) => (
127
+ <button
128
+ className={`-mb-px border-b-2 px-0.5 py-1.5 font-medium text-xs transition-colors ${
129
+ sourceFilter === filter.id
130
+ ? 'border-foreground text-foreground'
131
+ : 'border-transparent text-muted-foreground hover:text-foreground'
132
+ }`}
133
+ key={filter.id}
134
+ onClick={() => {
135
+ triggerSFX('sfx:menu-click')
136
+ setSourceFilter(filter.id)
137
+ }}
138
+ onMouseEnter={() => triggerSFX('sfx:menu-hover')}
139
+ type="button"
140
+ >
141
+ {filter.label}
142
+ </button>
143
+ ))}
144
+ </div>
145
+ {/* The only scrolling region. */}
146
+ <div
147
+ className="subtle-scrollbar grid min-h-0 flex-1 auto-rows-min gap-2 overflow-y-auto pb-1"
148
+ style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(72px, 1fr))' }}
149
+ >
150
+ {onCreateMaterialRequest ? (
151
+ <button
152
+ className="group relative flex flex-col gap-1.5 rounded-xl p-1.5 transition-colors hover:cursor-pointer hover:bg-sidebar-accent"
153
+ onClick={() => {
154
+ triggerSFX('sfx:menu-click')
155
+ onCreateMaterialRequest()
156
+ }}
157
+ onMouseEnter={() => triggerSFX('sfx:menu-hover')}
158
+ type="button"
150
159
  >
151
- {catalogItems.map((item) => (
152
- <button
153
- className={`relative aspect-square w-full overflow-hidden rounded-lg border transition-all ${
154
- selectedCatalogId === toLibraryMaterialRef(item.id)
155
- ? 'border-blue-500 ring-2 ring-blue-500/30'
156
- : 'border-gray-300 hover:border-gray-400'
157
- }`}
158
- key={item.id}
159
- onClick={() => handleCatalogSelect(item.id)}
160
- title={item.label}
161
- type="button"
162
- >
163
- <div className="pointer-events-none absolute inset-0 rounded-[inherit] ring-1 ring-white/12 ring-inset" />
164
- {item.previewThumbnailUrl ? (
165
- <img
166
- alt={item.label}
167
- className="h-full w-full object-cover"
168
- src={item.previewThumbnailUrl}
169
- />
170
- ) : item.previewColor ? (
171
- <div className="h-full w-full" style={{ backgroundColor: item.previewColor }} />
172
- ) : (
173
- <div className="h-full w-full bg-gray-100" />
174
- )}
175
- </button>
176
- ))}
177
- {selectedCategory === 'other' && onChange ? (
178
- <button
179
- className={`flex aspect-square w-full items-center justify-center rounded-lg border font-medium text-[10px] transition-all ${
180
- showCustom
181
- ? 'border-blue-500 ring-2 ring-blue-500/30'
182
- : 'border-gray-300 hover:border-gray-400'
183
- }`}
184
- onClick={handleCustomOpen}
185
- title="Custom"
186
- type="button"
187
- >
188
- Custom
189
- </button>
190
- ) : null}
191
- </div>
192
- </div>
193
- )}
160
+ <div className="flex aspect-square w-full items-center justify-center rounded-lg border border-border/45 border-dashed">
161
+ <Plus className="size-5 text-muted-foreground group-hover:text-foreground" />
162
+ </div>
163
+ <span className="truncate px-0.5 text-left font-medium text-[11px] text-muted-foreground group-hover:text-foreground">
164
+ New material
165
+ </span>
166
+ </button>
167
+ ) : null}
168
+ {catalogItems.map((item) => {
169
+ const isSelected = selectedMaterialPreset === toLibraryMaterialRef(item.id)
170
+ return (
171
+ <button
172
+ className={`group relative flex flex-col gap-1.5 rounded-xl p-1.5 transition-colors hover:cursor-pointer hover:bg-sidebar-accent ${
173
+ isSelected ? 'bg-sidebar-accent ring-1 ring-primary ring-inset' : ''
174
+ }`}
175
+ key={item.id}
176
+ onClick={() => {
177
+ triggerSFX('sfx:menu-click')
178
+ handleCatalogSelect(item.id)
179
+ }}
180
+ onMouseEnter={() => triggerSFX('sfx:menu-hover')}
181
+ type="button"
182
+ >
183
+ <div className="relative aspect-square w-full overflow-hidden rounded-lg">
184
+ {item.previewThumbnailUrl ? (
185
+ <img
186
+ alt={item.label}
187
+ className="h-full w-full object-cover"
188
+ src={item.previewThumbnailUrl}
189
+ />
190
+ ) : (
191
+ <div
192
+ className="h-full w-full"
193
+ style={{ backgroundColor: item.previewColor ?? '#f3f4f6' }}
194
+ />
195
+ )}
196
+ </div>
197
+ <span className="truncate px-0.5 text-left font-medium text-[11px] text-muted-foreground group-hover:text-foreground">
198
+ {item.label}
199
+ </span>
200
+ </button>
201
+ )
202
+ })}
203
+ </div>
194
204
  </div>
195
205
  )
196
206
  }
@@ -0,0 +1,108 @@
1
+ 'use client'
2
+
3
+ import type { MaterialProperties, MaterialSchema } from '@pascal-app/core'
4
+ import { Input } from '../primitives/input'
5
+ import { SliderControl } from './slider-control'
6
+
7
+ const DEFAULT_MATERIAL_PROPERTIES: MaterialProperties = {
8
+ color: '#ffffff',
9
+ roughness: 0.5,
10
+ metalness: 0,
11
+ opacity: 1,
12
+ transparent: false,
13
+ side: 'front',
14
+ }
15
+
16
+ export function MaterialPropertiesEditor({
17
+ value,
18
+ onChange,
19
+ }: {
20
+ value: MaterialSchema
21
+ onChange: (next: MaterialSchema) => void
22
+ }) {
23
+ const currentProps = value.properties ?? DEFAULT_MATERIAL_PROPERTIES
24
+
25
+ const updateMaterial = (
26
+ updates: Partial<MaterialProperties>,
27
+ nextTransparent = currentProps.transparent,
28
+ ) => {
29
+ onChange({
30
+ ...value,
31
+ preset: value.preset ?? 'custom',
32
+ properties: {
33
+ ...currentProps,
34
+ ...updates,
35
+ transparent: nextTransparent,
36
+ },
37
+ })
38
+ }
39
+
40
+ return (
41
+ <div className="space-y-3">
42
+ <div className="space-y-2">
43
+ <label className="block font-medium text-muted-foreground text-xs uppercase tracking-[0.12em]">
44
+ Color
45
+ </label>
46
+ <div className="flex items-center gap-2">
47
+ <input
48
+ className="h-9 w-9 shrink-0 cursor-pointer rounded-md border border-input bg-transparent p-0 [&::-moz-color-swatch]:rounded-[5px] [&::-moz-color-swatch]:border-none [&::-webkit-color-swatch-wrapper]:p-0 [&::-webkit-color-swatch]:rounded-[5px] [&::-webkit-color-swatch]:border-none"
49
+ onChange={(e) => updateMaterial({ color: e.target.value })}
50
+ type="color"
51
+ value={currentProps.color}
52
+ />
53
+ <Input
54
+ onChange={(e) => updateMaterial({ color: e.target.value })}
55
+ value={currentProps.color}
56
+ />
57
+ </div>
58
+ </div>
59
+
60
+ <SliderControl
61
+ label="Roughness"
62
+ max={1}
63
+ min={0}
64
+ onChange={(value) => updateMaterial({ roughness: value })}
65
+ precision={2}
66
+ step={0.01}
67
+ value={currentProps.roughness}
68
+ />
69
+
70
+ <SliderControl
71
+ label="Metalness"
72
+ max={1}
73
+ min={0}
74
+ onChange={(value) => updateMaterial({ metalness: value })}
75
+ precision={2}
76
+ step={0.01}
77
+ value={currentProps.metalness}
78
+ />
79
+
80
+ <SliderControl
81
+ label="Opacity"
82
+ max={1}
83
+ min={0}
84
+ onChange={(value) => updateMaterial({ opacity: value }, value < 1 || currentProps.transparent)}
85
+ precision={2}
86
+ step={0.01}
87
+ value={currentProps.opacity}
88
+ />
89
+
90
+ <div className="space-y-2">
91
+ <label className="block font-medium text-muted-foreground text-xs uppercase tracking-[0.12em]">
92
+ Side
93
+ </label>
94
+ <select
95
+ className="h-9 w-full rounded-md border border-input bg-transparent px-3 text-sm shadow-xs outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 dark:bg-input/30"
96
+ onChange={(e) =>
97
+ updateMaterial({ side: e.target.value as 'front' | 'back' | 'double' })
98
+ }
99
+ value={currentProps.side}
100
+ >
101
+ <option value="front">Front</option>
102
+ <option value="back">Back</option>
103
+ <option value="double">Double</option>
104
+ </select>
105
+ </div>
106
+ </div>
107
+ )
108
+ }