@pascal-app/editor 1.0.7 → 1.1.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 (336) hide show
  1. package/package.json +9 -15
  2. package/src/components/editor/action-menu-button.tsx +1 -1
  3. package/src/components/editor/alignment-3d-guide-layer.tsx +1 -1
  4. package/src/components/editor/custom-camera-controls.tsx +101 -45
  5. package/src/components/editor/duplicate-node.ts +2 -1
  6. package/src/components/editor/editor-layout-v2.tsx +1 -1
  7. package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
  8. package/src/components/editor/first-person-controls.tsx +20 -1983
  9. package/src/components/editor/fit-scene-framing.test.ts +318 -0
  10. package/src/components/editor/fit-scene-framing.ts +142 -0
  11. package/src/components/editor/floating-action-menu.tsx +5 -3
  12. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  13. package/src/components/editor/floor-openings-overlay.tsx +1 -1
  14. package/src/components/editor/floor-region-controls.tsx +8 -4
  15. package/src/components/editor/floorplan-navigation-presentation.test.ts +0 -40
  16. package/src/components/editor/floorplan-navigation-presentation.ts +0 -66
  17. package/src/components/editor/floorplan-panel.tsx +199 -30
  18. package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
  19. package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
  20. package/src/components/editor/follow-pascal.test.tsx +1 -2
  21. package/src/components/editor/follow-pascal.tsx +1 -1
  22. package/src/components/editor/footprint-height-handle.tsx +2 -2
  23. package/src/components/editor/group-actions.ts +1 -2
  24. package/src/components/editor/group-floating-action-menu.tsx +1 -1
  25. package/src/components/editor/group-move-3d.ts +1 -2
  26. package/src/components/editor/group-rotate-handle.tsx +1 -2
  27. package/src/components/editor/group-transform-shared.ts +3 -1
  28. package/src/components/editor/handles/use-handle-drag.ts +1 -2
  29. package/src/components/editor/index.tsx +43 -13
  30. package/src/components/editor/inspector-extension-actions.tsx +1 -1
  31. package/src/components/editor/measurement-pill.tsx +1 -1
  32. package/src/components/editor/node-arrow-handles.tsx +2 -2
  33. package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
  34. package/src/components/editor/open-wall-ends-hint.tsx +1 -1
  35. package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
  36. package/src/components/editor/quick-measurement-card.tsx +1 -1
  37. package/src/components/editor/room-controls.tsx +10 -6
  38. package/src/components/editor/room-handles-2d.tsx +3 -3
  39. package/src/components/editor/room-handles.tsx +4 -5
  40. package/src/components/editor/room-highlight.tsx +6 -105
  41. package/src/components/editor/room-labels.test.ts +8 -822
  42. package/src/components/editor/room-labels.tsx +41 -1670
  43. package/src/components/editor/room-webgpu-materials.test.ts +1 -1
  44. package/src/components/editor/selection-manager.tsx +5 -3
  45. package/src/components/editor/site-edge-labels.tsx +7 -3
  46. package/src/components/editor/snapshot-capture-overlay.tsx +2 -5
  47. package/src/components/editor/use-floorplan-background-placement.ts +2 -1
  48. package/src/components/editor/views/plugins-view.tsx +1 -1
  49. package/src/components/editor/views/view-bar.tsx +9 -9
  50. package/src/components/editor/wall-measurement-label.tsx +31 -15
  51. package/src/components/editor/wall-move-side-handles.tsx +11 -7
  52. package/src/components/editor/wall-pull-grip.tsx +2 -2
  53. package/src/components/editor/wall-region-handles.tsx +2 -2
  54. package/src/components/editor/wall-snap-beacon-layer.tsx +53 -32
  55. package/src/components/editor-2d/floorplan-group-move.tsx +3 -4
  56. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +13 -13
  57. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +2 -3
  58. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
  59. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +23 -6
  60. package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +0 -37
  61. package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +0 -30
  62. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +13 -3
  63. package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
  64. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +14 -3
  65. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
  66. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
  67. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +30 -10
  68. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +39 -35
  69. package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
  70. package/src/components/systems/roof/roof-edit-system.tsx +33 -21
  71. package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
  72. package/src/components/tools/distribution/distribution-run-tool.tsx +151 -33
  73. package/src/components/tools/item/move-tool.tsx +1 -6
  74. package/src/components/tools/item/placement-history.test.tsx +1 -1
  75. package/src/components/tools/item/use-placement-coordinator.tsx +3 -4
  76. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +7 -3
  77. package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
  78. package/src/components/tools/registry/move-registry-node-tool.tsx +39 -16
  79. package/src/components/tools/shared/cursor-sphere.tsx +1 -1
  80. package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
  81. package/src/components/tools/shared/placement-box.tsx +2 -6
  82. package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
  83. package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
  84. package/src/components/tools/shared/segment-angle.ts +0 -4
  85. package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
  86. package/src/components/tools/tool-manager.tsx +1 -24
  87. package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
  88. package/src/components/tools/wall/wall-typed-entry.ts +70 -0
  89. package/src/components/ui/action-menu/camera-actions.tsx +2 -2
  90. package/src/components/ui/action-menu/control-modes.tsx +1 -1
  91. package/src/components/ui/action-menu/follow-pascal-pill.tsx +1 -1
  92. package/src/components/ui/action-menu/index.tsx +3 -3
  93. package/src/components/ui/action-menu/measurement-control.tsx +3 -3
  94. package/src/components/ui/action-menu/placement.ts +0 -18
  95. package/src/components/ui/action-menu/point-ask-button.tsx +1 -1
  96. package/src/components/ui/action-menu/view-toggles.tsx +5 -5
  97. package/src/components/ui/controls/finish-swatch.tsx +1 -1
  98. package/src/components/ui/controls/material-paint-panel.tsx +2 -2
  99. package/src/components/ui/controls/scene-material-list.tsx +2 -2
  100. package/src/components/ui/controls/select-control.tsx +1 -1
  101. package/src/components/ui/controls/slider-control.tsx +18 -6
  102. package/src/components/ui/controls/terrain-sculpt-panel.tsx +1 -1
  103. package/src/components/ui/controls/tool-options-panel.tsx +74 -44
  104. package/src/components/ui/floating-level-selector.tsx +3 -3
  105. package/src/components/ui/helpers/contextual-helper-panel.tsx +1 -1
  106. package/src/components/ui/helpers/helper-manager.test.tsx +54 -1
  107. package/src/components/ui/helpers/helper-manager.tsx +55 -1
  108. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  109. package/src/components/ui/panels/collections/collections-popover.tsx +2 -2
  110. package/src/components/ui/panels/floor-foundation-panel.tsx +3 -3
  111. package/src/components/ui/panels/floor-opening-panel.tsx +5 -5
  112. package/src/components/ui/panels/mobile-selection-bar.tsx +28 -24
  113. package/src/components/ui/panels/multi-height-mode.tsx +3 -3
  114. package/src/components/ui/panels/multi-selection-panel.tsx +1 -1
  115. package/src/components/ui/panels/node-display.ts +1 -1
  116. package/src/components/ui/panels/panel-manager.tsx +5 -3
  117. package/src/components/ui/panels/panel-wrapper.tsx +42 -19
  118. package/src/components/ui/panels/parametric-inspector.tsx +1 -1
  119. package/src/components/ui/panels/room-construction-rows.tsx +7 -7
  120. package/src/components/ui/panels/room-name-options.ts +1 -5
  121. package/src/components/ui/panels/room-openings-field.tsx +1 -1
  122. package/src/components/ui/panels/room-panel.tsx +7 -9
  123. package/src/components/ui/primitives/color-dot.tsx +2 -2
  124. package/src/components/ui/primitives/opacity-control.tsx +2 -2
  125. package/src/components/ui/primitives/sidebar.tsx +3 -3
  126. package/src/components/ui/right-stack.test.ts +13 -6
  127. package/src/components/ui/right-stack.tsx +22 -11
  128. package/src/components/ui/sidebar/icon-rail.tsx +1 -1
  129. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  130. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
  131. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +1 -1
  132. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +2 -2
  133. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +1 -1
  134. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +2 -6
  135. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
  136. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +1 -1
  137. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +1 -1
  138. package/src/components/ui/sidebar/panels/site-panel/index.tsx +3 -10
  139. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +1 -1
  140. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
  141. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +46 -7
  142. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +1 -1
  143. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +1 -1
  144. package/src/components/ui/sidebar/panels/site-panel/tree-node-drag.tsx +14 -5
  145. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -2
  146. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +1 -1
  147. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +3 -3
  148. package/src/components/ui/sidebar/tab-bar.tsx +1 -1
  149. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +2 -2
  150. package/src/components/ui/wall-type/wall-type-menu.tsx +1 -1
  151. package/src/components/viewer-overlay.tsx +1 -2
  152. package/src/hooks/use-auto-frame.ts +1 -2
  153. package/src/hooks/use-keyboard.ts +1 -2
  154. package/src/hooks/use-selected-room.ts +5 -7
  155. package/src/i18n/floor-footprint-name.ts +1 -1
  156. package/src/i18n/locales/en/messages.po +1036 -306
  157. package/src/i18n/locales/en/messages.ts +1 -1
  158. package/src/i18n/locales/en-XA/messages.ts +1 -1
  159. package/src/i18n/locales/es/messages.po +2267 -439
  160. package/src/i18n/locales/fr/messages.po +2267 -439
  161. package/src/i18n/locales/ja/messages.po +2267 -439
  162. package/src/i18n/locales/zh-CN/messages.po +2267 -439
  163. package/src/i18n/locales.test.ts +1 -1
  164. package/src/index.tsx +183 -178
  165. package/src/lib/below-grade.test.tsx +4 -4
  166. package/src/lib/camera-facing.test.ts +1 -1
  167. package/src/lib/camera-facing.ts +2 -3
  168. package/src/lib/contextual-help.ts +6 -0
  169. package/src/lib/editor-level-display.ts +1 -1
  170. package/src/lib/elevation-guides.ts +21 -6
  171. package/src/lib/elevator-support.ts +1 -72
  172. package/src/lib/floor-footprints.test.ts +3 -3
  173. package/src/lib/floor-footprints.ts +1 -9
  174. package/src/lib/floor-foundation-support.test.ts +8 -3
  175. package/src/lib/floor-opening-draft.ts +2 -2
  176. package/src/lib/floor-region-session.ts +1 -1
  177. package/src/lib/floorplan/annotation-visibility.test.ts +1 -1
  178. package/src/lib/floorplan/annotation-visibility.ts +1 -1
  179. package/src/lib/floorplan/floorplan-export.test.ts +1 -1
  180. package/src/lib/floorplan/floorplan-export.tsx +8 -11
  181. package/src/lib/floorplan/floorplan-extension.ts +4 -157
  182. package/src/lib/floorplan/floorplan-mode.ts +2 -1
  183. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +1 -1
  184. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +8 -6
  185. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +3 -4
  186. package/src/lib/floorplan/geometry.ts +2 -112
  187. package/src/lib/floorplan/index.ts +0 -9
  188. package/src/lib/floorplan/items.ts +2 -1
  189. package/src/lib/floorplan/open-wall-ends.ts +2 -1
  190. package/src/lib/floorplan/schedules.ts +6 -3
  191. package/src/lib/floorplan/sheet-export.ts +7 -3
  192. package/src/lib/floorplan/site-plan/site-parts.ts +7 -1
  193. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +1 -1
  194. package/src/lib/follow-pascal-presentation.ts +1 -1
  195. package/src/lib/gesture-lifecycle.ts +3 -1
  196. package/src/lib/glb-export.ts +1 -1
  197. package/src/lib/interaction/scope.ts +1 -0
  198. package/src/lib/layer-thickness.ts +1 -1
  199. package/src/lib/level-selection.test.ts +18 -2
  200. package/src/lib/level-selection.ts +31 -8
  201. package/src/lib/linear-display.ts +1 -1
  202. package/src/lib/live-gesture-round-trip.test.ts +1 -1
  203. package/src/lib/material-paint.ts +1 -1
  204. package/src/lib/measurement-label.test.ts +1 -1
  205. package/src/lib/measurements.test.ts +7 -5
  206. package/src/lib/mezzanine-selection.test.ts +1 -1
  207. package/src/lib/node-action-movement.test.tsx +115 -0
  208. package/src/lib/node-action-movement.ts +2 -0
  209. package/src/lib/paint-region-mode.ts +1 -1
  210. package/src/lib/placement-metadata.ts +4 -0
  211. package/src/lib/point-ask/describe-target.ts +6 -8
  212. package/src/lib/point-ask/region.ts +1 -1
  213. package/src/lib/point-ask/session.ts +1 -2
  214. package/src/lib/print-shell-compiler.ts +12 -1
  215. package/src/lib/print-wall-solids.test.ts +11 -0
  216. package/src/lib/print-wall-solids.ts +9 -0
  217. package/src/lib/roof-duplication.test.ts +100 -0
  218. package/src/lib/roof-duplication.ts +30 -5
  219. package/src/lib/room-built-on.test.ts +2 -5
  220. package/src/lib/room-built-on.ts +1 -15
  221. package/src/lib/room-construction-commands.ts +3 -2
  222. package/src/lib/room-controls-phase6.test.ts +1 -1
  223. package/src/lib/room-handle-drag.ts +8 -37
  224. package/src/lib/room-name-catalog.test.ts +2 -1
  225. package/src/lib/room-name-messages.test.ts +2 -1
  226. package/src/lib/room-name-messages.ts +1 -118
  227. package/src/lib/room-name-storage.test.ts +2 -1
  228. package/src/lib/room-push-dimensions.test.ts +1 -1
  229. package/src/lib/room-selection-commands.ts +2 -2
  230. package/src/lib/room-selection.test.ts +6 -6
  231. package/src/lib/room-transform-session.ts +1 -2
  232. package/src/lib/room-zone-routing.ts +2 -2
  233. package/src/lib/scene-clipboard.ts +2 -1
  234. package/src/lib/selection-routing.test.ts +0 -28
  235. package/src/lib/selection-routing.ts +1 -40
  236. package/src/lib/sfx-bus.ts +8 -32
  237. package/src/lib/sfx-player.test.ts +72 -1
  238. package/src/lib/sfx-player.ts +49 -337
  239. package/src/lib/snapping-mode.test.ts +23 -1
  240. package/src/lib/viewer-selection.test.ts +38 -10
  241. package/src/lib/wall-pull.test.ts +1 -1
  242. package/src/lib/wall-pull.ts +1 -27
  243. package/src/lib/wall-region-list.ts +3 -1
  244. package/src/lib/window-interaction.test.ts +4 -2
  245. package/src/store/use-editor.tsx +22 -26
  246. package/src/store/use-floorplan-annotation-visibility.ts +5 -5
  247. package/src/store/use-placement-typing.test.ts +165 -0
  248. package/src/store/use-placement-typing.ts +186 -0
  249. package/src/store/use-wall-region-handles-selection.ts +1 -2
  250. package/src/components/editor/__bench__/room-label-placement.bench.ts +0 -108
  251. package/src/components/editor/first-person/build-collider-world.test.ts +0 -596
  252. package/src/components/editor/first-person/build-collider-world.ts +0 -549
  253. package/src/components/editor/first-person/ground-holes.test.ts +0 -128
  254. package/src/components/editor/first-person/ground-holes.ts +0 -130
  255. package/src/components/editor/first-person/mechanism-targets.test.ts +0 -162
  256. package/src/components/editor/first-person/mechanism-targets.ts +0 -88
  257. package/src/components/editor/first-person/terrain-collider.test.ts +0 -132
  258. package/src/components/editor/first-person/terrain-collider.ts +0 -212
  259. package/src/components/editor/floorplan-camera-sync.test.ts +0 -249
  260. package/src/components/editor/floorplan-camera-sync.ts +0 -213
  261. package/src/components/editor/localized-html.tsx +0 -22
  262. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +0 -236
  263. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +0 -708
  264. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +0 -385
  265. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +0 -670
  266. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +0 -89
  267. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +0 -106
  268. package/src/components/editor-2d/svg-paths.ts +0 -124
  269. package/src/components/tools/elevator/elevator-defaults.ts +0 -8
  270. package/src/components/tools/elevator/elevator-tool.tsx +0 -372
  271. package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
  272. package/src/components/ui/action-menu/action-button.tsx +0 -89
  273. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +0 -42
  274. package/src/components/ui/primitives/button.tsx +0 -69
  275. package/src/components/ui/primitives/dropdown-menu.tsx +0 -232
  276. package/src/components/ui/primitives/popover.tsx +0 -42
  277. package/src/components/ui/primitives/switch.tsx +0 -29
  278. package/src/components/ui/primitives/tooltip.tsx +0 -57
  279. package/src/components/ui/scene-loader.tsx +0 -73
  280. package/src/components/viewer/floorplan-compass-button.tsx +0 -67
  281. package/src/components/viewer/floorplan-preview-geometry.test.ts +0 -107
  282. package/src/components/viewer/floorplan-preview-geometry.ts +0 -281
  283. package/src/components/viewer/floorplan-preview-navigation.test.ts +0 -43
  284. package/src/components/viewer/floorplan-preview-navigation.ts +0 -62
  285. package/src/components/viewer/floorplan-preview-visibility.test.ts +0 -35
  286. package/src/components/viewer/floorplan-preview-visibility.ts +0 -19
  287. package/src/components/viewer/floorplan-preview.tsx +0 -1065
  288. package/src/components/viewer/render-settings-menu.tsx +0 -294
  289. package/src/components/viewer/sky-control.tsx +0 -710
  290. package/src/components/viewer/use-viewer-camera-navigation-sync.ts +0 -140
  291. package/src/components/viewer/use-viewer-ui-sounds.test.ts +0 -15
  292. package/src/components/viewer/use-viewer-ui-sounds.ts +0 -46
  293. package/src/components/viewer/viewer-controls-bar.tsx +0 -496
  294. package/src/components/viewer/viewer-hover-label.tsx +0 -42
  295. package/src/components/viewer/viewer-rooms.tsx +0 -248
  296. package/src/components/viewer/viewer-scene-header.tsx +0 -286
  297. package/src/components/viewer/viewer-selection-manager.tsx +0 -124
  298. package/src/components/viewer/viewer-stage-modes.test.ts +0 -38
  299. package/src/components/viewer/viewer-stage-modes.ts +0 -49
  300. package/src/components/viewer/viewer-stage-switcher.tsx +0 -97
  301. package/src/components/viewer/viewer-stage.test.tsx +0 -47
  302. package/src/components/viewer/viewer-stage.tsx +0 -221
  303. package/src/components/walkthrough-hud.tsx +0 -193
  304. package/src/hooks/use-mobile.ts +0 -19
  305. package/src/i18n/level-name.ts +0 -39
  306. package/src/i18n/scene-theme-name.ts +0 -16
  307. package/src/lib/below-grade.ts +0 -133
  308. package/src/lib/camera-pose.test.ts +0 -237
  309. package/src/lib/camera-pose.ts +0 -172
  310. package/src/lib/door-interaction.ts +0 -102
  311. package/src/lib/floorplan/floorplan-extension.test.ts +0 -42
  312. package/src/lib/floorplan/floorplan-readonly.test.ts +0 -47
  313. package/src/lib/floorplan/floorplan-readonly.ts +0 -95
  314. package/src/lib/floorplan/marks.test.ts +0 -213
  315. package/src/lib/floorplan/marks.ts +0 -238
  316. package/src/lib/measurement-label.ts +0 -68
  317. package/src/lib/measurements.ts +0 -225
  318. package/src/lib/node-display-label.ts +0 -37
  319. package/src/lib/room-assembly-overlay.test.ts +0 -275
  320. package/src/lib/room-assembly-overlay.ts +0 -265
  321. package/src/lib/room-selection.ts +0 -417
  322. package/src/lib/scene-bounds.test.ts +0 -183
  323. package/src/lib/scene-bounds.ts +0 -169
  324. package/src/lib/typing-target.ts +0 -9
  325. package/src/lib/viewer-selection.ts +0 -595
  326. package/src/lib/window-interaction.ts +0 -142
  327. package/src/lib/zone-content.ts +0 -133
  328. package/src/store/camera-pose-store.test.ts +0 -29
  329. package/src/store/camera-pose-store.ts +0 -27
  330. package/src/store/navigation-sync-pose-store.test.ts +0 -34
  331. package/src/store/navigation-sync-pose-store.ts +0 -27
  332. package/src/store/use-audio.test.ts +0 -146
  333. package/src/store/use-audio.tsx +0 -169
  334. package/src/store/use-first-person-hud.ts +0 -40
  335. package/src/store/use-follow-pascal.test.ts +0 -62
  336. package/src/store/use-follow-pascal.ts +0 -53
@@ -35,11 +35,11 @@ import {
35
35
  useWallsBelowSetting,
36
36
  } from '../../editor-2d/floorplan-adjacent-walls-layer'
37
37
  import { SliderControl } from '../controls/slider-control'
38
- import { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover'
39
- import { Switch } from '../primitives/switch'
40
- import { ActionButton } from './action-button'
41
- import { useActionMenuPopupSide } from './placement'
42
- import { localizedLevelName } from '../../../i18n/level-name'
38
+ import { Popover, PopoverContent, PopoverTrigger } from '@pascal-app/viewer/ui'
39
+ import { Switch } from '@pascal-app/viewer/ui'
40
+ import { ActionButton } from '@pascal-app/viewer/ui'
41
+ import { useActionMenuPopupSide } from '@pascal-app/viewer/ui'
42
+ import { localizedLevelName } from '@pascal-app/viewer/ui'
43
43
 
44
44
  const MAX_FILE_SIZE = 200 * 1024 * 1024 // 200MB
45
45
  const ACCEPTED_FILE_TYPES = '.glb,.gltf,image/jpeg,image/png,image/webp,image/gif'
@@ -13,7 +13,7 @@ import {
13
13
  } from '@pascal-app/core'
14
14
  import { useState } from 'react'
15
15
  import { materialSwatchStyle } from '../../../lib/material-swatch-style'
16
- import { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover'
16
+ import { Popover, PopoverContent, PopoverTrigger } from '@pascal-app/viewer/ui'
17
17
  import { MaterialPicker } from './material-picker'
18
18
 
19
19
  type Swatch = { label: string; color: string | null; image: string | null; tinted?: boolean }
@@ -6,8 +6,8 @@ import { useState } from 'react'
6
6
  import { useMaterialPaintPanelModel } from '../../../lib/material-paint-panel-model'
7
7
  import { type PaintMode, usePaintRegionMode } from '../../../lib/paint-region-mode'
8
8
  import useEditor, { armMaterialPaint } from '../../../store/use-editor'
9
- import { Button } from '../primitives/button'
10
- import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip'
9
+ import { Button } from '@pascal-app/viewer/ui'
10
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@pascal-app/viewer/ui'
11
11
  import { MaterialPicker } from './material-picker'
12
12
  import { SceneMaterialList } from './scene-material-list'
13
13
  import { SegmentedControl } from './segmented-control'
@@ -12,9 +12,9 @@ import { Copy, Paintbrush, Pencil, Trash2 } from 'lucide-react'
12
12
  import { useEffect, useMemo, useState } from 'react'
13
13
  import { sceneMaterialUsageCounts } from '../../../lib/scene-material-refs'
14
14
  import useEditor from '../../../store/use-editor'
15
- import { Button } from '../primitives/button'
15
+ import { Button } from '@pascal-app/viewer/ui'
16
16
  import { Input } from '../primitives/input'
17
- import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip'
17
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@pascal-app/viewer/ui'
18
18
  import { MaterialPropertiesEditor } from './material-properties-editor'
19
19
 
20
20
  export function SceneMaterialList({ autoEditId }: { autoEditId?: SceneMaterialId | null }) {
@@ -8,7 +8,7 @@ import {
8
8
  DropdownMenuRadioGroup,
9
9
  DropdownMenuRadioItem,
10
10
  DropdownMenuTrigger,
11
- } from '../primitives/dropdown-menu'
11
+ } from '@pascal-app/viewer/ui'
12
12
 
13
13
  interface SelectControlProps<T extends string> {
14
14
  label: string
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { formatNumber, parseLocalizedNumber, useLocale, useScene } from '@pascal-app/core'
3
+ import { formatNumber, parseLocalizedNumber, useLocale, useScene, usePascalI18n } from '@pascal-app/core'
4
4
  import { useCallback, useEffect, useRef, useState } from 'react'
5
5
  import { lingoUnitSpec, measurementHint, parseMeasurement } from '../../../lib/measurement-parser'
6
6
  import { useLinearDisplay } from '../../../lib/use-linear-display'
@@ -71,6 +71,7 @@ export function SliderControl({
71
71
  restoreOnCommit = true,
72
72
  mixed = false,
73
73
  }: SliderControlProps) {
74
+ const i18n = usePascalI18n()
74
75
  const locale = useLocale()
75
76
  // Values and bounds stay in meters; gestures and input use the displayed unit.
76
77
  const { isImperial, displayUnit, parseUnit, precision, step, toDisplay, toStored } =
@@ -260,6 +261,12 @@ export function SliderControl({
260
261
  [],
261
262
  )
262
263
 
264
+ useEffect(() => {
265
+ const cancelOnBlur = () => { if (dragRef.current || editingRef.current) cancelEdit() }
266
+ window.addEventListener('blur', cancelOnBlur)
267
+ return () => window.removeEventListener('blur', cancelOnBlur)
268
+ }, [cancelEdit])
269
+
263
270
  useEffect(() => {
264
271
  if (!isDragging) return
265
272
  const escape = (event: KeyboardEvent) => {
@@ -392,6 +399,7 @@ export function SliderControl({
392
399
  </span>
393
400
  )}
394
401
  <input
402
+ aria-label={typeof label === 'string' ? label : undefined}
395
403
  autoFocus
396
404
  className="w-14 bg-transparent p-0 text-right font-mono text-foreground outline-none selection:bg-primary/30"
397
405
  onBlur={submitValue}
@@ -418,14 +426,18 @@ export function SliderControl({
418
426
  )}
419
427
  </>
420
428
  ) : mixed && !isDragging ? (
421
- <div
429
+ <button
430
+ type="button"
431
+ aria-label={typeof label === 'string' ? i18n._({ id: 'editor.controls.sliderControl.editLabel', message: 'Edit {label}', values: { label } }) : undefined}
422
432
  className="flex cursor-text items-center text-muted-foreground transition-colors hover:text-foreground"
423
433
  onClick={handleValueClick}
424
434
  >
425
- <span className="font-mono tracking-tight">Mixed</span>
426
- </div>
435
+ <span className="font-mono tracking-tight">{i18n._({ id: 'editor.controls.sliderControl.mixed', message: 'Mixed' })}</span>
436
+ </button>
427
437
  ) : (
428
- <div
438
+ <button
439
+ type="button"
440
+ aria-label={typeof label === 'string' ? i18n._({ id: 'editor.controls.sliderControl.editLabel', message: 'Edit {label}', values: { label } }) : undefined}
429
441
  className="flex cursor-text items-center text-foreground/60 transition-colors hover:text-foreground"
430
442
  onClick={handleValueClick}
431
443
  >
@@ -433,7 +445,7 @@ export function SliderControl({
433
445
  {formatDisplay(displayValue)}
434
446
  </span>
435
447
  {displayUnit && <span className="ml-[1px] text-muted-foreground">{displayUnit}</span>}
436
- </div>
448
+ </button>
437
449
  )}
438
450
  </div>
439
451
  </div>
@@ -10,7 +10,7 @@ import {
10
10
  import { Mountain, Pipette } from 'lucide-react'
11
11
  import { brushRadiusRange, flattenSite, resetSiteTerrain } from '../../../lib/terrain-sculpt'
12
12
  import useEditor from '../../../store/use-editor'
13
- import { Button } from '../primitives/button'
13
+ import { Button } from '@pascal-app/viewer/ui'
14
14
  import { SegmentedControl } from './segmented-control'
15
15
  import { SliderControl } from './slider-control'
16
16
 
@@ -34,50 +34,80 @@ function ToolOptionRow({
34
34
  return (
35
35
  <div className="flex flex-col gap-2">
36
36
  <div className="px-0.5 font-medium text-muted-foreground text-xs">{option.label}</div>
37
- <div
38
- className="grid gap-1.5"
39
- style={{
40
- gridTemplateColumns: `repeat(${Math.min(option.choices.length, 3)}, minmax(0, 1fr))`,
41
- }}
42
- >
43
- {option.choices.map((choice) => {
44
- const selected = active && choice.value === value
45
- const thumbnail = getChoiceThumbnail?.(option, choice.value)
46
- return (
47
- <button
48
- aria-pressed={selected}
49
- className={cn(
50
- 'rounded-lg px-2 py-2 text-center font-medium text-xs transition-colors',
51
- selected
52
- ? 'bg-primary/10 text-primary ring-1 ring-primary/50'
53
- : 'bg-black/4 text-muted-foreground hover:bg-black/8 hover:text-foreground dark:bg-muted/40 dark:hover:bg-muted',
54
- )}
55
- key={choice.value}
56
- onClick={() => {
57
- triggerSFX('sfx:menu-click')
58
- option.set(choice.value)
59
- onSelect?.(option, choice.value)
60
- }}
61
- onMouseEnter={() => triggerSFX('sfx:menu-hover')}
62
- type="button"
63
- >
64
- {thumbnail && (
65
- <Image
66
- alt=""
67
- className={cn(
68
- 'mx-auto mb-1 size-14 object-contain',
69
- !selected && 'opacity-70 grayscale',
70
- )}
71
- height={56}
72
- src={thumbnail}
73
- width={56}
74
- />
75
- )}
76
- {choice.label}
77
- </button>
78
- )
79
- })}
80
- </div>
37
+ {option.input ? (
38
+ <label className="flex items-center gap-2 rounded-lg border border-border px-2 py-1.5">
39
+ <input
40
+ key={value}
41
+ aria-label={option.label}
42
+ className="min-w-0 flex-1 bg-transparent text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring"
43
+ type="number"
44
+ defaultValue={value}
45
+ min={option.input.min}
46
+ max={option.input.max}
47
+ step={option.input.step ?? 'any'}
48
+ onBlur={(event) => {
49
+ const next = event.target.valueAsNumber
50
+ if (
51
+ Number.isFinite(next) &&
52
+ !event.target.validity.rangeUnderflow &&
53
+ !event.target.validity.rangeOverflow
54
+ ) {
55
+ option.set(String(next))
56
+ onSelect?.(option, String(next))
57
+ } else event.target.value = value
58
+ }}
59
+ onKeyDown={(event) => {
60
+ if (event.key === 'Enter') event.currentTarget.blur()
61
+ }}
62
+ />
63
+ <span className="text-xs text-muted-foreground">{option.input.unit}</span>
64
+ </label>
65
+ ) : (
66
+ <div
67
+ className="grid gap-1.5"
68
+ style={{
69
+ gridTemplateColumns: `repeat(${Math.min(option.choices.length, 3)}, minmax(0, 1fr))`,
70
+ }}
71
+ >
72
+ {option.choices.map((choice) => {
73
+ const selected = active && choice.value === value
74
+ const thumbnail = getChoiceThumbnail?.(option, choice.value)
75
+ return (
76
+ <button
77
+ aria-pressed={selected}
78
+ className={cn(
79
+ 'rounded-lg px-2 py-2 text-center font-medium text-xs transition-colors',
80
+ selected
81
+ ? 'bg-primary/10 text-primary ring-1 ring-primary/50'
82
+ : 'bg-black/4 text-muted-foreground hover:bg-black/8 hover:text-foreground dark:bg-muted/40 dark:hover:bg-muted',
83
+ )}
84
+ key={choice.value}
85
+ onClick={() => {
86
+ triggerSFX('sfx:menu-click')
87
+ option.set(choice.value)
88
+ onSelect?.(option, choice.value)
89
+ }}
90
+ onMouseEnter={() => triggerSFX('sfx:menu-hover')}
91
+ type="button"
92
+ >
93
+ {thumbnail && (
94
+ <Image
95
+ alt=""
96
+ className={cn(
97
+ 'mx-auto mb-1 size-14 object-contain',
98
+ !selected && 'opacity-70 grayscale',
99
+ )}
100
+ height={56}
101
+ src={thumbnail}
102
+ width={56}
103
+ />
104
+ )}
105
+ {choice.label}
106
+ </button>
107
+ )
108
+ })}
109
+ </div>
110
+ )}
81
111
  {activeChoice && activeChoice.description ? (
82
112
  <p className="px-0.5 text-[11px] text-muted-foreground leading-relaxed">
83
113
  {activeChoice.description}
@@ -61,14 +61,14 @@ import {
61
61
  useLocale,
62
62
  usePascalI18n,
63
63
  } from '@pascal-app/core'
64
- import { authoredLevelName, localizedLevelName } from '../../i18n/level-name'
64
+ import { authoredLevelName, localizedLevelName } from '@pascal-app/viewer/ui'
65
65
  import { messageWithSlot } from '../../i18n/message-slot'
66
66
  import { addStoreyBelow, addStoreyOnTop, setGroundFloor } from '../../lib/add-storey'
67
67
  import { deleteLevelWithFallbackSelection } from '../../lib/level-selection'
68
68
  import { unitMemberLevels, leaveUnitFocus } from '../../lib/units'
69
69
  import { useLinearDisplay } from '../../lib/use-linear-display'
70
70
  import { cn } from '../../lib/utils'
71
- import { useIsMobile } from '../../hooks/use-mobile'
71
+ import { useIsMobile } from '@pascal-app/viewer/ui'
72
72
  import { ActionButton } from './controls/action-button'
73
73
  import { SliderControl } from './controls/slider-control'
74
74
  import { LevelDuplicateDialog } from './level-duplicate-dialog'
@@ -81,7 +81,7 @@ import {
81
81
  DialogHeader,
82
82
  DialogTitle,
83
83
  } from './primitives/dialog'
84
- import { Popover, PopoverContent, PopoverTrigger } from './primitives/popover'
84
+ import { Popover, PopoverContent, PopoverTrigger } from '@pascal-app/viewer/ui'
85
85
 
86
86
  // ── Inline rename input for a level row ─────────────────────────────────────
87
87
 
@@ -23,7 +23,7 @@ import useEditor, { type GridSnapStep } from '../../../store/use-editor'
23
23
  import useFenceCurveDraft from '../../../store/use-fence-curve-draft'
24
24
  import { IconRefGlyph } from '../icon-ref'
25
25
  import { ShortcutToken } from '../primitives/shortcut-token'
26
- import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip'
26
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@pascal-app/viewer/ui'
27
27
  import { useInRightStack } from '../right-stack'
28
28
 
29
29
  // One muted container holds every row — passive key hints and interactive chips
@@ -1,11 +1,18 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
2
  import { renderToStaticMarkup } from 'react-dom/server'
3
+ import {
4
+ AXIS_MOVE_DRAG_LABEL,
5
+ PATH_POINT_DRAG_LABEL,
6
+ ROTATE_HANDLE_DRAG_LABEL,
7
+ } from '../../../lib/contextual-help'
3
8
  import { SELECT_HUD_TITLE, toolHudTitle } from '../../../lib/hud-title'
4
9
  import { snapContextOf } from '../../../lib/snapping-mode'
5
10
  import { cornerHintRows } from '../../../store/use-corner-snap-hint'
6
- import { TooltipProvider } from '../primitives/tooltip'
11
+ import { TooltipProvider } from '@pascal-app/viewer/ui'
7
12
  import { ContextualHelperPanel } from './contextual-helper-panel'
8
13
  import {
14
+ mepHandleGesture,
15
+ mepHandleGestureHints,
9
16
  mezzanineHints,
10
17
  openingHints,
11
18
  ROOM_DIVIDE_HINTS,
@@ -128,6 +135,52 @@ describe('HUD header', () => {
128
135
  })
129
136
  })
130
137
 
138
+ describe('MEP handle HUD', () => {
139
+ const english = (hints: ReturnType<typeof mepHandleGestureHints>) =>
140
+ hints.map((hint) => (typeof hint.label === 'string' ? hint.label : hint.label.message))
141
+ const drag = (label: string) => ({ nodeId: 'pipe_a', label })
142
+
143
+ test('names a run point drag and an axis slide, and leaves rotate and resize to their own HUDs', () => {
144
+ expect(mepHandleGesture(drag(PATH_POINT_DRAG_LABEL))).toBe('edit-path')
145
+ expect(mepHandleGesture(drag(AXIS_MOVE_DRAG_LABEL))).toBe('move')
146
+ expect(mepHandleGesture(drag(ROTATE_HANDLE_DRAG_LABEL))).toBeNull()
147
+ expect(mepHandleGesture(drag('resize-diameter'))).toBeNull()
148
+ expect(mepHandleGesture(null)).toBeNull()
149
+ })
150
+
151
+ test('keeps "Alt: Detach the joint" on screen for the whole run point drag', () => {
152
+ const idle = mepHandleGestureHints('edit-path', false)
153
+ expect(english(idle)).toEqual(['Detach the joint', 'Cancel'])
154
+ expect(idle.map((hint) => hint.keys)).toEqual([['Alt'], ['Esc']])
155
+ expect(idle[0]?.active).toBe(false)
156
+ expect(mepHandleGestureHints('edit-path', true)[0]?.active).toBe(true)
157
+ expect(english(mepHandleGestureHints('move', false))).toEqual(['Cancel'])
158
+
159
+ const html = renderToStaticMarkup(
160
+ <TooltipProvider>
161
+ <ContextualHelperPanel hints={idle} snapContext="polygon" />
162
+ </TooltipProvider>,
163
+ )
164
+ for (const label of ['Detach the joint', 'Cancel', 'Snapping: Grid']) expect(html).toContain(label)
165
+ })
166
+
167
+ test('a handle drag snaps in its kind context, and a rotate arc in angles', () => {
168
+ const contextOf = (handle: string, profile: 'item' | 'structural') =>
169
+ snapContextOf({
170
+ scope: { kind: 'handle-drag', nodeId: 'pipe_a', handle },
171
+ mode: 'select',
172
+ tool: null,
173
+ profileOf: () => undefined,
174
+ profileOfNode: () => profile,
175
+ })
176
+ expect(contextOf(PATH_POINT_DRAG_LABEL, 'structural')).toBe('polygon')
177
+ expect(contextOf(AXIS_MOVE_DRAG_LABEL, 'structural')).toBe('polygon')
178
+ expect(contextOf(AXIS_MOVE_DRAG_LABEL, 'item')).toBe('item')
179
+ expect(contextOf('resize-diameter', 'item')).toBe('item')
180
+ expect(contextOf(ROTATE_HANDLE_DRAG_LABEL, 'item')).toBe('rotation')
181
+ })
182
+ })
183
+
131
184
  describe('Cut opening HUD', () => {
132
185
  test('names the drag gesture "Draw the opening", not the mezzanine', () => {
133
186
  // The drag row folds under "More shortcuts", so read the rows the HUD is given.
@@ -11,11 +11,13 @@ import {
11
11
  import { useViewer } from '@pascal-app/viewer'
12
12
  import { useEffect, useMemo, useState, useSyncExternalStore } from 'react'
13
13
  import { useShallow } from 'zustand/react/shallow'
14
- import { useIsMobile } from '../../../hooks/use-mobile'
14
+ import { useIsMobile } from '@pascal-app/viewer/ui'
15
15
  import {
16
+ AXIS_MOVE_DRAG_LABEL,
16
17
  type ContextualShortcutHint,
17
18
  GROUP_MOVE_DRAG_LABEL,
18
19
  GROUP_ROTATE_DRAG_LABEL,
20
+ PATH_POINT_DRAG_LABEL,
19
21
  ROTATE_HANDLE_DRAG_LABEL,
20
22
  resolveRotateHandleHelpHints,
21
23
  resolveSelectModeHelpHints,
@@ -294,6 +296,40 @@ export const MEZZANINE_GESTURE_HINTS: Record<MezzanineGesture, ContextualShortcu
294
296
  ],
295
297
  }
296
298
 
299
+ export type MepHandleGesture = 'edit-path' | 'move'
300
+
301
+ /**
302
+ * A drag on an MEP handle — a run point, where Alt detaches the joint, or a
303
+ * fitting, run or run segment slid along an axis — or null. Its HUD names the
304
+ * kind and the gesture, not "Resize".
305
+ */
306
+ export function mepHandleGesture(
307
+ drag: { nodeId: string; label: string } | null,
308
+ ): MepHandleGesture | null {
309
+ if (drag?.label === PATH_POINT_DRAG_LABEL) return 'edit-path'
310
+ if (drag?.label === AXIS_MOVE_DRAG_LABEL) return 'move'
311
+ return null
312
+ }
313
+
314
+ export function mepHandleGestureHints(
315
+ gesture: MepHandleGesture,
316
+ altPressed: boolean,
317
+ ): ContextualShortcutHint[] {
318
+ const cancel: ContextualShortcutHint = {
319
+ keys: ['Esc'],
320
+ label: /* i18n */ { id: 'editor.helpers.helperManager.cancel', message: 'Cancel' },
321
+ }
322
+ if (gesture === 'move') return [cancel]
323
+ return [
324
+ {
325
+ keys: ['Alt'],
326
+ label: /* i18n */ { id: 'editor.helpers.helperManager.detachTheJoint', message: 'Detach the joint' },
327
+ active: altPressed,
328
+ },
329
+ cancel,
330
+ ]
331
+ }
332
+
297
333
  /**
298
334
  * Whether the armed tool's own hints (`toolHints`) win over the node in hand: a registry tool that
299
335
  * holds its draft in a placing scope (the roof's footprint, a fitting, a block) says what its clicks
@@ -340,6 +376,10 @@ export function HelperManager() {
340
376
  const reshapingNode = useReshapingNode()
341
377
  const activeHandleDrag = useActiveHandleDrag()
342
378
  const mezzanineHandle = useScene((s) => mezzanineGesture(activeHandleDrag, s.nodes))
379
+ const mepHandle = mepHandleGesture(activeHandleDrag)
380
+ const mepHandleKind = useScene((s) =>
381
+ mepHandle && activeHandleDrag ? s.nodes[activeHandleDrag.nodeId as AnyNodeId]?.type : undefined,
382
+ )
343
383
  const selectedIds = useViewer((s) => s.selection.selectedIds)
344
384
  const isMobile = useIsMobile()
345
385
  const modifiers = useActiveModifierKeys()
@@ -518,6 +558,20 @@ export function HelperManager() {
518
558
  )
519
559
  }
520
560
 
561
+ if (mepHandle) {
562
+ return (
563
+ <ContextualHelperPanel
564
+ hints={mepHandleGestureHints(mepHandle, modifiers.alt)}
565
+ snapContext={snapContext}
566
+ title={
567
+ mepHandleKind
568
+ ? nodeKindHudTitle(mepHandleKind, mepHandle === 'move' ? 'move' : 'edit')
569
+ : null
570
+ }
571
+ />
572
+ )
573
+ }
574
+
521
575
  // A single-node resize arrow is still an active snapping interaction. Roof
522
576
  // width/depth handles opt into grid snapping, so keep the mode and grid-step
523
577
  // controls visible for the whole drag instead of falling through to idle
@@ -4,7 +4,7 @@ import type { LevelNode, MessageDescriptor } from '@pascal-app/core'
4
4
  import { useEffect, useState } from 'react'
5
5
  import type { LevelDuplicatePreset } from '@pascal-app/core/building'
6
6
  import { useLocale, usePascalI18n } from '@pascal-app/core'
7
- import { localizedLevelName } from '../../i18n/level-name'
7
+ import { localizedLevelName } from '@pascal-app/viewer/ui'
8
8
  import { cn } from '../../lib/utils'
9
9
  import {
10
10
  Dialog,
@@ -21,12 +21,12 @@ import {
21
21
  DropdownMenuContent,
22
22
  DropdownMenuItem,
23
23
  DropdownMenuTrigger,
24
- } from '../../../../components/ui/primitives/dropdown-menu'
24
+ } from '@pascal-app/viewer/ui'
25
25
  import {
26
26
  Popover,
27
27
  PopoverContent,
28
28
  PopoverTrigger,
29
- } from '../../../../components/ui/primitives/popover'
29
+ } from '@pascal-app/viewer/ui'
30
30
  import { cn } from '../../../../lib/utils'
31
31
 
32
32
  interface CollectionsPopoverProps {
@@ -19,7 +19,7 @@ import {
19
19
  } from '@pascal-app/core'
20
20
  import { useViewer } from '@pascal-app/viewer'
21
21
  import { localizedFloorFootprintName } from '../../../i18n/floor-footprint-name'
22
- import { localizedLevelName } from '../../../i18n/level-name'
22
+ import { localizedLevelName } from '@pascal-app/viewer/ui'
23
23
  import { useRef, useState } from 'react'
24
24
  import {
25
25
  applyFloorFoundation,
@@ -36,11 +36,11 @@ import {
36
36
  THICK_FLOOR_HINT,
37
37
  thickFloorAdvice,
38
38
  } from '../../../lib/floor-footprints'
39
- import { formatLinearMeasurement } from '../../../lib/measurements'
39
+ import { formatLinearMeasurement } from '@pascal-app/core'
40
40
  import { FinishSwatch } from '../controls/finish-swatch'
41
41
  import { MetricControl } from '../controls/metric-control'
42
42
  import { SegmentedControl } from '../controls/segmented-control'
43
- import { Button } from '../primitives/button'
43
+ import { Button } from '@pascal-app/viewer/ui'
44
44
  import { PanelWrapper } from './panel-wrapper'
45
45
 
46
46
  /** Unpainted finishes, as the plate renders them (nodes' slab slot defaults). */
@@ -14,13 +14,13 @@ import {
14
14
  finishFloorOpening,
15
15
  openingRoom,
16
16
  } from '../../../lib/floor-edit-session'
17
- import { formatAreaLabel } from '../../../lib/measurements'
18
- import { Button } from '../primitives/button'
19
- import { Switch } from '../primitives/switch'
17
+ import { formatAreaLabel } from '@pascal-app/core'
18
+ import { Button } from '@pascal-app/viewer/ui'
19
+ import { Switch } from '@pascal-app/viewer/ui'
20
20
  import { PanelWrapper } from './panel-wrapper'
21
- import { localizedLevelName } from '../../../i18n/level-name'
21
+ import { localizedLevelName } from '@pascal-app/viewer/ui'
22
22
  import { nodeLabel } from '../../../i18n/node-messages'
23
- import { displayRoomName } from '../../../lib/room-name-messages'
23
+ import { displayRoomName } from '@pascal-app/viewer/ui'
24
24
 
25
25
  /**
26
26
  * A floor (or ceiling) opening: where it is, what it cuts, and the switch for
@@ -12,8 +12,8 @@ interface MobileSelectionBarProps {
12
12
  node: AnyNode | null
13
13
  label?: string
14
14
  icon?: string
15
- onMove: () => void
16
- onDuplicate: () => void
15
+ onMove?: () => void
16
+ onDuplicate?: () => void
17
17
  onDelete: () => void
18
18
  onEdit: () => void
19
19
  }
@@ -58,28 +58,32 @@ export function MobileSelectionBar({
58
58
  </button>
59
59
 
60
60
  <div className="flex items-center gap-0.5 border-border/40 border-l pl-1">
61
- <button
62
- aria-label={i18n._({ id: 'editor.panels.mobileSelectionBar.move', message: 'Move' })}
63
- className={ACTION_BTN}
64
- onClick={(e) => {
65
- stop(e)
66
- onMove()
67
- }}
68
- type="button"
69
- >
70
- <Move className="h-4 w-4" />
71
- </button>
72
- <button
73
- aria-label={i18n._({ id: 'editor.panels.mobileSelectionBar.duplicate', message: 'Duplicate' })}
74
- className={ACTION_BTN}
75
- onClick={(e) => {
76
- stop(e)
77
- onDuplicate()
78
- }}
79
- type="button"
80
- >
81
- <Copy className="h-4 w-4" />
82
- </button>
61
+ {onMove && (
62
+ <button
63
+ aria-label={i18n._({ id: 'editor.panels.mobileSelectionBar.move', message: 'Move' })}
64
+ className={ACTION_BTN}
65
+ onClick={(e) => {
66
+ stop(e)
67
+ onMove()
68
+ }}
69
+ type="button"
70
+ >
71
+ <Move className="h-4 w-4" />
72
+ </button>
73
+ )}
74
+ {onDuplicate && (
75
+ <button
76
+ aria-label={i18n._({ id: 'editor.panels.mobileSelectionBar.duplicate', message: 'Duplicate' })}
77
+ className={ACTION_BTN}
78
+ onClick={(e) => {
79
+ stop(e)
80
+ onDuplicate()
81
+ }}
82
+ type="button"
83
+ >
84
+ <Copy className="h-4 w-4" />
85
+ </button>
86
+ )}
83
87
  <button
84
88
  aria-label={i18n._({ id: 'editor.panels.mobileSelectionBar.delete', message: 'Delete' })}
85
89
  className={cn(
@@ -16,7 +16,7 @@ import {
16
16
  import { useViewer } from '@pascal-app/viewer'
17
17
  import { useCallback } from 'react'
18
18
  import { useShallow } from 'zustand/react/shallow'
19
- import { formatLinearMeasurement } from '../../../lib/measurements'
19
+ import { formatLinearMeasurement } from '@pascal-app/core'
20
20
  import { SegmentedControl } from '../controls/segmented-control'
21
21
  import { SliderControl } from '../controls/slider-control'
22
22
  import {
@@ -42,7 +42,7 @@ function wallFollowsLevelPatch(wall: WallNode, nodes: Record<string, AnyNode>):
42
42
  }
43
43
 
44
44
  function effectiveHeight(node: AnyNode, nodes: Record<string, AnyNode>): number {
45
- if (node.type === 'wall') return getWallEffectiveHeightForNodes(node, nodes)
45
+ if (node.type === 'wall') return getWallEffectiveHeightForNodes(node, nodes, 0)
46
46
  if (node.type === 'ceiling') return resolveCeilingHeight(node, nodes)
47
47
  const height = (node as { height?: number }).height
48
48
  return typeof height === 'number' ? height : 0
@@ -72,7 +72,7 @@ export function applyMultiHeightMode(
72
72
  if (isCustom) return {}
73
73
  if (node.type === 'ceiling') return { height: ceilingCustomHeight(node, nodes) }
74
74
  if (node.type === 'wall') {
75
- return { height: Math.max(0.1, getWallEffectiveHeightForNodes(node, nodes)) }
75
+ return { height: Math.max(0.1, getWallEffectiveHeightForNodes(node, nodes, 0)) }
76
76
  }
77
77
  return {}
78
78
  }
@@ -14,7 +14,7 @@ import { useViewer } from '@pascal-app/viewer'
14
14
  import { Building2, Copy, Group, Merge, Split, Trash2, Ungroup } from 'lucide-react'
15
15
  import { useMemo } from 'react'
16
16
  import { deleteSelection, duplicateSelectionAndPickUp } from '../../editor/group-actions'
17
- import { formatLinearMeasurement } from '../../../lib/measurements'
17
+ import { formatLinearMeasurement } from '@pascal-app/core'
18
18
  import { triggerSFX } from '../../../lib/sfx-bus'
19
19
  import { writeWindowChanges } from '../../../lib/window-changes'
20
20
  import { collectSelectableCandidateIds } from '../../tools/select/select-candidates'