@pascal-app/editor 1.0.3 → 1.0.4

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 (440) hide show
  1. package/package.json +11 -9
  2. package/src/components/editor/action-menu-button.tsx +83 -0
  3. package/src/components/editor/bake-thumbnail.tsx +315 -4
  4. package/src/components/editor/capture-level-picker.tsx +72 -0
  5. package/src/components/editor/custom-camera-controls.tsx +202 -57
  6. package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
  7. package/src/components/editor/duplicate-node.ts +244 -0
  8. package/src/components/editor/editor-layout-mobile.tsx +36 -25
  9. package/src/components/editor/editor-layout-v2.tsx +131 -58
  10. package/src/components/editor/export-manager.tsx +34 -8
  11. package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
  12. package/src/components/editor/first-person/build-collider-world.ts +6 -2
  13. package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
  14. package/src/components/editor/first-person/mechanism-targets.ts +36 -0
  15. package/src/components/editor/first-person-controls.tsx +205 -2
  16. package/src/components/editor/floating-action-menu.tsx +63 -207
  17. package/src/components/editor/floor-aware-camera-controls.test.ts +442 -0
  18. package/src/components/editor/floor-aware-camera-controls.ts +324 -0
  19. package/src/components/editor/floor-openings-overlay.tsx +243 -0
  20. package/src/components/editor/floor-region-controls.tsx +846 -0
  21. package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
  22. package/src/components/editor/floor-surface.test.ts +165 -0
  23. package/src/components/editor/floor-surface.ts +182 -0
  24. package/src/components/editor/floorplan-background-selection.test.ts +11 -2
  25. package/src/components/editor/floorplan-background-selection.ts +1 -3
  26. package/src/components/editor/floorplan-panel.tsx +344 -342
  27. package/src/components/editor/footprint-height-handle.tsx +204 -0
  28. package/src/components/editor/group-actions.ts +54 -2
  29. package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
  30. package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
  31. package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
  32. package/src/components/editor/handles/use-handle-drag.ts +269 -197
  33. package/src/components/editor/index.tsx +294 -169
  34. package/src/components/editor/join-walls-pill.tsx +44 -0
  35. package/src/components/editor/node-action-menu.tsx +68 -70
  36. package/src/components/editor/node-arrow-handles.tsx +25 -6
  37. package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
  38. package/src/components/editor/open-wall-ends-hint.tsx +89 -0
  39. package/src/components/editor/quick-measurement-hud.tsx +3 -2
  40. package/src/components/editor/registry-action-contributions.test.tsx +63 -0
  41. package/src/components/editor/registry-action-contributions.tsx +80 -1
  42. package/src/components/editor/room-controls.tsx +847 -0
  43. package/src/components/editor/room-floor-highlight.tsx +128 -0
  44. package/src/components/editor/room-handles-2d.tsx +354 -0
  45. package/src/components/editor/room-handles.tsx +675 -0
  46. package/src/components/editor/room-highlight.tsx +225 -0
  47. package/src/components/editor/room-labels.test.ts +357 -0
  48. package/src/components/editor/room-labels.tsx +990 -0
  49. package/src/components/editor/room-transform-ghost.tsx +256 -0
  50. package/src/components/editor/room-webgpu-materials.test.ts +27 -0
  51. package/src/components/editor/selection-manager.tsx +451 -317
  52. package/src/components/editor/slab-hole-highlights.tsx +3 -0
  53. package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
  54. package/src/components/editor/snapshot-capture.test.ts +52 -4
  55. package/src/components/editor/snapshot-capture.ts +30 -5
  56. package/src/components/editor/thumbnail-generator.test.ts +308 -0
  57. package/src/components/editor/thumbnail-generator.tsx +1035 -229
  58. package/src/components/editor/use-floorplan-background-placement.ts +31 -11
  59. package/src/components/editor/vector-edge-extractor.tsx +121 -0
  60. package/src/components/editor/views/host-view.tsx +61 -0
  61. package/src/components/editor/views/plugins-view.tsx +670 -0
  62. package/src/components/editor/views/scene-region.tsx +59 -0
  63. package/src/components/editor/views/use-editor-views.ts +57 -0
  64. package/src/components/editor/views/view-bar.tsx +220 -0
  65. package/src/components/editor/wall-justification.test.ts +140 -0
  66. package/src/components/editor/wall-move-side-handles.tsx +43 -142
  67. package/src/components/editor/wall-push-arrow.tsx +92 -0
  68. package/src/components/editor/wall-region-handles.tsx +509 -0
  69. package/src/components/editor/wall-snap-beacon-layer.tsx +3 -0
  70. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
  71. package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
  72. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
  73. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
  74. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +113 -102
  75. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
  76. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
  77. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
  78. package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
  79. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
  80. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
  81. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +291 -227
  82. package/src/components/editor-2d/svg-paths.ts +12 -15
  83. package/src/components/procedural-items/inspector.tsx +40 -15
  84. package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
  85. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
  86. package/src/components/systems/zone/zone-system.test.ts +47 -0
  87. package/src/components/systems/zone/zone-system.tsx +29 -68
  88. package/src/components/tools/item/placement-history.test.tsx +474 -0
  89. package/src/components/tools/item/placement-strategies.ts +8 -4
  90. package/src/components/tools/item/use-draft-node.test.tsx +432 -1
  91. package/src/components/tools/item/use-draft-node.ts +166 -95
  92. package/src/components/tools/item/use-placement-coordinator.tsx +93 -46
  93. package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
  94. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
  95. package/src/components/tools/registry/move-registry-node-tool.tsx +3 -1
  96. package/src/components/tools/select/box-select-tool.tsx +2 -12
  97. package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
  98. package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
  99. package/src/components/tools/select/select-candidates.test.ts +37 -4
  100. package/src/components/tools/select/select-candidates.ts +15 -11
  101. package/src/components/tools/shared/cursor-sphere.tsx +36 -12
  102. package/src/components/tools/site/site-boundary-editor.tsx +1 -2
  103. package/src/components/tools/stair/stair-defaults.ts +0 -3
  104. package/src/components/tools/stair/stair-tool.tsx +90 -42
  105. package/src/components/tools/tool-manager.tsx +46 -20
  106. package/src/components/tools/wall/wall-drafting.test.ts +19 -4
  107. package/src/components/tools/zone/zone-tool.tsx +4 -12
  108. package/src/components/ui/action-menu/action-button.tsx +3 -0
  109. package/src/components/ui/action-menu/camera-actions.tsx +2 -1
  110. package/src/components/ui/action-menu/control-modes.tsx +2 -35
  111. package/src/components/ui/action-menu/measurement-control.tsx +3 -10
  112. package/src/components/ui/build-panel/build-panel.tsx +235 -0
  113. package/src/components/ui/command-palette/editor-commands.tsx +4 -30
  114. package/src/components/ui/command-palette/index.tsx +5 -24
  115. package/src/components/ui/controls/finish-swatch.tsx +110 -0
  116. package/src/components/ui/controls/material-paint-panel.tsx +97 -26
  117. package/src/components/ui/controls/material-picker.tsx +4 -1
  118. package/src/components/ui/controls/scene-material-list.tsx +4 -32
  119. package/src/components/ui/controls/select-control.tsx +65 -0
  120. package/src/components/ui/floating-level-selector.tsx +9 -14
  121. package/src/components/ui/helpers/building-helper.tsx +4 -1
  122. package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
  123. package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
  124. package/src/components/ui/helpers/helper-manager.tsx +325 -14
  125. package/src/components/ui/helpers/item-helper.tsx +8 -0
  126. package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
  127. package/src/components/ui/icon-ref.tsx +45 -1
  128. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  129. package/src/components/ui/node-icon-image.tsx +16 -0
  130. package/src/components/ui/panels/collections/collections-popover.tsx +20 -14
  131. package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
  132. package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
  133. package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
  134. package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
  135. package/src/components/ui/panels/multi-field-value.test.ts +26 -3
  136. package/src/components/ui/panels/multi-field-value.ts +6 -3
  137. package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
  138. package/src/components/ui/panels/multi-selection-panel.tsx +43 -4
  139. package/src/components/ui/panels/node-display.ts +8 -3
  140. package/src/components/ui/panels/panel-manager.tsx +103 -68
  141. package/src/components/ui/panels/panel-wrapper.tsx +78 -36
  142. package/src/components/ui/panels/parametric-field-control.tsx +1 -1
  143. package/src/components/ui/panels/parametric-inspector.tsx +4 -4
  144. package/src/components/ui/panels/reference-panel.tsx +2 -3
  145. package/src/components/ui/panels/room-construction-rows.tsx +774 -0
  146. package/src/components/ui/panels/room-name-options.test.ts +55 -0
  147. package/src/components/ui/panels/room-name-options.ts +47 -0
  148. package/src/components/ui/panels/room-openings-field.tsx +110 -0
  149. package/src/components/ui/panels/room-panel.tsx +725 -0
  150. package/src/components/ui/panels/shape-choice.tsx +58 -0
  151. package/src/components/ui/panels/wall-region-list.tsx +109 -0
  152. package/src/components/ui/primitives/color-dot.tsx +36 -24
  153. package/src/components/ui/primitives/sidebar.tsx +2 -0
  154. package/src/components/ui/right-stack.test.ts +29 -0
  155. package/src/components/ui/right-stack.tsx +145 -0
  156. package/src/components/ui/sidebar/icon-rail.tsx +5 -0
  157. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +31 -4
  158. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
  159. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
  160. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
  161. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
  162. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
  163. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
  164. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
  165. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
  166. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
  167. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
  168. package/src/components/ui/sidebar/panels/site-panel/index.tsx +176 -211
  169. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
  170. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
  171. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
  172. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
  173. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
  174. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
  175. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
  176. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
  177. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
  178. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
  179. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +0 -27
  180. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
  181. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
  182. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
  183. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
  184. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +0 -2
  185. package/src/components/ui/sidebar/tab-bar.tsx +10 -0
  186. package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
  187. package/src/components/viewer/floorplan-compass-button.tsx +9 -1
  188. package/src/components/viewer/floorplan-preview.tsx +5 -6
  189. package/src/components/viewer/viewer-hover-label.tsx +34 -0
  190. package/src/components/viewer/viewer-rooms.tsx +175 -0
  191. package/src/components/viewer/viewer-scene-header.tsx +181 -145
  192. package/src/components/viewer/viewer-selection-manager.tsx +124 -0
  193. package/src/components/viewer/viewer-stage.tsx +2 -2
  194. package/src/components/viewer-zone-system.tsx +3 -8
  195. package/src/hooks/use-auto-save.integration.test.tsx +123 -9
  196. package/src/hooks/use-auto-save.ts +34 -8
  197. package/src/hooks/use-keyboard.test.ts +178 -3
  198. package/src/hooks/use-keyboard.ts +89 -71
  199. package/src/hooks/use-open-wall-ends.test.ts +99 -0
  200. package/src/hooks/use-open-wall-ends.ts +238 -0
  201. package/src/hooks/use-placement-notice.test.ts +48 -0
  202. package/src/hooks/use-placement-notice.ts +42 -0
  203. package/src/hooks/use-selected-room.ts +143 -0
  204. package/src/index.tsx +246 -39
  205. package/src/lib/ceiling-edit-session.test.ts +321 -0
  206. package/src/lib/ceiling-edit-session.ts +206 -0
  207. package/src/lib/ceiling-region-session.test.ts +179 -0
  208. package/src/lib/continuation.test.ts +19 -1
  209. package/src/lib/continuation.ts +25 -4
  210. package/src/lib/door-interaction.ts +14 -0
  211. package/src/lib/editor-level-display.test.ts +96 -0
  212. package/src/lib/editor-level-display.ts +48 -0
  213. package/src/lib/editor-views.ts +144 -0
  214. package/src/lib/export-geometry-wait.test.ts +90 -0
  215. package/src/lib/floor-edit-session.ts +153 -0
  216. package/src/lib/floor-footprints.test.ts +563 -0
  217. package/src/lib/floor-footprints.ts +395 -0
  218. package/src/lib/floor-foundation-support.test.ts +283 -0
  219. package/src/lib/floor-opening-draft.ts +218 -0
  220. package/src/lib/floor-openings.test.ts +216 -0
  221. package/src/lib/floor-region-geometry.ts +191 -0
  222. package/src/lib/floor-region-session.test.ts +485 -0
  223. package/src/lib/floor-region-session.ts +589 -0
  224. package/src/lib/floor-region-snap.ts +101 -0
  225. package/src/lib/floor-step-hover.test.ts +32 -0
  226. package/src/lib/floorplan/annotation-visibility.ts +17 -0
  227. package/src/lib/floorplan/dimension-drive.test.ts +360 -0
  228. package/src/lib/floorplan/dimension-drive.ts +479 -0
  229. package/src/lib/floorplan/floorplan-export.test.ts +12 -8
  230. package/src/lib/floorplan/floorplan-export.tsx +32 -8
  231. package/src/lib/floorplan/floorplan-extension.ts +32 -0
  232. package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
  233. package/src/lib/floorplan/floorplan-mode.ts +2 -0
  234. package/src/lib/floorplan/floorplan-pdfkit-document.ts +339 -18
  235. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
  236. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -25
  237. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
  238. package/src/lib/floorplan/floorplan-readonly.ts +6 -0
  239. package/src/lib/floorplan/geometry.ts +13 -6
  240. package/src/lib/floorplan/index.ts +0 -9
  241. package/src/lib/floorplan/items.ts +5 -2
  242. package/src/lib/floorplan/marks.test.ts +213 -0
  243. package/src/lib/floorplan/marks.ts +238 -0
  244. package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
  245. package/src/lib/floorplan/open-wall-ends.ts +162 -0
  246. package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
  247. package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
  248. package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
  249. package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
  250. package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
  251. package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
  252. package/src/lib/floorplan/schedules.test.ts +189 -0
  253. package/src/lib/floorplan/schedules.ts +339 -0
  254. package/src/lib/floorplan/sheet-export.test.ts +100 -0
  255. package/src/lib/floorplan/sheet-export.ts +318 -0
  256. package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -0
  257. package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
  258. package/src/lib/floorplan/site-plan/contributors.ts +97 -0
  259. package/src/lib/floorplan/site-plan/coverage.ts +232 -0
  260. package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
  261. package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
  262. package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
  263. package/src/lib/floorplan/site-plan/front-edge.ts +156 -0
  264. package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
  265. package/src/lib/floorplan/site-plan/geometry.ts +557 -0
  266. package/src/lib/floorplan/site-plan/index.ts +71 -0
  267. package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
  268. package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
  269. package/src/lib/floorplan/site-plan/site-parts.ts +379 -0
  270. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
  271. package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
  272. package/src/lib/floorplan/site-plan/street-name.ts +302 -0
  273. package/src/lib/floorplan/site-plan/yards.test.ts +69 -0
  274. package/src/lib/floorplan/types.ts +1 -27
  275. package/src/lib/fresh-planar-placement.test.ts +36 -0
  276. package/src/lib/fresh-planar-placement.ts +59 -36
  277. package/src/lib/geometry-script/author.ts +152 -0
  278. package/src/lib/geometry-script/client.ts +152 -0
  279. package/src/lib/geometry-script/protocol.ts +12 -0
  280. package/src/lib/gesture-lifecycle.test.ts +595 -0
  281. package/src/lib/gesture-lifecycle.ts +290 -0
  282. package/src/lib/glb-export-zone-holes.test.ts +41 -0
  283. package/src/lib/glb-export.test.ts +485 -0
  284. package/src/lib/glb-export.ts +237 -119
  285. package/src/lib/history.test.ts +6 -0
  286. package/src/lib/history.ts +7 -0
  287. package/src/lib/hud-title.ts +109 -0
  288. package/src/lib/hydration-gated-storage.ts +49 -0
  289. package/src/lib/ifc-export.test.ts +174 -0
  290. package/src/lib/ifc-export.ts +201 -0
  291. package/src/lib/ifc-parts.ts +15 -0
  292. package/src/lib/inspector-expanded.test.ts +44 -0
  293. package/src/lib/inspector-expanded.ts +36 -0
  294. package/src/lib/interaction/scope.ts +18 -0
  295. package/src/lib/keyboard-pan.test.ts +129 -7
  296. package/src/lib/keyboard-pan.ts +161 -12
  297. package/src/lib/live-gesture-round-trip.test.ts +360 -0
  298. package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
  299. package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
  300. package/src/lib/lot/clean-ring.test.ts +186 -0
  301. package/src/lib/lot/clean-ring.ts +217 -0
  302. package/src/lib/lot/dossier.test.ts +202 -0
  303. package/src/lib/lot/dossier.ts +498 -0
  304. package/src/lib/lot/drop-in.test.ts +326 -0
  305. package/src/lib/lot/drop-in.ts +407 -0
  306. package/src/lib/lot/index.ts +43 -0
  307. package/src/lib/lot/lot-patch.test.ts +340 -0
  308. package/src/lib/lot/lot-patch.ts +394 -0
  309. package/src/lib/lot/parcel-provider.ts +43 -0
  310. package/src/lib/lot/terrain.test.ts +161 -0
  311. package/src/lib/lot/terrain.ts +266 -0
  312. package/src/lib/material-catalog-model.ts +3 -1
  313. package/src/lib/material-paint-panel-model.ts +9 -5
  314. package/src/lib/material-paint.ts +4 -0
  315. package/src/lib/mezzanine-actions.test.ts +342 -0
  316. package/src/lib/mezzanine-draft.test.ts +501 -0
  317. package/src/lib/mezzanine-draft.ts +278 -0
  318. package/src/lib/mezzanine-messages.ts +26 -0
  319. package/src/lib/mezzanine-selection.test.ts +207 -0
  320. package/src/lib/mezzanine-stairs.ts +46 -0
  321. package/src/lib/model-export.ts +3 -1
  322. package/src/lib/node-action-movement.ts +17 -6
  323. package/src/lib/node-display-label.ts +37 -0
  324. package/src/lib/orbit-floor-clearance.test.ts +165 -0
  325. package/src/lib/orbit-floor-clearance.ts +106 -0
  326. package/src/lib/paint-commit-route.test.ts +37 -0
  327. package/src/lib/paint-commit-route.ts +17 -0
  328. package/src/lib/paint-eyedropper.test.tsx +165 -0
  329. package/src/lib/paint-eyedropper.ts +99 -0
  330. package/src/lib/paint-pass-through.test.ts +111 -0
  331. package/src/lib/paint-pass-through.ts +76 -0
  332. package/src/lib/paint-region-hover.ts +25 -0
  333. package/src/lib/paint-region-mode.ts +154 -0
  334. package/src/lib/paint-regions.test.ts +166 -0
  335. package/src/lib/paint-regions.ts +192 -0
  336. package/src/lib/paint-scope.test.ts +317 -35
  337. package/src/lib/paint-scope.ts +266 -25
  338. package/src/lib/panel-rows.ts +21 -0
  339. package/src/lib/parametric-node-update.ts +1 -1
  340. package/src/lib/plate-paint-affected.test.ts +300 -0
  341. package/src/lib/plate-paint-affected.ts +177 -0
  342. package/src/lib/plugin-chat.ts +50 -0
  343. package/src/lib/plugin-panels.test.ts +25 -0
  344. package/src/lib/plugin-panels.ts +94 -0
  345. package/src/lib/print-shell-compiler.ts +1 -0
  346. package/src/lib/registered-draft-snap.ts +12 -0
  347. package/src/lib/roof-duplication.ts +10 -2
  348. package/src/lib/room-assembly-overlay.test.ts +275 -0
  349. package/src/lib/room-assembly-overlay.ts +266 -0
  350. package/src/lib/room-built-on.test.ts +379 -0
  351. package/src/lib/room-built-on.ts +138 -0
  352. package/src/lib/room-construction-commands.ts +712 -0
  353. package/src/lib/room-construction.test.ts +746 -0
  354. package/src/lib/room-construction.ts +385 -0
  355. package/src/lib/room-controls-phase6.test.ts +630 -0
  356. package/src/lib/room-divide-session.ts +523 -0
  357. package/src/lib/room-handle-drag.ts +1068 -0
  358. package/src/lib/room-name-catalog.test.ts +90 -0
  359. package/src/lib/room-name-catalog.ts +168 -0
  360. package/src/lib/room-push-dimensions.test.ts +311 -0
  361. package/src/lib/room-push-dimensions.ts +251 -0
  362. package/src/lib/room-selection-commands.ts +62 -0
  363. package/src/lib/room-selection.test.ts +973 -0
  364. package/src/lib/room-selection.ts +417 -0
  365. package/src/lib/room-structure-commands.ts +94 -0
  366. package/src/lib/room-structure.test.ts +921 -0
  367. package/src/lib/room-transform-session.ts +570 -0
  368. package/src/lib/room-zone-routing.test.ts +294 -0
  369. package/src/lib/room-zone-routing.ts +197 -0
  370. package/src/lib/scene-clipboard.test.ts +364 -0
  371. package/src/lib/scene-clipboard.ts +229 -48
  372. package/src/lib/scene-material-refs.test.ts +79 -0
  373. package/src/lib/scene-material-refs.ts +79 -0
  374. package/src/lib/scene.test.ts +24 -36
  375. package/src/lib/scene.ts +33 -57
  376. package/src/lib/selection-routing.test.ts +129 -45
  377. package/src/lib/selection-routing.ts +53 -74
  378. package/src/lib/session-writes.test.ts +120 -0
  379. package/src/lib/session-writes.ts +205 -0
  380. package/src/lib/sidebar-panel.ts +62 -0
  381. package/src/lib/site-boundary.test.ts +4 -6
  382. package/src/lib/site-boundary.ts +1 -1
  383. package/src/lib/snapping-mode.test.ts +4 -0
  384. package/src/lib/snapping-mode.ts +8 -1
  385. package/src/lib/terrace-draft.test.ts +145 -0
  386. package/src/lib/terrace-draft.ts +213 -0
  387. package/src/lib/tool-cursor-icon.test.ts +16 -0
  388. package/src/lib/tool-cursor-icon.ts +11 -0
  389. package/src/lib/typing-target.ts +9 -0
  390. package/src/lib/units.ts +77 -39
  391. package/src/lib/vector-edges.test.ts +58 -0
  392. package/src/lib/vector-edges.ts +400 -0
  393. package/src/lib/viewer-selection.test.ts +577 -0
  394. package/src/lib/viewer-selection.ts +569 -0
  395. package/src/lib/wall-draw-variant.test.ts +71 -0
  396. package/src/lib/wall-draw-variant.ts +91 -0
  397. package/src/lib/wall-paint-region-session.ts +246 -0
  398. package/src/lib/wall-paint-region.test.ts +373 -0
  399. package/src/lib/wall-polygon-draft.test.ts +262 -0
  400. package/src/lib/wall-polygon-draft.ts +286 -0
  401. package/src/lib/wall-push.test.ts +453 -0
  402. package/src/lib/wall-region-face.ts +352 -0
  403. package/src/lib/wall-region-handles.test.ts +304 -0
  404. package/src/lib/wall-region-handles.ts +368 -0
  405. package/src/lib/wall-region-list.test.ts +119 -0
  406. package/src/lib/wall-region-list.ts +136 -0
  407. package/src/lib/wall-region-snap.ts +110 -0
  408. package/src/lib/window-interaction.ts +14 -0
  409. package/src/lib/zone-content.ts +4 -1
  410. package/src/lib/zone-name.test.ts +17 -0
  411. package/src/lib/zone-name.ts +17 -0
  412. package/src/store/opens-in-select-mode.test.ts +223 -0
  413. package/src/store/paint-mode.test.ts +188 -0
  414. package/src/store/terrain-sculpt-mode.test.ts +19 -21
  415. package/src/store/tool-mode.test.ts +137 -6
  416. package/src/store/use-delete-confirmation.ts +28 -1
  417. package/src/store/use-drawing-view.test.ts +24 -3
  418. package/src/store/use-drawing-view.ts +29 -1
  419. package/src/store/use-editor.tsx +432 -321
  420. package/src/store/use-floorplan-draft-preview.ts +13 -0
  421. package/src/store/use-floorplan-marquee.ts +12 -2
  422. package/src/store/use-quick-measurement-hud.ts +3 -3
  423. package/src/store/use-stair-build-preview.ts +13 -5
  424. package/src/store/use-wall-move-ghosts.ts +4 -2
  425. package/src/store/use-wall-region-handles-selection.test.ts +222 -0
  426. package/src/store/use-wall-region-handles-selection.ts +139 -0
  427. package/src/store/view-layout.test.ts +175 -0
  428. package/src/store/view-layout.ts +196 -0
  429. package/src/test-utils/immediate-animation-frames.ts +16 -0
  430. package/src/test-utils/live-gesture-round-trip.ts +224 -0
  431. package/src/test-utils/selection-harness.ts +82 -0
  432. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
  433. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
  434. package/src/components/tools/fence/fence-drafting.test.ts +0 -100
  435. package/src/components/tools/fence/fence-drafting.ts +0 -266
  436. package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
  437. package/src/components/viewer/viewer-units-panel.tsx +0 -116
  438. package/src/lib/floorplan/stairs.ts +0 -489
  439. package/src/lib/level-duplication.test.ts +0 -144
  440. package/src/lib/level-duplication.ts +0 -195
@@ -0,0 +1,12 @@
1
+ import { expect, test } from 'bun:test'
2
+ import { readFileSync } from 'node:fs'
3
+
4
+ test('the floor region preview draws with WebGPU node materials, not drei fat lines', () => {
5
+ const source = readFileSync(new URL('./floor-region-controls.tsx', import.meta.url), 'utf8')
6
+ expect(source).not.toMatch(/@react-three\/drei/)
7
+ expect(source).not.toMatch(/<Line\b|<lineBasicMaterial\b|<meshBasicMaterial\b|\bLineMaterial\b/)
8
+ expect(source).toMatch(
9
+ /import\s*\{[^}]*\bLineBasicNodeMaterial\b[^}]*\bMeshBasicNodeMaterial\b[^}]*\}\s*from\s*['"]three\/webgpu['"]/,
10
+ )
11
+ expect(source).toContain('<lineSegments')
12
+ })
@@ -0,0 +1,165 @@
1
+ import { afterEach, expect, test } from 'bun:test'
2
+ import { type AnyNode, sceneRegistry, useScene } from '@pascal-app/core'
3
+ import * as THREE from 'three'
4
+ import { FloorSurface } from './floor-surface'
5
+
6
+ const GROUND = 'level_ground'
7
+ const UPPER = 'level_upper'
8
+
9
+ function node(id: string, type: string, parentId: string | null, extra: object = {}) {
10
+ return { id, type, parentId, visible: true, ...extra } as unknown as AnyNode
11
+ }
12
+
13
+ // A flat slab whose top is at `top`, spanning x/z from -5 to 5.
14
+ function slabMesh(top: number, thickness = 0.2) {
15
+ const mesh = new THREE.Mesh(
16
+ new THREE.BoxGeometry(10, thickness, 10),
17
+ new THREE.MeshBasicMaterial({ side: THREE.DoubleSide }),
18
+ )
19
+ mesh.position.set(0, top - thickness / 2, 0)
20
+ return mesh
21
+ }
22
+
23
+ function register(id: string, type: string, object: THREE.Object3D) {
24
+ sceneRegistry.nodes.set(id, object)
25
+ sceneRegistry.byType[type]!.add(id)
26
+ object.updateMatrixWorld(true)
27
+ }
28
+
29
+ function scene(nodes: AnyNode[]) {
30
+ useScene.setState({ nodes: Object.fromEntries(nodes.map((n) => [n.id, n])) as never })
31
+ }
32
+
33
+ function topAt(surface: FloorSurface, x = 0, z = 0, levelY = 0) {
34
+ void surface.revision
35
+ return surface.topAt(x, z, levelY)
36
+ }
37
+
38
+ afterEach(() => sceneRegistry.clear())
39
+
40
+ test('the highest floor of the active level under the camera counts, nothing else', () => {
41
+ scene([
42
+ node(GROUND, 'level', null, { level: 0 }),
43
+ node(UPPER, 'level', null, { level: 1 }),
44
+ node('slab_base', 'slab', GROUND),
45
+ node('slab_platform', 'slab', GROUND),
46
+ node('slab_upper', 'slab', UPPER),
47
+ node('wall_a', 'wall', GROUND),
48
+ node('ceiling_a', 'ceiling', GROUND),
49
+ ])
50
+ register('slab_base', 'slab', slabMesh(0.05))
51
+ register('slab_platform', 'slab', slabMesh(0.5))
52
+ register('slab_upper', 'slab', slabMesh(2.76))
53
+ register('wall_a', 'wall', slabMesh(2.5, 2.5))
54
+ register('ceiling_a', 'ceiling', slabMesh(2.5, 0.05))
55
+
56
+ const surface = new FloorSurface()
57
+ surface.levelId = GROUND
58
+ // The raised platform, not the base slab below it, nor the wall, ceiling or
59
+ // the level above.
60
+ expect(topAt(surface)).toBeCloseTo(0.5, 6)
61
+ // Off every slab: nothing under the camera.
62
+ expect(topAt(surface, 20, 20)).toBeNull()
63
+
64
+ surface.levelId = UPPER
65
+ expect(topAt(surface, 0, 0, 2.71)).toBeCloseTo(2.76, 6)
66
+ })
67
+
68
+ test('hidden slabs and items standing on a slab are not the floor', () => {
69
+ scene([
70
+ node(GROUND, 'level', null, { level: 0 }),
71
+ node('slab_base', 'slab', GROUND),
72
+ node('slab_hidden', 'slab', GROUND, { visible: false }),
73
+ node('item_table', 'item', 'slab_base'),
74
+ ])
75
+ const base = slabMesh(0.05)
76
+ register('slab_base', 'slab', base)
77
+ register('slab_hidden', 'slab', slabMesh(0.9))
78
+ const table = slabMesh(0.75 - 0.05, 0.05)
79
+ base.add(table)
80
+ register('item_table', 'item', table)
81
+
82
+ const surface = new FloorSurface()
83
+ surface.levelId = GROUND
84
+ expect(topAt(surface)).toBeCloseTo(0.05, 6)
85
+ })
86
+
87
+ test('on the ground level the site ground counts; on other levels it does not', () => {
88
+ scene([
89
+ node(GROUND, 'level', null, { level: 0 }),
90
+ node(UPPER, 'level', null, { level: 1 }),
91
+ node('site_a', 'site', null),
92
+ ])
93
+ const site = new THREE.Group()
94
+ site.add(slabMesh(-0.05, 0.01))
95
+ register('site_a', 'site', site)
96
+
97
+ const surface = new FloorSurface()
98
+ surface.levelId = GROUND
99
+ expect(topAt(surface, 3, 3)).toBeCloseTo(-0.05, 6)
100
+ surface.levelId = UPPER
101
+ expect(topAt(surface, 3, 3, 2.71)).toBeNull()
102
+ })
103
+
104
+ test('rebuilding a slab in place moves the revision so a cached answer is refreshed', () => {
105
+ scene([node(GROUND, 'level', null, { level: 0 }), node('slab_base', 'slab', GROUND)])
106
+ const slab = slabMesh(0.05)
107
+ register('slab_base', 'slab', slab)
108
+ const surface = new FloorSurface()
109
+ surface.levelId = GROUND
110
+ const before = surface.revision
111
+ slab.position.y += 0.4
112
+ slab.updateMatrixWorld(true)
113
+ expect(surface.revision).not.toBe(before)
114
+ expect(topAt(surface)).toBeCloseTo(0.45, 6)
115
+ })
116
+
117
+ test('a platform moved horizontally into a cached camera position refreshes the floor', () => {
118
+ scene([node(GROUND, 'level', null, { level: 0 }), node('slab_platform', 'slab', GROUND)])
119
+ const slab = slabMesh(1)
120
+ slab.position.x = 20
121
+ register('slab_platform', 'slab', slab)
122
+ const surface = new FloorSurface()
123
+ surface.levelId = GROUND
124
+ expect(topAt(surface)).toBeNull()
125
+ const before = surface.revision
126
+ slab.position.x = 0
127
+ slab.updateMatrixWorld(true)
128
+ expect(surface.revision).not.toBe(before)
129
+ expect(topAt(surface)).toBeCloseTo(1, 6)
130
+ })
131
+
132
+ test('resizing and hiding a mounted platform invalidate a cached floor height', () => {
133
+ scene([node(GROUND, 'level', null, { level: 0 }), node('slab_platform', 'slab', GROUND)])
134
+ const slab = slabMesh(1)
135
+ register('slab_platform', 'slab', slab)
136
+ const surface = new FloorSurface()
137
+ surface.levelId = GROUND
138
+ expect(topAt(surface)).toBeCloseTo(1, 6)
139
+ let before = surface.revision
140
+ slab.scale.y = 2
141
+ slab.updateMatrixWorld(true)
142
+ expect(surface.revision).not.toBe(before)
143
+ expect(topAt(surface)).toBeCloseTo(1.1, 6)
144
+ before = surface.revision
145
+ slab.visible = false
146
+ expect(surface.revision).not.toBe(before)
147
+ expect(topAt(surface)).toBeNull()
148
+ })
149
+
150
+ test('a floor mesh mounted or replaced after its site keeps the active surface current', () => {
151
+ scene([node(GROUND, 'level', null, { level: 0 }), node('site_a', 'site', null)])
152
+ const site = new THREE.Group()
153
+ register('site_a', 'site', site)
154
+ const surface = new FloorSurface()
155
+ surface.levelId = GROUND
156
+ expect(topAt(surface)).toBeNull()
157
+ const floor = slabMesh(0.05)
158
+ site.add(floor)
159
+ site.updateMatrixWorld(true)
160
+ expect(topAt(surface)).toBeCloseTo(0.05, 6)
161
+ site.remove(floor)
162
+ site.add(slabMesh(0.5))
163
+ site.updateMatrixWorld(true)
164
+ expect(topAt(surface)).toBeCloseTo(0.5, 6)
165
+ })
@@ -0,0 +1,182 @@
1
+ import { type AnyNode, type AnyNodeId, sceneRegistry, useScene } from '@pascal-app/core'
2
+ import { BATCHED_LAYER, SCENE_LAYER } from '@pascal-app/viewer'
3
+ import { type Intersection, type Mesh, type Object3D, Raycaster, Vector3 } from 'three'
4
+
5
+ // Drawn by the scene pass or by a merged batch that stands in for the mesh.
6
+ const DRAWN_LAYERS_MASK = (1 << SCENE_LAYER) | (1 << BATCHED_LAYER)
7
+ // High enough above any level's floor that a raised platform is below it.
8
+ const CAST_HEIGHT = 50
9
+ const down = new Vector3(0, -1, 0)
10
+ const origin = new Vector3()
11
+
12
+ function isMesh(object: Object3D): object is Mesh {
13
+ return (object as Mesh).isMesh === true
14
+ }
15
+
16
+ function isDrawn(mesh: Mesh) {
17
+ if ((mesh.layers.mask & DRAWN_LAYERS_MASK) === 0) return false
18
+ for (let current: Object3D | null = mesh; current; current = current.parent) {
19
+ if (!current.visible) return false
20
+ }
21
+ return true
22
+ }
23
+
24
+ function levelOf(nodes: Record<AnyNodeId, AnyNode>, id: string): AnyNode | null {
25
+ let node: AnyNode | undefined = nodes[id as AnyNodeId]
26
+ while (node) {
27
+ if (node.type === 'level') return node
28
+ node = node.parentId ? nodes[node.parentId as AnyNodeId] : undefined
29
+ }
30
+ return null
31
+ }
32
+
33
+ /**
34
+ * The walkable surface of the active level: its floor slabs and plates
35
+ * (raised platforms and sunken floors included) and, on the ground level, the
36
+ * site's ground and terrain. Walls, items, ceilings, roofs and other levels
37
+ * are not in it. The mesh list is rebuilt only when the scene, the registry
38
+ * or the level changes; `revision` moves with it and with any geometry or
39
+ * transform change, so callers can cache answers between moves.
40
+ */
41
+ export class FloorSurface {
42
+ /** The level whose floors count. */
43
+ levelId: string | null = null
44
+ private readonly raycaster = new Raycaster()
45
+ private readonly hits: Intersection[] = []
46
+ private meshes: Mesh[] = []
47
+ private objects: { object: Object3D; children: Object3D[] }[] = []
48
+ private builtFor: { registry: number; nodes: unknown; levelId: string | null } | null = null
49
+ private meshStates: {
50
+ geometry: Mesh['geometry']
51
+ position: unknown
52
+ positionVersion: number
53
+ index: unknown
54
+ indexVersion: number
55
+ matrix: number[]
56
+ drawn: boolean
57
+ }[] = []
58
+ private version = 0
59
+
60
+ get revision() {
61
+ this.refresh()
62
+ return this.version
63
+ }
64
+
65
+ /** Height of the highest floor surface at `x`/`z`, or null over nothing. */
66
+ topAt(x: number, z: number, levelY: number): number | null {
67
+ origin.set(x, levelY + CAST_HEIGHT, z)
68
+ this.raycaster.set(origin, down)
69
+ this.raycaster.far = Number.POSITIVE_INFINITY
70
+ let top: number | null = null
71
+ for (const mesh of this.meshes) {
72
+ if (!isDrawn(mesh)) continue
73
+ this.hits.length = 0
74
+ mesh.raycast(this.raycaster, this.hits)
75
+ for (const hit of this.hits) if (top === null || hit.point.y > top) top = hit.point.y
76
+ }
77
+ this.hits.length = 0
78
+ return top
79
+ }
80
+
81
+ private refresh() {
82
+ const nodes = useScene.getState().nodes
83
+ const registry = sceneRegistry.revision
84
+ const built = this.builtFor
85
+ let structureChanged = false
86
+ for (const { object, children } of this.objects) {
87
+ if (object.children.length !== children.length) {
88
+ structureChanged = true
89
+ break
90
+ }
91
+ for (let i = 0; i < children.length; i++) {
92
+ if (object.children[i] === children[i]) continue
93
+ structureChanged = true
94
+ break
95
+ }
96
+ if (structureChanged) break
97
+ }
98
+ if (
99
+ !built ||
100
+ built.registry !== registry ||
101
+ built.nodes !== nodes ||
102
+ built.levelId !== this.levelId ||
103
+ structureChanged
104
+ ) {
105
+ this.builtFor = { registry, nodes, levelId: this.levelId }
106
+ this.meshes = this.collect(nodes)
107
+ this.meshStates = this.meshes.map((mesh) => ({
108
+ geometry: mesh.geometry,
109
+ position: null,
110
+ positionVersion: -1,
111
+ index: null,
112
+ indexVersion: -1,
113
+ matrix: mesh.matrixWorld.elements.slice(),
114
+ drawn: isDrawn(mesh),
115
+ }))
116
+ this.version += 1
117
+ }
118
+ // Geometry is rebuilt after the node change that caused it, in place.
119
+ let changed = false
120
+ for (let i = 0; i < this.meshes.length; i++) {
121
+ const mesh = this.meshes[i]!
122
+ const state = this.meshStates[i]!
123
+ const position = mesh.geometry.getAttribute('position')
124
+ const positionVersion = position && 'version' in position ? position.version : 0
125
+ const index = mesh.geometry.index
126
+ const indexVersion = index?.version ?? 0
127
+ const drawn = isDrawn(mesh)
128
+ if (
129
+ state.geometry !== mesh.geometry ||
130
+ state.position !== position ||
131
+ state.positionVersion !== positionVersion ||
132
+ state.index !== index ||
133
+ state.indexVersion !== indexVersion ||
134
+ state.drawn !== drawn
135
+ ) {
136
+ state.geometry = mesh.geometry
137
+ state.position = position
138
+ state.positionVersion = positionVersion
139
+ state.index = index
140
+ state.indexVersion = indexVersion
141
+ state.drawn = drawn
142
+ changed = true
143
+ }
144
+ for (let j = 0; j < 16; j++) {
145
+ const value = mesh.matrixWorld.elements[j]!
146
+ if (state.matrix[j] === value) continue
147
+ state.matrix[j] = value
148
+ changed = true
149
+ }
150
+ }
151
+ if (changed) this.version += 1
152
+ }
153
+
154
+ private collect(nodes: Record<AnyNodeId, AnyNode>) {
155
+ const meshes: Mesh[] = []
156
+ this.objects = []
157
+ const level = this.levelId ? nodes[this.levelId as AnyNodeId] : undefined
158
+ if (level?.type !== 'level') return meshes
159
+
160
+ const nodeObjects = new Set(sceneRegistry.nodes.values())
161
+ const collect = (object: Object3D, root: Object3D) => {
162
+ // A slab's hosted items and a site's buildings are not its surface.
163
+ if (object !== root && nodeObjects.has(object)) return
164
+ this.objects.push({ object, children: object.children.slice() })
165
+ if (isMesh(object)) meshes.push(object)
166
+ for (const child of object.children) collect(child, root)
167
+ }
168
+ for (const id of sceneRegistry.byType.slab ?? []) {
169
+ if (nodes[id as AnyNodeId]?.visible === false) continue
170
+ if (levelOf(nodes, id)?.id !== level.id) continue
171
+ const object = sceneRegistry.nodes.get(id)
172
+ if (object) collect(object, object)
173
+ }
174
+ if (level.level === 0) {
175
+ for (const id of sceneRegistry.byType.site ?? []) {
176
+ const object = sceneRegistry.nodes.get(id)
177
+ if (object) collect(object, object)
178
+ }
179
+ }
180
+ return meshes
181
+ }
182
+ }
@@ -9,7 +9,6 @@ const baseArgs = {
9
9
  isWallBuildActive: false,
10
10
  modifierKeys: { meta: false, ctrl: false, shift: false, alt: false },
11
11
  planPoint: [0, 0] as [number, number],
12
- structureLayer: 'elements',
13
12
  }
14
13
 
15
14
  describe('resolveFloorplanBackgroundSelection', () => {
@@ -87,7 +86,6 @@ describe('resolveFloorplanBackgroundSelection', () => {
87
86
  canSelectElementFloorplanGeometry: false,
88
87
  canSelectFloorplanZones: true,
89
88
  getFloorplanHitIdAtPoint: () => 'zone_1',
90
- structureLayer: 'zones',
91
89
  })
92
90
 
93
91
  expect(result).toEqual({
@@ -96,4 +94,15 @@ describe('resolveFloorplanBackgroundSelection', () => {
96
94
  zoneId: 'zone_1',
97
95
  })
98
96
  })
97
+
98
+ test('a miss while zones are pickable (unit focus) clears the zone selection', () => {
99
+ const result = resolveFloorplanBackgroundSelection({
100
+ ...baseArgs,
101
+ canSelectElementFloorplanGeometry: false,
102
+ canSelectFloorplanZones: true,
103
+ getFloorplanHitIdAtPoint: () => null,
104
+ })
105
+
106
+ expect(result).toEqual({ handled: true, kind: 'clear-zones' })
107
+ })
99
108
  })
@@ -21,7 +21,6 @@ type ResolveFloorplanBackgroundSelectionArgs = {
21
21
  isWallBuildActive: boolean
22
22
  modifierKeys: ModifierKeys
23
23
  planPoint: WallPlanPoint
24
- structureLayer: string
25
24
  }
26
25
 
27
26
  function hasToggleModifier(modifierKeys: ModifierKeys): boolean {
@@ -77,7 +76,6 @@ export function resolveFloorplanBackgroundSelection({
77
76
  isWallBuildActive,
78
77
  modifierKeys,
79
78
  planPoint,
80
- structureLayer,
81
79
  }: ResolveFloorplanBackgroundSelectionArgs): FloorplanBackgroundSelectionResult {
82
80
  if (canSelectFloorplanZones) {
83
81
  const zoneId = getFloorplanHitIdAtPoint(planPoint)
@@ -102,7 +100,7 @@ export function resolveFloorplanBackgroundSelection({
102
100
  }
103
101
 
104
102
  if (!isWallBuildActive) {
105
- if (structureLayer === 'zones') {
103
+ if (canSelectFloorplanZones) {
106
104
  return {
107
105
  handled: true,
108
106
  kind: 'clear-zones',