@pascal-app/editor 1.0.6 → 1.1.0

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 (382) hide show
  1. package/package.json +18 -15
  2. package/src/components/editor/action-menu-button.tsx +4 -1
  3. package/src/components/editor/alignment-3d-guide-layer.tsx +102 -11
  4. package/src/components/editor/bake-thumbnail.tsx +3 -2
  5. package/src/components/editor/custom-camera-controls.tsx +110 -42
  6. package/src/components/editor/duplicate-node.ts +2 -1
  7. package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
  8. package/src/components/editor/first-person-controls.tsx +77 -68
  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 +31 -11
  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 +2 -2
  15. package/src/components/editor/floorplan-panel.test.ts +220 -0
  16. package/src/components/editor/floorplan-panel.tsx +458 -140
  17. package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
  18. package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
  19. package/src/components/editor/footprint-height-handle.tsx +2 -2
  20. package/src/components/editor/grid.tsx +24 -15
  21. package/src/components/editor/group-actions.ts +19 -1
  22. package/src/components/editor/group-floating-action-menu.tsx +1 -1
  23. package/src/components/editor/group-move-3d.ts +27 -2
  24. package/src/components/editor/group-selection-box-3d.tsx +4 -1
  25. package/src/components/editor/group-transform-shared.ts +23 -1
  26. package/src/components/editor/index.tsx +150 -20
  27. package/src/components/editor/inspector-extension-actions.tsx +36 -0
  28. package/src/components/editor/localized-html.tsx +22 -0
  29. package/src/components/editor/measurement-pill.tsx +1 -1
  30. package/src/components/editor/node-action-menu.tsx +46 -12
  31. package/src/components/editor/node-arrow-handles.tsx +2 -3
  32. package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
  33. package/src/components/editor/open-wall-ends-hint.tsx +1 -1
  34. package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
  35. package/src/components/editor/quick-measurement-card.tsx +1 -1
  36. package/src/components/editor/registry-action-contributions.test.tsx +67 -6
  37. package/src/components/editor/registry-action-contributions.tsx +115 -15
  38. package/src/components/editor/riser-diagram-panel.tsx +59 -9
  39. package/src/components/editor/room-controls.tsx +4 -3
  40. package/src/components/editor/room-handles-2d.tsx +158 -7
  41. package/src/components/editor/room-handles.tsx +244 -54
  42. package/src/components/editor/room-highlight.tsx +2 -0
  43. package/src/components/editor/room-labels.tsx +33 -17
  44. package/src/components/editor/selection-manager.tsx +15 -102
  45. package/src/components/editor/separator-end-grip.tsx +74 -0
  46. package/src/components/editor/site-edge-labels.tsx +7 -3
  47. package/src/components/editor/thumbnail-generator.test.ts +15 -6
  48. package/src/components/editor/thumbnail-generator.tsx +26 -1
  49. package/src/components/editor/use-floorplan-background-placement.ts +55 -52
  50. package/src/components/editor/views/view-bar.tsx +111 -26
  51. package/src/components/editor/wall-measurement-label.tsx +31 -15
  52. package/src/components/editor/wall-move-side-handles.tsx +15 -7
  53. package/src/components/editor/wall-pull-grip.tsx +193 -0
  54. package/src/components/editor/wall-region-handles.tsx +2 -2
  55. package/src/components/editor/wall-snap-beacon-layer.tsx +134 -33
  56. package/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts +76 -0
  57. package/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx +186 -0
  58. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +63 -3
  59. package/src/components/editor-2d/floorplan-group-move.tsx +32 -4
  60. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
  61. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +33 -24
  62. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +12 -5
  63. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
  64. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +29 -5
  65. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +96 -4
  66. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +52 -12
  67. package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
  68. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +13 -1
  69. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
  70. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
  71. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +34 -1
  72. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +211 -39
  73. package/src/components/procedural-items/inspector.tsx +59 -12
  74. package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +2 -0
  75. package/src/components/systems/ceiling/ceiling-system.test.ts +141 -0
  76. package/src/components/systems/ceiling/ceiling-system.tsx +53 -18
  77. package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
  78. package/src/components/systems/roof/roof-edit-system.tsx +30 -19
  79. package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
  80. package/src/components/tools/distribution/distribution-run-tool.tsx +200 -58
  81. package/src/components/tools/item/move-tool.tsx +1 -6
  82. package/src/components/tools/item/placement-history.test.tsx +1 -1
  83. package/src/components/tools/item/placement-strategies.test.ts +64 -1
  84. package/src/components/tools/item/placement-strategies.ts +5 -9
  85. package/src/components/tools/item/use-placement-coordinator.tsx +31 -35
  86. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +1 -1
  87. package/src/components/tools/registry/item-surface-move.test.ts +11 -0
  88. package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
  89. package/src/components/tools/registry/move-registry-node-tool.tsx +38 -14
  90. package/src/components/tools/select/select-candidates.test.ts +45 -0
  91. package/src/components/tools/select/select-candidates.ts +13 -2
  92. package/src/components/tools/shared/cursor-sphere.tsx +10 -3
  93. package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
  94. package/src/components/tools/shared/placement-box.tsx +2 -6
  95. package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
  96. package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
  97. package/src/components/tools/shared/segment-angle.ts +0 -4
  98. package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
  99. package/src/components/tools/tool-manager.tsx +1 -24
  100. package/src/components/tools/wall/wall-drafting.test.ts +299 -23
  101. package/src/components/tools/wall/wall-drafting.ts +132 -24
  102. package/src/components/tools/wall/wall-pull-snap.test.ts +274 -0
  103. package/src/components/tools/wall/wall-pull-snap.ts +563 -0
  104. package/src/components/tools/wall/wall-snap-geometry.test.ts +113 -1
  105. package/src/components/tools/wall/wall-snap-geometry.ts +137 -0
  106. package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
  107. package/src/components/tools/wall/wall-typed-entry.ts +70 -0
  108. package/src/components/tools/zone/zone-tool.tsx +3 -3
  109. package/src/components/ui/action-menu/action-button.tsx +2 -0
  110. package/src/components/ui/action-menu/camera-actions.tsx +8 -7
  111. package/src/components/ui/action-menu/control-modes.tsx +9 -5
  112. package/src/components/ui/action-menu/follow-pascal-pill.tsx +3 -1
  113. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +7 -1
  114. package/src/components/ui/action-menu/furnish-tools.tsx +7 -6
  115. package/src/components/ui/action-menu/index.tsx +1 -0
  116. package/src/components/ui/action-menu/measurement-control.tsx +35 -29
  117. package/src/components/ui/action-menu/point-ask-button.tsx +4 -3
  118. package/src/components/ui/action-menu/structure-tools.tsx +25 -23
  119. package/src/components/ui/action-menu/view-toggles.test.ts +18 -0
  120. package/src/components/ui/action-menu/view-toggles.tsx +256 -51
  121. package/src/components/ui/build-panel/build-panel.tsx +163 -25
  122. package/src/components/ui/command-palette/editor-commands.tsx +38 -11
  123. package/src/components/ui/controls/finish-swatch.tsx +45 -19
  124. package/src/components/ui/controls/material-picker.tsx +24 -17
  125. package/src/components/ui/controls/metric-control.test.ts +52 -0
  126. package/src/components/ui/controls/metric-control.tsx +62 -24
  127. package/src/components/ui/controls/segmented-control.tsx +5 -2
  128. package/src/components/ui/controls/slider-control.test.tsx +23 -0
  129. package/src/components/ui/controls/slider-control.tsx +47 -19
  130. package/src/components/ui/controls/tool-options-panel.tsx +74 -44
  131. package/src/components/ui/floating-level-selector.tsx +100 -48
  132. package/src/components/ui/helpers/building-helper.tsx +5 -3
  133. package/src/components/ui/helpers/contextual-helper-panel.tsx +97 -37
  134. package/src/components/ui/helpers/helper-manager.test.tsx +106 -3
  135. package/src/components/ui/helpers/helper-manager.tsx +136 -57
  136. package/src/components/ui/helpers/item-helper.tsx +6 -4
  137. package/src/components/ui/helpers/registered-tool-helper.tsx +3 -1
  138. package/src/components/ui/level-duplicate-dialog.tsx +32 -23
  139. package/src/components/ui/level-wall-copy-prompt.tsx +124 -0
  140. package/src/components/ui/panels/collections/collections-popover.tsx +11 -11
  141. package/src/components/ui/panels/deck-boards-fields.tsx +136 -0
  142. package/src/components/ui/panels/floor-foundation-panel.tsx +114 -39
  143. package/src/components/ui/panels/floor-opening-panel.tsx +74 -26
  144. package/src/components/ui/panels/inspector-extensions.tsx +66 -0
  145. package/src/components/ui/panels/mobile-panel-sheet.tsx +3 -1
  146. package/src/components/ui/panels/mobile-selection-bar.tsx +34 -28
  147. package/src/components/ui/panels/multi-field-value.test.ts +27 -0
  148. package/src/components/ui/panels/multi-field-value.ts +20 -1
  149. package/src/components/ui/panels/multi-height-mode.tsx +11 -10
  150. package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
  151. package/src/components/ui/panels/multi-selection-panel.tsx +76 -36
  152. package/src/components/ui/panels/node-display.ts +35 -24
  153. package/src/components/ui/panels/panel-manager.tsx +9 -5
  154. package/src/components/ui/panels/panel-wrapper.tsx +63 -127
  155. package/src/components/ui/panels/parametric-field-control.tsx +13 -10
  156. package/src/components/ui/panels/parametric-inspector.tsx +18 -12
  157. package/src/components/ui/panels/reference-panel.tsx +51 -35
  158. package/src/components/ui/panels/room-construction-rows.tsx +465 -66
  159. package/src/components/ui/panels/room-name-options.test.ts +36 -3
  160. package/src/components/ui/panels/room-name-options.ts +110 -11
  161. package/src/components/ui/panels/room-openings-field.tsx +22 -13
  162. package/src/components/ui/panels/room-panel.tsx +207 -100
  163. package/src/components/ui/panels/shape-choice.tsx +7 -5
  164. package/src/components/ui/panels/wall-region-list.tsx +7 -7
  165. package/src/components/ui/primitives/shortcut-token.tsx +10 -6
  166. package/src/components/ui/right-stack.test.ts +13 -6
  167. package/src/components/ui/right-stack.tsx +21 -10
  168. package/src/components/ui/sidebar/icon-rail.tsx +11 -9
  169. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +10 -10
  170. package/src/components/ui/sidebar/panels/items-panel/index.tsx +10 -9
  171. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +3 -0
  172. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +102 -32
  173. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +1 -5
  174. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +4 -3
  175. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -3
  176. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +4 -2
  177. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +7 -5
  178. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +4 -2
  179. package/src/components/ui/sidebar/panels/site-panel/default-names.ts +75 -0
  180. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +4 -2
  181. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +4 -2
  182. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +4 -2
  183. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +4 -2
  184. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +4 -2
  185. package/src/components/ui/sidebar/panels/site-panel/index.tsx +118 -72
  186. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +36 -3
  187. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +4 -2
  188. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -4
  189. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +8 -2
  190. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +8 -5
  191. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +4 -2
  192. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -4
  193. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +4 -2
  194. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +4 -2
  195. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +8 -7
  196. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +7 -6
  197. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +16 -1
  198. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +15 -8
  199. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +4 -2
  200. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +4 -2
  201. package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +3 -1
  202. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +19 -9
  203. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +25 -14
  204. package/src/components/ui/sidebar/tab-bar.tsx +4 -1
  205. package/src/components/ui/sidebar/use-plugin-panels.tsx +19 -10
  206. package/src/components/ui/snap-target-badge.tsx +7 -6
  207. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +721 -0
  208. package/src/components/ui/wall-type/wall-type-menu.tsx +167 -0
  209. package/src/components/viewer/floorplan-compass-button.tsx +12 -3
  210. package/src/components/viewer/floorplan-preview.tsx +27 -19
  211. package/src/components/viewer/render-settings-menu.tsx +294 -0
  212. package/src/components/viewer/sky-control.tsx +710 -0
  213. package/src/components/viewer/use-viewer-ui-sounds.test.ts +15 -0
  214. package/src/components/viewer/use-viewer-ui-sounds.ts +46 -0
  215. package/src/components/viewer/viewer-controls-bar.tsx +278 -260
  216. package/src/components/viewer/viewer-hover-label.tsx +12 -4
  217. package/src/components/viewer/viewer-rooms.tsx +62 -5
  218. package/src/components/viewer/viewer-scene-header.tsx +28 -17
  219. package/src/components/viewer/viewer-stage-switcher.tsx +4 -2
  220. package/src/components/viewer-overlay.tsx +8 -0
  221. package/src/components/walkthrough-hud.tsx +75 -2
  222. package/src/hooks/use-keyboard.test.ts +85 -4
  223. package/src/hooks/use-keyboard.ts +65 -7
  224. package/src/hooks/use-selected-room.ts +12 -1
  225. package/src/i18n/floor-footprint-name.ts +30 -0
  226. package/src/i18n/level-name.ts +39 -0
  227. package/src/i18n/locales/en/messages.po +15849 -0
  228. package/src/i18n/locales/en/messages.ts +1 -0
  229. package/src/i18n/locales/en-XA/messages.ts +1 -0
  230. package/src/i18n/locales/es/messages.po +39331 -0
  231. package/src/i18n/locales/es/messages.ts +1 -0
  232. package/src/i18n/locales/fr/messages.po +39331 -0
  233. package/src/i18n/locales/fr/messages.ts +1 -0
  234. package/src/i18n/locales/ja/messages.po +39331 -0
  235. package/src/i18n/locales/ja/messages.ts +1 -0
  236. package/src/i18n/locales/zh-CN/messages.po +39331 -0
  237. package/src/i18n/locales/zh-CN/messages.ts +1 -0
  238. package/src/i18n/locales.test.ts +63 -0
  239. package/src/i18n/locales.ts +32 -0
  240. package/src/i18n/message-slot.tsx +25 -0
  241. package/src/i18n/node-messages.ts +169 -0
  242. package/src/i18n/pseudo-locale.ts +17 -0
  243. package/src/i18n/published-entry.test.ts +17 -0
  244. package/src/i18n/scene-theme-name.ts +16 -0
  245. package/src/index.tsx +224 -45
  246. package/src/lib/add-storey.test.ts +3 -3
  247. package/src/lib/contextual-help.test.ts +20 -1
  248. package/src/lib/contextual-help.ts +101 -19
  249. package/src/lib/continuation.ts +38 -11
  250. package/src/lib/elevation-guides.ts +21 -6
  251. package/src/lib/elevator-support.ts +1 -72
  252. package/src/lib/floor-footprints.test.ts +3 -2
  253. package/src/lib/floor-footprints.ts +1 -9
  254. package/src/lib/floor-foundation-support.test.ts +15 -6
  255. package/src/lib/floor-opening-draft.ts +1 -1
  256. package/src/lib/floor-region-session.ts +1 -1
  257. package/src/lib/floorplan/dimension-drive.test.ts +36 -2
  258. package/src/lib/floorplan/dimension-drive.ts +68 -3
  259. package/src/lib/floorplan/floorplan-export.test.ts +16 -5
  260. package/src/lib/floorplan/floorplan-export.tsx +13 -16
  261. package/src/lib/floorplan/floorplan-extension.test.ts +11 -12
  262. package/src/lib/floorplan/floorplan-extension.ts +37 -18
  263. package/src/lib/floorplan/floorplan-mode.test.ts +13 -6
  264. package/src/lib/floorplan/floorplan-mode.ts +14 -12
  265. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +1 -1
  266. package/src/lib/floorplan/floorplan-readonly.ts +3 -3
  267. package/src/lib/floorplan/open-wall-ends.ts +2 -1
  268. package/src/lib/floorplan/schedules.ts +10 -6
  269. package/src/lib/floorplan/site-plan/site-annotations.test.ts +46 -1
  270. package/src/lib/floorplan/site-plan/site-annotations.ts +3 -0
  271. package/src/lib/floorplan/site-plan/site-parts.ts +6 -3
  272. package/src/lib/geometry-script/author.test.ts +114 -0
  273. package/src/lib/geometry-script/author.ts +55 -23
  274. package/src/lib/geometry-script/client.ts +3 -0
  275. package/src/lib/geometry-script/finish-scripts.ts +7 -7
  276. package/src/lib/gesture-lifecycle.ts +3 -1
  277. package/src/lib/glb-export.test.ts +327 -0
  278. package/src/lib/glb-export.ts +70 -2
  279. package/src/lib/grid-frame.test.ts +56 -3
  280. package/src/lib/grid-frame.ts +28 -1
  281. package/src/lib/history.test.ts +2 -1
  282. package/src/lib/hud-title.ts +48 -24
  283. package/src/lib/ifc-export.test.ts +41 -1
  284. package/src/lib/inspector-card-mode.test.ts +3 -31
  285. package/src/lib/inspector-card-mode.ts +5 -10
  286. package/src/lib/inspector-expanded.test.ts +13 -0
  287. package/src/lib/inspector-expanded.ts +13 -1
  288. package/src/lib/interaction/scope.ts +1 -0
  289. package/src/lib/keyboard-pan.test.ts +7 -0
  290. package/src/lib/keyboard-pan.ts +9 -2
  291. package/src/lib/layer-thickness.test.ts +22 -0
  292. package/src/lib/layer-thickness.ts +79 -0
  293. package/src/lib/level-selection.test.ts +18 -2
  294. package/src/lib/level-selection.ts +31 -8
  295. package/src/lib/linear-display.ts +1 -1
  296. package/src/lib/live-gesture-round-trip.test.ts +1 -1
  297. package/src/lib/lot/drop-in.test.ts +59 -1
  298. package/src/lib/lot/drop-in.ts +42 -50
  299. package/src/lib/material-swatch-style.ts +24 -0
  300. package/src/lib/measurement-label.test.ts +1 -1
  301. package/src/lib/measurement-parser.ts +7 -3
  302. package/src/lib/measurements.test.ts +24 -6
  303. package/src/lib/measurements.ts +1 -203
  304. package/src/lib/mezzanine-selection.test.ts +3 -1
  305. package/src/lib/node-action-movement.test.tsx +115 -0
  306. package/src/lib/node-action-movement.ts +2 -0
  307. package/src/lib/orbit-near-plane.test.ts +12 -0
  308. package/src/lib/orbit-near-plane.ts +13 -0
  309. package/src/lib/paint-regions.test.ts +8 -5
  310. package/src/lib/paint-scope.test.ts +53 -0
  311. package/src/lib/paint-scope.ts +18 -4
  312. package/src/lib/panel-rows.ts +17 -8
  313. package/src/lib/placement-metadata.ts +4 -0
  314. package/src/lib/point-ask/describe-target.ts +6 -8
  315. package/src/lib/point-ask/region.ts +1 -1
  316. package/src/lib/print-shell-compiler.ts +12 -1
  317. package/src/lib/print-wall-solids.test.ts +11 -0
  318. package/src/lib/print-wall-solids.ts +9 -0
  319. package/src/lib/reference-scale.ts +83 -0
  320. package/src/lib/room-assembly-overlay.ts +1 -1
  321. package/src/lib/room-built-on.test.ts +28 -17
  322. package/src/lib/room-built-on.ts +21 -17
  323. package/src/lib/room-construction-commands.ts +3 -2
  324. package/src/lib/room-construction.test.ts +28 -1
  325. package/src/lib/room-construction.ts +14 -0
  326. package/src/lib/room-controls-phase6.test.ts +141 -1
  327. package/src/lib/room-handle-drag.ts +554 -58
  328. package/src/lib/room-name-catalog.test.ts +8 -0
  329. package/src/lib/room-name-catalog.ts +8 -1
  330. package/src/lib/room-name-messages.test.ts +132 -0
  331. package/src/lib/room-name-messages.ts +147 -0
  332. package/src/lib/room-name-storage.test.ts +334 -0
  333. package/src/lib/room-push-dimensions.test.ts +38 -1
  334. package/src/lib/room-push-dimensions.ts +226 -2
  335. package/src/lib/room-selection.test.ts +23 -6
  336. package/src/lib/scene-bounds.test.ts +144 -0
  337. package/src/lib/scene-bounds.ts +79 -15
  338. package/src/lib/scene-clipboard.test.ts +70 -49
  339. package/src/lib/scene-clipboard.ts +11 -5
  340. package/src/lib/selection-cycle.ts +63 -0
  341. package/src/lib/separator-edit.test.ts +431 -0
  342. package/src/lib/separator-edit.ts +179 -0
  343. package/src/lib/separator-lines.ts +38 -0
  344. package/src/lib/sfx-bus.ts +27 -1
  345. package/src/lib/sfx-player.ts +16 -0
  346. package/src/lib/snapping-mode.test.ts +23 -1
  347. package/src/lib/tool-cursor-icon.ts +5 -1
  348. package/src/lib/tool-shortcuts.ts +130 -0
  349. package/src/lib/viewer-selection.test.ts +28 -24
  350. package/src/lib/viewer-selection.ts +21 -6
  351. package/src/lib/wall-draw-variant.test.ts +7 -6
  352. package/src/lib/wall-draw-variant.ts +107 -6
  353. package/src/lib/wall-nudge.test.ts +44 -0
  354. package/src/lib/wall-nudge.ts +131 -0
  355. package/src/lib/wall-parallel-snap.test.ts +83 -0
  356. package/src/lib/wall-parallel-snap.ts +176 -0
  357. package/src/lib/wall-placement-pointer.test.ts +23 -0
  358. package/src/lib/wall-placement-pointer.ts +18 -0
  359. package/src/lib/wall-pull.test.ts +245 -0
  360. package/src/lib/wall-pull.ts +209 -0
  361. package/src/lib/wall-push-line-up.test.ts +132 -0
  362. package/src/lib/wall-region-list.ts +17 -6
  363. package/src/lib/wall-run-snap.test.ts +188 -0
  364. package/src/lib/wall-run-snap.ts +83 -0
  365. package/src/lib/wall-type.test.ts +125 -0
  366. package/src/lib/wall-type.ts +119 -0
  367. package/src/lib/window-interaction.test.ts +44 -0
  368. package/src/lib/world-grid-snap.test.ts +139 -2
  369. package/src/lib/world-grid-snap.ts +102 -105
  370. package/src/store/use-alignment-guides.ts +27 -1
  371. package/src/store/use-editor.tsx +53 -0
  372. package/src/store/use-floorplan-annotation-visibility.test.ts +64 -0
  373. package/src/store/use-floorplan-annotation-visibility.ts +47 -22
  374. package/src/store/use-placement-typing.test.ts +165 -0
  375. package/src/store/use-placement-typing.ts +186 -0
  376. package/src/store/use-wall-snap-indicator.ts +15 -2
  377. package/src/components/tools/elevator/elevator-defaults.ts +0 -8
  378. package/src/components/tools/elevator/elevator-tool.tsx +0 -372
  379. package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
  380. package/src/lib/door-interaction.ts +0 -102
  381. package/src/lib/measurement-label.ts +0 -68
  382. package/src/lib/window-interaction.ts +0 -100
@@ -0,0 +1,124 @@
1
+ 'use client'
2
+
3
+ import {
4
+ type AnyNode,
5
+ type AnyNodeId,
6
+ getLevelAbove,
7
+ getLevelBelow,
8
+ getLevelDisplayName,
9
+ useScene, usePascalI18n,
10
+ } from '@pascal-app/core'
11
+ import { buildExteriorWallCopy, levelExteriorWalls } from '@pascal-app/core/building'
12
+ import { useViewer } from '@pascal-app/viewer'
13
+ import { create } from 'zustand'
14
+
15
+ type Offer = { levelId: string; sourceLevelId: string; direction: 'below' | 'above' }
16
+
17
+ /**
18
+ * The one open "Copy exterior walls?" offer, for the level just added. While it is open the plan
19
+ * traces the source level's walls on the new one; that trace is the offer's, never the saved
20
+ * Walls below / above preference, and it goes with the offer.
21
+ */
22
+ export const useLevelWallCopyOffer = create<{
23
+ offer: Offer | null
24
+ setOffer: (offer: Offer | null) => void
25
+ }>((set) => ({ offer: null, setOffer: (offer) => set({ offer }) }))
26
+
27
+ let stopWatchingLevel: (() => void) | null = null
28
+
29
+ /** Opens `offer` (or closes the open one); picking another level closes it. */
30
+ function setOffer(offer: Offer | null) {
31
+ stopWatchingLevel?.()
32
+ stopWatchingLevel = null
33
+ useLevelWallCopyOffer.getState().setOffer(offer)
34
+ if (!offer) return
35
+ stopWatchingLevel = useViewer.subscribe((state) => {
36
+ if (state.selection.levelId !== offer.levelId) setOffer(null)
37
+ })
38
+ }
39
+
40
+ /**
41
+ * After a level is added next to `sourceLevelId` (by default the level under it, else the one over
42
+ * it, for a basement), and selected: when that level has an outline, offer to copy its exterior
43
+ * walls up or down, tracing them dashed on the plan while the offer is open.
44
+ */
45
+ export function offerExteriorWallCopy(levelId: string, sourceLevelId?: string) {
46
+ const nodes = useScene.getState().nodes as Record<string, AnyNode>
47
+ const graph = nodes as Record<AnyNodeId, AnyNode>
48
+ sourceLevelId ??= (getLevelBelow(levelId, graph) ?? getLevelAbove(levelId, graph))?.id
49
+ const source = sourceLevelId ? nodes[sourceLevelId] : undefined
50
+ const level = nodes[levelId]
51
+ if (!sourceLevelId || source?.type !== 'level' || level?.type !== 'level') return setOffer(null)
52
+ const direction = source.level < level.level ? 'below' : 'above'
53
+ setOffer(
54
+ levelExteriorWalls(nodes, sourceLevelId).length ? { levelId, sourceLevelId, direction } : null,
55
+ )
56
+ }
57
+
58
+ /** Closes the open offer without copying. */
59
+ export function dismissExteriorWallCopy() {
60
+ setOffer(null)
61
+ }
62
+
63
+ /** Copies the offered walls in place, as one undo step of its own. */
64
+ export function acceptExteriorWallCopy(offer: Offer): number {
65
+ const walls = buildExteriorWallCopy({
66
+ nodes: useScene.getState().nodes as Record<string, AnyNode>,
67
+ fromLevelId: offer.sourceLevelId,
68
+ toLevelId: offer.levelId,
69
+ })
70
+ if (walls.length) useScene.getState().applyNodeChanges({ create: walls })
71
+ setOffer(null)
72
+ return walls.length
73
+ }
74
+
75
+ /**
76
+ * "Copy exterior walls from the level below?" — a small card under the level list, never in the
77
+ * way: it goes when answered or when another level is picked, and hides once the level has walls.
78
+ */
79
+ export function LevelWallCopyPrompt() {
80
+ const i18n = usePascalI18n()
81
+ const offer = useLevelWallCopyOffer((state) => state.offer)
82
+ const activeLevelId = useViewer((state) => state.selection.levelId)
83
+ const sourceName = useScene((state) => {
84
+ const source = offer ? state.nodes[offer.sourceLevelId as never] : undefined
85
+ return source?.type === 'level' ? getLevelDisplayName(source) : null
86
+ })
87
+ const levelHasWalls = useScene((state) => {
88
+ const level = offer ? state.nodes[offer.levelId as AnyNodeId] : undefined
89
+ return (
90
+ level?.type === 'level' &&
91
+ level.children.some((id) => state.nodes[id as AnyNodeId]?.type === 'wall')
92
+ )
93
+ })
94
+ if (!offer || !sourceName || activeLevelId !== offer.levelId || levelHasWalls) return null
95
+ return (
96
+ <div
97
+ className="mt-2 w-56 rounded-xl border border-border bg-background/95 p-3 text-xs shadow-2xl backdrop-blur-md"
98
+ data-level-wall-copy={offer.levelId}
99
+ role="status"
100
+ >
101
+ <p className="font-medium text-foreground text-sm">
102
+ {i18n._({ id: 'editor.levelWallCopyPrompt.copyExteriorWallsFromTheLevel', message: 'Copy exterior walls from the level {direction}?', values: { direction: offer.direction } })}</p>
103
+ <p className="mt-0.5 text-muted-foreground">
104
+ {i18n._({ id: 'editor.levelWallCopyPrompt.sourcenameSOutsideWallsInPlace', message: '{sourceName}’s outside walls, in place, so this level lines up with it.', values: { sourceName } })}</p>
105
+ <div className="mt-2.5 flex gap-2">
106
+ <button
107
+ className="flex-1 rounded-full bg-foreground px-3 py-1.5 font-medium text-background transition-opacity hover:opacity-90"
108
+ data-level-wall-copy-accept
109
+ onClick={() => acceptExteriorWallCopy(offer)}
110
+ type="button"
111
+ >
112
+ {i18n._({ id: 'editor.levelWallCopyPrompt.copy', message: 'Copy' })}
113
+ </button>
114
+ <button
115
+ className="flex-1 rounded-full border border-border px-3 py-1.5 text-foreground transition-colors hover:bg-accent"
116
+ onClick={dismissExteriorWallCopy}
117
+ type="button"
118
+ >
119
+ {i18n._({ id: 'editor.levelWallCopyPrompt.noThanks', message: 'No thanks' })}
120
+ </button>
121
+ </div>
122
+ </div>
123
+ )
124
+ }
@@ -1,7 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import type { AnyNodeId, CollectionId } from '@pascal-app/core'
4
- import { useScene } from '@pascal-app/core'
4
+ import { useScene, usePascalI18n } from '@pascal-app/core'
5
5
  import {
6
6
  Check,
7
7
  ChevronDown,
@@ -36,6 +36,7 @@ interface CollectionsPopoverProps {
36
36
  }
37
37
 
38
38
  export function CollectionsPopover({ nodeIds, children }: CollectionsPopoverProps) {
39
+ const i18n = usePascalI18n()
39
40
  const collections = useScene((s) => s.collections)
40
41
  const nodes = useScene((s) => s.nodes)
41
42
  const createCollection = useScene((s) => s.createCollection)
@@ -102,7 +103,7 @@ export function CollectionsPopover({ nodeIds, children }: CollectionsPopoverProp
102
103
  <div className="flex items-center gap-1.5">
103
104
  <Layers className="h-3.5 w-3.5 text-muted-foreground" />
104
105
  <span className="font-semibold text-foreground text-xs tracking-tight">
105
- Collections
106
+ {i18n._({ id: 'editor.panels.collections.collectionsPopover.collections', message: 'Collections' })}
106
107
  </span>
107
108
  </div>
108
109
  <button
@@ -114,7 +115,7 @@ export function CollectionsPopover({ nodeIds, children }: CollectionsPopoverProp
114
115
  type="button"
115
116
  >
116
117
  <Plus className="h-3 w-3" />
117
- New
118
+ {i18n._({ id: 'editor.panels.collections.collectionsPopover.new', message: 'New' })}
118
119
  </button>
119
120
  </div>
120
121
 
@@ -132,7 +133,7 @@ export function CollectionsPopover({ nodeIds, children }: CollectionsPopoverProp
132
133
  setCreateName('')
133
134
  }
134
135
  }}
135
- placeholder="Collection name…"
136
+ placeholder={i18n._({ id: 'editor.panels.collections.collectionsPopover.collectionName', message: 'Collection name…' })}
136
137
  value={createName}
137
138
  />
138
139
  <button
@@ -162,7 +163,7 @@ export function CollectionsPopover({ nodeIds, children }: CollectionsPopoverProp
162
163
  <div className="flex flex-col items-center justify-center gap-2 px-4 py-8 text-center">
163
164
  <Layers className="h-6 w-6 text-muted-foreground/40" />
164
165
  <p className="text-muted-foreground text-xs">
165
- No collections yet. Create one to group elements together.
166
+ {i18n._({ id: 'editor.panels.collections.collectionsPopover.noCollectionsYetCreateOneTo', message: 'No collections yet. Create one to group elements together.' })}
166
167
  </p>
167
168
  </div>
168
169
  ) : (
@@ -182,8 +183,7 @@ export function CollectionsPopover({ nodeIds, children }: CollectionsPopoverProp
182
183
  key={collection.id}
183
184
  >
184
185
  <span className="truncate text-foreground/80 text-xs">
185
- Delete "{collection.name}"?
186
- </span>
186
+ {i18n._({ id: 'editor.panels.collections.collectionsPopover.deleteName', message: 'Delete "{name}"?', values: { name: collection.name } })}</span>
187
187
  <div className="flex shrink-0 items-center gap-1">
188
188
  <button
189
189
  className="rounded-md bg-red-500/20 px-2 py-0.5 font-medium text-[11px] text-red-600 dark:text-red-400 transition-colors hover:bg-red-500/30"
@@ -193,14 +193,14 @@ export function CollectionsPopover({ nodeIds, children }: CollectionsPopoverProp
193
193
  }}
194
194
  type="button"
195
195
  >
196
- Delete
196
+ {i18n._({ id: 'editor.panels.collections.collectionsPopover.delete', message: 'Delete' })}
197
197
  </button>
198
198
  <button
199
199
  className="rounded-md px-2 py-0.5 font-medium text-[11px] text-muted-foreground transition-colors hover:bg-black/6 dark:hover:bg-white/10"
200
200
  onClick={() => setDeletingId(null)}
201
201
  type="button"
202
202
  >
203
- Cancel
203
+ {i18n._({ id: 'editor.panels.collections.collectionsPopover.cancel', message: 'Cancel' })}
204
204
  </button>
205
205
  </div>
206
206
  </li>
@@ -314,14 +314,14 @@ export function CollectionsPopover({ nodeIds, children }: CollectionsPopoverProp
314
314
  }}
315
315
  >
316
316
  <Pencil className="h-3.5 w-3.5" />
317
- Rename
317
+ {i18n._({ id: 'editor.panels.collections.collectionsPopover.rename', message: 'Rename' })}
318
318
  </DropdownMenuItem>
319
319
  <DropdownMenuItem
320
320
  onClick={() => setDeletingId(collection.id)}
321
321
  variant="destructive"
322
322
  >
323
323
  <Trash2 className="h-3.5 w-3.5" />
324
- Delete
324
+ {i18n._({ id: 'editor.panels.collections.collectionsPopover.delete', message: 'Delete' })}
325
325
  </DropdownMenuItem>
326
326
  </DropdownMenuContent>
327
327
  </DropdownMenu>
@@ -0,0 +1,136 @@
1
+ 'use client'
2
+
3
+ import {
4
+ type AnyNodeId,
5
+ DECK_BOARD_GAPS,
6
+ DECK_BOARD_SIZES,
7
+ DECK_BOARD_STOCK,
8
+ type DeckBoardDirection,
9
+ type DeckBoardSize,
10
+ deckBoardSettings,
11
+ deckJoistSpacingLimit,
12
+ inchFractionLabel,
13
+ type MessageDescriptor,
14
+ setZoneIntent,
15
+ usePascalI18n,
16
+ useScene,
17
+ } from '@pascal-app/core'
18
+ import type { ReactNode } from 'react'
19
+ import { useShallow } from 'zustand/react/shallow'
20
+ import { applyRoomPlan } from '../../../lib/room-structure-commands'
21
+ import { SegmentedControl } from '../controls/segmented-control'
22
+
23
+ const IN = 0.0254
24
+
25
+ const BOARD_OPTIONS = DECK_BOARD_SIZES.map((value) => ({
26
+ label: DECK_BOARD_STOCK[value].label,
27
+ value,
28
+ }))
29
+
30
+ const GAP_OPTIONS = DECK_BOARD_GAPS.map((gap, index) => ({
31
+ label: `${inchFractionLabel(gap)} in`,
32
+ value: String(index),
33
+ }))
34
+
35
+ const RUN_OPTIONS: ReadonlyArray<{ label: MessageDescriptor; value: DeckBoardDirection }> = [
36
+ {
37
+ label: /* i18n */ {
38
+ id: 'editor.panels.deckBoardsFields.alongHouse',
39
+ message: 'Along house',
40
+ },
41
+ value: 'parallel',
42
+ },
43
+ {
44
+ label: /* i18n */ { id: 'editor.panels.deckBoardsFields.diagonal', message: 'Diagonal' },
45
+ value: 'diagonal',
46
+ },
47
+ {
48
+ label: /* i18n */ {
49
+ id: 'editor.panels.deckBoardsFields.outFromHouse',
50
+ message: 'Out from house',
51
+ },
52
+ value: 'perpendicular',
53
+ },
54
+ ]
55
+
56
+ function Field({ label, children }: { label: string; children: ReactNode }) {
57
+ return (
58
+ <div className="flex min-h-7 items-center justify-between gap-3">
59
+ <span className="shrink-0">{label}</span>
60
+ {children}
61
+ </div>
62
+ )
63
+ }
64
+
65
+ /**
66
+ * A deck's boards (`decking`, through core's `setZoneIntent`): their stock, the gap between them
67
+ * and the way they run against the house. The engine lays them over the deck's frame, and the
68
+ * frame follows: joists 12 in on centre under diagonal 5/4 or composite boards (IRC R507.7), and
69
+ * along the house on beams square to it under boards run out from it.
70
+ */
71
+ export function DeckBoardsFields({ zoneId }: { zoneId: string }) {
72
+ const i18n = usePascalI18n()
73
+ const deck = useScene(
74
+ useShallow((scene) => {
75
+ const zone = scene.nodes[zoneId as AnyNodeId]
76
+ if (zone?.type !== 'zone' || zone.roomType !== 'deck') return null
77
+ return deckBoardSettings(zone)
78
+ }),
79
+ )
80
+ if (!deck) return null
81
+ const set = (decking: NonNullable<Parameters<typeof setZoneIntent>[1]['patch']['decking']>) =>
82
+ applyRoomPlan(setZoneIntent(useScene.getState().nodes, { zoneId, patch: { decking } }))
83
+ const stock = DECK_BOARD_STOCK[deck.boardSize]
84
+ const gap = DECK_BOARD_GAPS.findIndex((option) => Math.abs(option - deck.gap) < 1e-6)
85
+ const spacing = Math.min(16 * IN, deckJoistSpacingLimit(deck))
86
+ return (
87
+ <div
88
+ className="mt-3 flex flex-col gap-1.5 text-muted-foreground text-xs"
89
+ data-deck-boards={deck.boardSize}
90
+ >
91
+ <h3 className="font-medium text-xs">
92
+ {i18n._({ id: 'editor.panels.deckBoardsFields.deckBoards', message: 'Deck boards' })}
93
+ </h3>
94
+ <Field label={i18n._({ id: 'editor.panels.deckBoardsFields.boards', message: 'Boards' })}>
95
+ <SegmentedControl
96
+ onChange={(boardSize: DeckBoardSize) => set({ boardSize })}
97
+ options={BOARD_OPTIONS}
98
+ value={deck.boardSize}
99
+ />
100
+ </Field>
101
+ <Field label={i18n._({ id: 'editor.panels.deckBoardsFields.gap', message: 'Gap' })}>
102
+ <SegmentedControl
103
+ mixed={gap < 0}
104
+ onChange={(index: string) => set({ gap: DECK_BOARD_GAPS[Number(index)] })}
105
+ options={GAP_OPTIONS}
106
+ value={String(Math.max(gap, 0))}
107
+ />
108
+ </Field>
109
+ <Field label={i18n._({ id: 'editor.panels.deckBoardsFields.run', message: 'Run' })}>
110
+ <SegmentedControl
111
+ onChange={(direction: DeckBoardDirection) => set({ direction })}
112
+ options={RUN_OPTIONS.map((option) => ({
113
+ label: i18n._(option.label),
114
+ value: option.value,
115
+ }))}
116
+ value={deck.direction}
117
+ />
118
+ </Field>
119
+ <p>
120
+ {i18n._({
121
+ id: 'editor.panels.deckBoardsFields.boardsSummary',
122
+ message:
123
+ '{label} boards {thickness} × {width} in, {gap} in apart, {direction, select, parallel {along the house, on joists square to it} diagonal {at 45° to the house} other {out from the house, on joists along it, carried by beams square to the house}}; joists {spacing} in on centre.',
124
+ values: {
125
+ label: stock.label,
126
+ thickness: inchFractionLabel(stock.thickness),
127
+ width: inchFractionLabel(stock.width),
128
+ gap: inchFractionLabel(deck.gap),
129
+ direction: deck.direction,
130
+ spacing: Math.round(spacing / IN),
131
+ },
132
+ })}
133
+ </p>
134
+ </div>
135
+ )
136
+ }