@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
@@ -38,15 +38,48 @@ describe('roomNameOptions', () => {
38
38
  })
39
39
  })
40
40
 
41
- test('free text is always offered first unless it is exactly a name', () => {
41
+ test('free text is offered first unless it is exactly a name, which is then active', () => {
42
42
  const { typed, options } = roomNameOptions('Wine cellar', SECTIONS)
43
- expect(typed).toEqual({ kind: 'typed', value: 'Wine cellar', match: null })
43
+ expect(typed).toEqual({ kind: 'typed', value: 'Wine cellar', label: 'Wine cellar', match: null })
44
44
  expect(options).toEqual([typed!])
45
- expect(roomNameOptions('Kitchen', SECTIONS).typed).toBeNull()
45
+ const exact = roomNameOptions('Kitchen', SECTIONS)
46
+ expect(exact.typed).toBeNull()
47
+ expect(exact.options[exact.active]).toMatchObject({ kind: 'suggestion', value: 'Kitchen' })
46
48
  // A case-only difference still offers the text as typed.
47
49
  expect(roomNameOptions('kitchen', SECTIONS).typed?.value).toBe('kitchen')
48
50
  })
49
51
 
52
+ test('an exact English name wins over names that contain it', () => {
53
+ const storage: RoomNameSection[] = [
54
+ { id: 'utility', title: 'Utility', names: ['Walk-in closet', 'Closet'] },
55
+ ]
56
+ for (const typed of ['Closet', 'closet']) {
57
+ const { options, active } = roomNameOptions(typed, storage)
58
+ expect(options[active]?.value).toBe('Closet')
59
+ }
60
+ })
61
+
62
+ test('a shared label is exact only for the room’s own entry', () => {
63
+ const shared: RoomNameSection[] = [
64
+ {
65
+ id: 'living',
66
+ title: 'Séjour',
67
+ names: ['Living room', 'Kitchen', 'Lounge'],
68
+ labels: ['Salon', 'Cuisine', 'Salon'],
69
+ },
70
+ ]
71
+ const activeValue = (current: string) => {
72
+ const { options, active } = roomNameOptions('Salon', shared, true, current)
73
+ return options[active]?.value
74
+ }
75
+ expect(activeValue('Lounge')).toBe('Lounge')
76
+ expect(activeValue('Lounge 2')).toBe('Lounge')
77
+ expect(activeValue('Living room')).toBe('Living room')
78
+ // Any other room falls back to the first suggestion.
79
+ expect(activeValue('Kitchen')).toBe('Living room')
80
+ expect(activeValue('')).toBe('Living room')
81
+ })
82
+
50
83
  test('the keyboard order is the typed row, then the groups in order', () => {
51
84
  const { groups, options } = roomNameOptions('e', SECTIONS)
52
85
  expect(options.slice(1)).toEqual(groups.flatMap((g) => g.options))
@@ -1,8 +1,22 @@
1
- import type { RoomNameSection } from '../../../lib/room-name-catalog'
1
+ import { type AnyNodeId, useScene } from '@pascal-app/core'
2
+ import {
3
+ baseRoomName,
4
+ numberedRoomName,
5
+ type RoomNameSection,
6
+ } from '../../../lib/room-name-catalog'
7
+ import {
8
+ type DescriptorTranslator,
9
+ displayRoomName,
10
+ editedRoomName,
11
+ } from '../../../lib/room-name-messages'
12
+ import { renameRoom } from '../../../lib/room-structure-commands'
2
13
 
3
14
  export type RoomNameOption = {
4
15
  kind: 'typed' | 'suggestion'
16
+ /** What a pick stores: the English catalog name, or the typed text as written. */
5
17
  value: string
18
+ /** What the row shows and the search matches, in the UI language. */
19
+ label: string
6
20
  match: { start: number; end: number } | null
7
21
  }
8
22
 
@@ -11,25 +25,37 @@ export type RoomNameGroup = { id: string; title: string; options: RoomNameOption
11
25
  /**
12
26
  * The room-name combobox's rows. Until the user types, every section is listed
13
27
  * whole — the field's current name is not a filter. Typing keeps, in every
14
- * section, the names containing the text (ignoring case and surrounding
15
- * spaces), drops the sections left empty, and puts the typed text first as its
16
- * own row unless it already is one of the names. `options` is the flat order
17
- * the keyboard walks: the typed row, then each group's rows.
28
+ * section, the names whose label or English name contains the text (ignoring
29
+ * case and surrounding spaces), drops the sections left empty, and puts the
30
+ * typed text first as its own row unless it is exactly an English catalog
31
+ * name. `options` is the flat order the keyboard walks: the typed row, then
32
+ * each group's rows. `current` is the room's stored name.
18
33
  */
19
34
  export function roomNameOptions(
20
35
  typed: string,
21
36
  sections: readonly RoomNameSection[],
22
37
  filtering = true,
23
- ): { typed: RoomNameOption | null; groups: RoomNameGroup[]; options: RoomNameOption[] } {
38
+ current = '',
39
+ ): {
40
+ typed: RoomNameOption | null
41
+ groups: RoomNameGroup[]
42
+ options: RoomNameOption[]
43
+ /** The row Enter picks after typing: an exact name, else the first suggestion. */
44
+ active: number
45
+ } {
24
46
  const query = filtering ? typed.trim().toLowerCase() : ''
25
47
  const groups = sections.flatMap((section) => {
26
- const options = section.names.flatMap((value): RoomNameOption[] => {
27
- const start = query ? value.toLowerCase().indexOf(query) : -1
28
- if (query && start < 0) return []
48
+ const options = section.names.flatMap((value, index): RoomNameOption[] => {
49
+ const label = section.labels?.[index] ?? value
50
+ // The label is what is shown and highlighted; the English name matches too,
51
+ // for packs that have not translated every name yet.
52
+ const start = query ? label.toLowerCase().indexOf(query) : -1
53
+ if (query && start < 0 && !value.toLowerCase().includes(query)) return []
29
54
  return [
30
55
  {
31
56
  kind: 'suggestion',
32
57
  value,
58
+ label,
33
59
  match: start >= 0 ? { start, end: start + query.length } : null,
34
60
  },
35
61
  ]
@@ -38,10 +64,83 @@ export function roomNameOptions(
38
64
  })
39
65
  const known = sections.some((section) => section.names.includes(typed))
40
66
  const typedRow: RoomNameOption | null =
41
- query && !known ? { kind: 'typed', value: typed, match: null } : null
67
+ query && !known ? { kind: 'typed', value: typed, label: typed, match: null } : null
68
+ const options = [...(typedRow ? [typedRow] : []), ...groups.flatMap((group) => group.options)]
69
+ const suggestions = options.filter((option) => option.kind === 'suggestion')
70
+ // Labels can be shared ("Salon" for both Living room and Lounge): a shared
71
+ // label is only an exact match for the room's own name.
72
+ const own = baseRoomName(current).toLowerCase()
73
+ const byLabel = suggestions.filter((option) => option.label.toLowerCase() === query)
74
+ const exactRow =
75
+ suggestions.find((option) => option.value.toLowerCase() === query) ??
76
+ (byLabel.length === 1
77
+ ? byLabel[0]
78
+ : byLabel.find((option) => option.value.toLowerCase() === own))
79
+ const exact = exactRow ? options.indexOf(exactRow) : -1
42
80
  return {
43
81
  typed: typedRow,
44
82
  groups,
45
- options: [...(typedRow ? [typedRow] : []), ...groups.flatMap((group) => group.options)],
83
+ options,
84
+ active: exact >= 0 ? exact : typedRow && options.length > 1 ? 1 : 0,
46
85
  }
47
86
  }
87
+
88
+ /**
89
+ * The commit of an edited room-name field, on blur or Enter (unless Enter
90
+ * picks a suggestion): the room panel's, and the layer tree's with its own
91
+ * `write`. `committed` is what the field last committed or showed; text still
92
+ * equal to it writes nothing.
93
+ */
94
+ export function commitRoomNameEdit(
95
+ zoneId: string,
96
+ name: string,
97
+ committed: string,
98
+ text: string,
99
+ i18n: DescriptorTranslator,
100
+ write: (zoneId: string, name: string) => void = renameRoom,
101
+ ) {
102
+ if (text === committed) return
103
+ const stored = editedRoomName(name, text, i18n)
104
+ if (stored !== name) write(zoneId, stored)
105
+ }
106
+
107
+ /**
108
+ * The layer tree's rename of a room or zone. Its field shows the name as
109
+ * `displayRoomName` does and passes the trimmed text, or `undefined` when
110
+ * cleared; it writes through `updateNode`, as main's tree does.
111
+ */
112
+ export function commitTreeRoomRename(
113
+ zoneId: string,
114
+ text: string | undefined,
115
+ i18n: DescriptorTranslator,
116
+ ) {
117
+ const { nodes, updateNode } = useScene.getState()
118
+ const id = zoneId as AnyNodeId
119
+ const name = nodes[id]?.name ?? ''
120
+ if (text === undefined) {
121
+ updateNode(id, { name: undefined })
122
+ return
123
+ }
124
+ commitRoomNameEdit(zoneId, name, displayRoomName(name, i18n), text, i18n, (_, stored) =>
125
+ updateNode(id, { name: stored }),
126
+ )
127
+ }
128
+
129
+ /**
130
+ * The room panel's pick of a row: a click, or Enter on a suggestion. A suggestion stores its English catalog name,
131
+ * numbered against the level's other rooms, in every UI language; the typed
132
+ * row stores the text exactly as typed. Returns what the field shows after it.
133
+ */
134
+ export function commitRoomNamePick(
135
+ zoneId: string,
136
+ name: string,
137
+ option: RoomNameOption,
138
+ i18n: DescriptorTranslator,
139
+ ): string {
140
+ const stored =
141
+ option.kind === 'typed'
142
+ ? option.value
143
+ : numberedRoomName(useScene.getState().nodes, zoneId, option.value)
144
+ if (stored !== name) renameRoom(zoneId, stored)
145
+ return displayRoomName(stored, i18n)
146
+ }
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { area, useScene } from '@pascal-app/core'
3
+ import { area, useScene, usePascalI18n } from '@pascal-app/core'
4
4
  import { useViewer } from '@pascal-app/viewer'
5
5
  import { useMemo } from 'react'
6
6
  import {
@@ -11,12 +11,10 @@ import {
11
11
  useFloorEditSession,
12
12
  } from '../../../lib/floor-edit-session'
13
13
  import { type OpeningSurface, startOpeningDraft } from '../../../lib/floor-opening-draft'
14
- import { formatAreaLabel } from '../../../lib/measurements'
14
+ import { formatAreaLabel } from '@pascal-app/core'
15
15
  import { pill } from './room-construction-rows'
16
16
  import { ShapeChoice } from './shape-choice'
17
17
 
18
- const count = (n: number) => `${n} ${n === 1 ? 'opening' : 'openings'}`
19
-
20
18
  /**
21
19
  * A room's openings through its floor (or its ceiling), inside the row's
22
20
  * settings. The floor has "Edit floor": a session that outlines the openings
@@ -31,6 +29,7 @@ export function RoomOpeningsField({
31
29
  zoneId: string
32
30
  surface: OpeningSurface
33
31
  }) {
32
+ const i18n = usePascalI18n()
34
33
  const nodes = useScene((scene) => scene.nodes)
35
34
  const unit = useViewer((s) => s.unit)
36
35
  const readOnly = useScene((scene) => scene.readOnly)
@@ -38,40 +37,46 @@ export function RoomOpeningsField({
38
37
  () => roomSurfaceOpenings(nodes, zoneId, surface),
39
38
  [nodes, zoneId, surface],
40
39
  )
40
+ const openingCount = i18n._({
41
+ id: 'editor.panels.roomOpeningsField.openings',
42
+ // {number} rather than #: counts are written without grouping, as before ("1000 openings").
43
+ message: '{count, plural, =0 {No openings} one {{number} opening} other {{number} openings}}',
44
+ values: { count: openings.length, number: String(openings.length) },
45
+ })
41
46
  const editing = useFloorEditSession((s) => surface === 'floor' && s.session?.zoneId === zoneId)
42
47
  const cut = (
43
48
  <ShapeChoice
44
49
  data={`cut-opening-${surface}`}
45
50
  disabled={readOnly}
46
- label="Cut opening"
51
+ label={i18n._({ id: 'editor.panels.roomOpeningsField.cutOpening', message: 'Cut opening' })}
47
52
  onPick={(shape) => startOpeningDraft(zoneId, surface, shape)}
48
53
  />
49
54
  )
50
55
  if (surface === 'ceiling')
51
56
  return (
52
57
  <div className="flex flex-col gap-1" data-room-openings={surface}>
53
- {openings.length > 0 && <span>{count(openings.length)}</span>}
58
+ {openings.length > 0 && <span>{openingCount}</span>}
54
59
  {cut}
55
60
  </div>
56
61
  )
57
62
  if (!editing)
58
63
  return (
59
64
  <div className="flex min-h-7 items-center justify-between gap-3" data-room-openings={surface}>
60
- <span>{openings.length ? count(openings.length) : 'No openings'}</span>
65
+ <span>{openingCount}</span>
61
66
  <button
62
67
  className={pill}
63
68
  data-edit-floor
64
69
  onClick={() => startFloorEdit(zoneId)}
65
70
  type="button"
66
71
  >
67
- Edit floor
72
+ {i18n._({ id: 'editor.panels.roomOpeningsField.editFloor', message: 'Edit floor' })}
68
73
  </button>
69
74
  </div>
70
75
  )
71
76
  return (
72
77
  <div className="flex flex-col gap-1.5" data-room-openings={surface} data-editing-floor>
73
78
  {openings.length === 0 ? (
74
- <p>No openings yet. Cut one for a stair void or a hatch.</p>
79
+ <p>{i18n._({ id: 'editor.panels.roomOpeningsField.noOpeningsYetCutOneFor', message: 'No openings yet. Cut one for a stair void or a hatch.' })}</p>
75
80
  ) : (
76
81
  openings.map((opening, index) => (
77
82
  <button
@@ -83,10 +88,14 @@ export function RoomOpeningsField({
83
88
  >
84
89
  <span className="text-foreground">
85
90
  {opening.source === 'stair'
86
- ? 'Stair opening'
91
+ ? i18n._({ id: 'editor.panels.roomOpeningsField.stairOpening', message: 'Stair opening' })
87
92
  : opening.source === 'elevator'
88
- ? 'Elevator opening'
89
- : `Opening ${index + 1}`}
93
+ ? i18n._({ id: 'editor.panels.roomOpeningsField.elevatorOpening', message: 'Elevator opening' })
94
+ : i18n._({
95
+ id: 'editor.panels.roomOpeningsField.openingNumber',
96
+ message: 'Opening {number}',
97
+ values: { number: index + 1 },
98
+ })}
90
99
  </span>
91
100
  <span className="font-mono tabular-nums">
92
101
  {formatAreaLabel(area([{ outer: opening.polygon, holes: [] }]), unit, 2)}
@@ -102,7 +111,7 @@ export function RoomOpeningsField({
102
111
  onClick={() => (finishFloorEdit() ? undefined : endFloorEdit())}
103
112
  type="button"
104
113
  >
105
- Done
114
+ {i18n._({ id: 'editor.panels.roomOpeningsField.done', message: 'Done' })}
106
115
  </button>
107
116
  </div>
108
117
  </div>