@pascal-app/editor 1.0.3 → 1.0.5

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 (624) hide show
  1. package/README.md +106 -0
  2. package/package.json +42 -38
  3. package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
  4. package/src/components/editor/action-menu-button.tsx +83 -0
  5. package/src/components/editor/bake-thumbnail.tsx +307 -5
  6. package/src/components/editor/capture-level-picker.tsx +72 -0
  7. package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
  8. package/src/components/editor/construction-reveal-audio.tsx +40 -0
  9. package/src/components/editor/custom-camera-controls.tsx +275 -57
  10. package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
  11. package/src/components/editor/duplicate-node.ts +244 -0
  12. package/src/components/editor/editor-layout-mobile.tsx +37 -26
  13. package/src/components/editor/editor-layout-v2.tsx +132 -59
  14. package/src/components/editor/export-manager.tsx +47 -26
  15. package/src/components/editor/first-person/build-collider-world.test.ts +320 -0
  16. package/src/components/editor/first-person/build-collider-world.ts +28 -7
  17. package/src/components/editor/first-person/ground-holes.test.ts +128 -0
  18. package/src/components/editor/first-person/ground-holes.ts +130 -0
  19. package/src/components/editor/first-person/mechanism-targets.test.ts +162 -0
  20. package/src/components/editor/first-person/mechanism-targets.ts +88 -0
  21. package/src/components/editor/first-person-controls.tsx +220 -9
  22. package/src/components/editor/floating-action-menu.tsx +63 -207
  23. package/src/components/editor/floor-aware-camera-controls.test.ts +466 -0
  24. package/src/components/editor/floor-aware-camera-controls.ts +324 -0
  25. package/src/components/editor/floor-openings-overlay.tsx +243 -0
  26. package/src/components/editor/floor-region-controls.tsx +846 -0
  27. package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
  28. package/src/components/editor/floor-surface.test.ts +165 -0
  29. package/src/components/editor/floor-surface.ts +182 -0
  30. package/src/components/editor/floorplan-background-selection.test.ts +11 -2
  31. package/src/components/editor/floorplan-background-selection.ts +1 -3
  32. package/src/components/editor/floorplan-mode-coordinator.tsx +2 -2
  33. package/src/components/editor/floorplan-panel.tsx +414 -363
  34. package/src/components/editor/follow-pascal.test.tsx +912 -0
  35. package/src/components/editor/follow-pascal.tsx +354 -0
  36. package/src/components/editor/footprint-height-handle.tsx +204 -0
  37. package/src/components/editor/group-actions.ts +56 -2
  38. package/src/components/editor/group-move-3d.ts +2 -0
  39. package/src/components/editor/group-rotate-handle.tsx +2 -0
  40. package/src/components/editor/group-transform-shared.test.ts +460 -0
  41. package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
  42. package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
  43. package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
  44. package/src/components/editor/handles/use-handle-drag.ts +269 -197
  45. package/src/components/editor/index.tsx +409 -197
  46. package/src/components/editor/join-walls-pill.tsx +44 -0
  47. package/src/components/editor/node-action-menu.tsx +68 -70
  48. package/src/components/editor/node-arrow-handles.tsx +25 -6
  49. package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
  50. package/src/components/editor/open-wall-ends-hint.tsx +89 -0
  51. package/src/components/editor/quick-measurement-hud.tsx +3 -2
  52. package/src/components/editor/registry-action-contributions.test.tsx +63 -0
  53. package/src/components/editor/registry-action-contributions.tsx +80 -1
  54. package/src/components/editor/riser-diagram-panel.tsx +14 -7
  55. package/src/components/editor/room-controls.tsx +847 -0
  56. package/src/components/editor/room-floor-highlight.tsx +128 -0
  57. package/src/components/editor/room-handles-2d.tsx +354 -0
  58. package/src/components/editor/room-handles.tsx +675 -0
  59. package/src/components/editor/room-highlight.tsx +233 -0
  60. package/src/components/editor/room-labels.test.ts +850 -0
  61. package/src/components/editor/room-labels.tsx +1890 -0
  62. package/src/components/editor/room-transform-ghost.tsx +256 -0
  63. package/src/components/editor/room-webgpu-materials.test.ts +27 -0
  64. package/src/components/editor/selection-manager.tsx +474 -419
  65. package/src/components/editor/slab-hole-highlights.tsx +5 -2
  66. package/src/components/editor/snapshot-capture-overlay.tsx +7 -2
  67. package/src/components/editor/snapshot-capture.test.ts +119 -60
  68. package/src/components/editor/snapshot-capture.ts +36 -5
  69. package/src/components/editor/thumbnail-generator.test.ts +654 -0
  70. package/src/components/editor/thumbnail-generator.tsx +1103 -223
  71. package/src/components/editor/use-floorplan-background-placement.ts +34 -12
  72. package/src/components/editor/vector-edge-extractor.tsx +121 -0
  73. package/src/components/editor/views/host-view.tsx +61 -0
  74. package/src/components/editor/views/plugins-view.tsx +663 -0
  75. package/src/components/editor/views/scene-region.tsx +59 -0
  76. package/src/components/editor/views/use-editor-views.ts +57 -0
  77. package/src/components/editor/views/view-bar.test.tsx +54 -0
  78. package/src/components/editor/views/view-bar.tsx +343 -0
  79. package/src/components/editor/wall-justification.test.ts +140 -0
  80. package/src/components/editor/wall-move-side-handles.tsx +43 -142
  81. package/src/components/editor/wall-push-arrow.tsx +92 -0
  82. package/src/components/editor/wall-region-handles.tsx +509 -0
  83. package/src/components/editor/wall-snap-beacon-layer.tsx +8 -4
  84. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
  85. package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
  86. package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
  87. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
  88. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +2 -2
  89. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
  90. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +116 -106
  91. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
  92. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
  93. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
  94. package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
  95. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
  96. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
  97. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +319 -233
  98. package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
  99. package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
  100. package/src/components/editor-2d/svg-paths.ts +12 -15
  101. package/src/components/point-ask/bubble.tsx +538 -0
  102. package/src/components/point-ask/hover-chip.tsx +91 -0
  103. package/src/components/point-ask/mode-pill.tsx +92 -0
  104. package/src/components/point-ask/optional-motion.ts +22 -0
  105. package/src/components/point-ask/outline-corners.tsx +93 -0
  106. package/src/components/point-ask/pins.tsx +711 -0
  107. package/src/components/point-ask/point-ask-layer.tsx +83 -0
  108. package/src/components/point-ask/point-ask-styles.ts +105 -0
  109. package/src/components/point-ask/region-marquee-styles.ts +15 -0
  110. package/src/components/point-ask/region-marquee.tsx +64 -0
  111. package/src/components/procedural-items/inspector.tsx +40 -15
  112. package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
  113. package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
  114. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
  115. package/src/components/systems/roof/roof-edit-system.tsx +1 -0
  116. package/src/components/systems/zone/zone-system.test.ts +47 -0
  117. package/src/components/systems/zone/zone-system.tsx +29 -68
  118. package/src/components/tools/distribution/connection-compatibility.test.ts +34 -0
  119. package/src/components/tools/distribution/connection-compatibility.ts +43 -0
  120. package/src/components/tools/distribution/distribution-public.ts +9 -0
  121. package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
  122. package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
  123. package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
  124. package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
  125. package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
  126. package/src/components/tools/distribution/draw-alignment.ts +35 -0
  127. package/src/components/tools/distribution/ports.test.ts +56 -0
  128. package/src/components/tools/distribution/ports.ts +378 -0
  129. package/src/components/tools/distribution/run-cursor.test.ts +135 -0
  130. package/src/components/tools/distribution/run-cursor.ts +36 -0
  131. package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
  132. package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
  133. package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
  134. package/src/components/tools/distribution/run-port-snap.ts +25 -0
  135. package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
  136. package/src/components/tools/distribution/run-screen-direction.ts +30 -0
  137. package/src/components/tools/elevator/elevator-tool.tsx +3 -6
  138. package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
  139. package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
  140. package/src/components/tools/item/placement-history.test.tsx +512 -0
  141. package/src/components/tools/item/placement-strategies.test.ts +46 -1
  142. package/src/components/tools/item/placement-strategies.ts +11 -10
  143. package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
  144. package/src/components/tools/item/use-draft-node.test.tsx +432 -1
  145. package/src/components/tools/item/use-draft-node.ts +166 -95
  146. package/src/components/tools/item/use-placement-coordinator.tsx +148 -95
  147. package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
  148. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
  149. package/src/components/tools/registry/item-surface-move.test.ts +62 -63
  150. package/src/components/tools/registry/item-surface-move.ts +2 -0
  151. package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
  152. package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
  153. package/src/components/tools/registry/move-registry-node-tool.tsx +66 -30
  154. package/src/components/tools/select/box-select-tool.tsx +2 -12
  155. package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
  156. package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
  157. package/src/components/tools/select/select-candidates.test.ts +37 -4
  158. package/src/components/tools/select/select-candidates.ts +15 -11
  159. package/src/components/tools/shared/cursor-sphere.tsx +36 -12
  160. package/src/components/tools/site/site-boundary-editor.tsx +1 -2
  161. package/src/components/tools/tool-manager.tsx +59 -24
  162. package/src/components/tools/wall/wall-drafting.test.ts +175 -47
  163. package/src/components/tools/zone/zone-tool.tsx +4 -12
  164. package/src/components/ui/action-menu/action-button.tsx +9 -1
  165. package/src/components/ui/action-menu/camera-actions.tsx +9 -4
  166. package/src/components/ui/action-menu/control-modes.tsx +15 -42
  167. package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
  168. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
  169. package/src/components/ui/action-menu/index.tsx +56 -33
  170. package/src/components/ui/action-menu/measurement-control.tsx +14 -19
  171. package/src/components/ui/action-menu/placement.ts +20 -0
  172. package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
  173. package/src/components/ui/action-menu/view-toggles.tsx +47 -42
  174. package/src/components/ui/build-panel/build-panel.tsx +242 -0
  175. package/src/components/ui/command-palette/editor-commands.tsx +7 -42
  176. package/src/components/ui/command-palette/index.tsx +50 -39
  177. package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
  178. package/src/components/ui/controls/action-button.tsx +5 -2
  179. package/src/components/ui/controls/finish-swatch.tsx +110 -0
  180. package/src/components/ui/controls/material-paint-panel.tsx +97 -26
  181. package/src/components/ui/controls/material-picker.tsx +4 -1
  182. package/src/components/ui/controls/metric-control.tsx +1 -1
  183. package/src/components/ui/controls/scene-material-list.tsx +4 -32
  184. package/src/components/ui/controls/segmented-control.tsx +21 -8
  185. package/src/components/ui/controls/select-control.tsx +65 -0
  186. package/src/components/ui/controls/slider-control.tsx +1 -1
  187. package/src/components/ui/controls/toggle-control.tsx +3 -3
  188. package/src/components/ui/controls/tool-options-panel.tsx +1 -1
  189. package/src/components/ui/floating-level-selector.tsx +159 -59
  190. package/src/components/ui/helpers/building-helper.tsx +4 -1
  191. package/src/components/ui/helpers/contextual-helper-panel.tsx +181 -72
  192. package/src/components/ui/helpers/helper-manager.test.tsx +121 -0
  193. package/src/components/ui/helpers/helper-manager.tsx +331 -14
  194. package/src/components/ui/helpers/item-helper.tsx +17 -1
  195. package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
  196. package/src/components/ui/icon-ref.tsx +45 -1
  197. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  198. package/src/components/ui/node-icon-image.tsx +16 -0
  199. package/src/components/ui/panels/collections/collections-popover.tsx +28 -22
  200. package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
  201. package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
  202. package/src/components/ui/panels/mobile-panel-sheet.tsx +4 -4
  203. package/src/components/ui/panels/mobile-selection-bar.tsx +8 -5
  204. package/src/components/ui/panels/multi-field-value.test.ts +26 -3
  205. package/src/components/ui/panels/multi-field-value.ts +6 -3
  206. package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
  207. package/src/components/ui/panels/multi-selection-panel.tsx +161 -5
  208. package/src/components/ui/panels/node-display.ts +8 -3
  209. package/src/components/ui/panels/panel-manager.tsx +103 -68
  210. package/src/components/ui/panels/panel-wrapper.tsx +130 -62
  211. package/src/components/ui/panels/parametric-field-control.tsx +3 -3
  212. package/src/components/ui/panels/parametric-inspector.tsx +7 -7
  213. package/src/components/ui/panels/reference-panel.tsx +2 -3
  214. package/src/components/ui/panels/room-construction-rows.tsx +774 -0
  215. package/src/components/ui/panels/room-name-options.test.ts +55 -0
  216. package/src/components/ui/panels/room-name-options.ts +47 -0
  217. package/src/components/ui/panels/room-openings-field.tsx +110 -0
  218. package/src/components/ui/panels/room-panel.tsx +790 -0
  219. package/src/components/ui/panels/shape-choice.tsx +58 -0
  220. package/src/components/ui/panels/wall-region-list.tsx +109 -0
  221. package/src/components/ui/primitives/badge.tsx +8 -0
  222. package/src/components/ui/primitives/color-dot.tsx +36 -24
  223. package/src/components/ui/primitives/sidebar.tsx +3 -1
  224. package/src/components/ui/right-stack.test.ts +29 -0
  225. package/src/components/ui/right-stack.tsx +164 -0
  226. package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
  227. package/src/components/ui/sidebar/icon-rail.tsx +8 -3
  228. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  229. package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
  230. package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
  231. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
  232. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
  233. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
  234. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +138 -27
  235. package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
  236. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
  237. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
  238. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
  239. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
  240. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
  241. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
  242. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
  243. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
  244. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
  245. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
  246. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
  247. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
  248. package/src/components/ui/sidebar/panels/site-panel/index.tsx +181 -223
  249. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
  250. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
  251. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
  252. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
  253. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
  254. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
  255. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
  256. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
  257. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
  258. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
  259. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +13 -37
  260. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
  261. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
  262. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
  263. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
  264. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +2 -4
  265. package/src/components/ui/sidebar/tab-bar.tsx +14 -4
  266. package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
  267. package/src/components/viewer/floorplan-compass-button.tsx +9 -1
  268. package/src/components/viewer/floorplan-preview.tsx +5 -6
  269. package/src/components/viewer/viewer-controls-bar.tsx +18 -6
  270. package/src/components/viewer/viewer-hover-label.tsx +34 -0
  271. package/src/components/viewer/viewer-rooms.tsx +191 -0
  272. package/src/components/viewer/viewer-scene-header.tsx +181 -145
  273. package/src/components/viewer/viewer-selection-manager.tsx +124 -0
  274. package/src/components/viewer/viewer-stage.tsx +2 -2
  275. package/src/components/viewer-zone-system.tsx +3 -8
  276. package/src/hooks/use-auto-save.integration.test.tsx +215 -43
  277. package/src/hooks/use-auto-save.ts +60 -9
  278. package/src/hooks/use-ceiling-events.test.ts +123 -118
  279. package/src/hooks/use-grid-events.test.ts +105 -105
  280. package/src/hooks/use-keyboard.test.ts +243 -19
  281. package/src/hooks/use-keyboard.ts +118 -83
  282. package/src/hooks/use-open-wall-ends.test.ts +99 -0
  283. package/src/hooks/use-open-wall-ends.ts +238 -0
  284. package/src/hooks/use-placement-notice.test.ts +71 -0
  285. package/src/hooks/use-placement-notice.ts +73 -0
  286. package/src/hooks/use-selected-room.ts +143 -0
  287. package/src/index.tsx +312 -58
  288. package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
  289. package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
  290. package/src/lib/add-storey.test.ts +177 -0
  291. package/src/lib/add-storey.ts +59 -0
  292. package/src/lib/below-grade.test.tsx +171 -0
  293. package/src/lib/below-grade.ts +133 -0
  294. package/src/lib/camera-facing.test.ts +24 -0
  295. package/src/lib/camera-facing.ts +24 -0
  296. package/src/lib/ceiling-edit-session.test.ts +321 -0
  297. package/src/lib/ceiling-edit-session.ts +206 -0
  298. package/src/lib/ceiling-region-session.test.ts +179 -0
  299. package/src/lib/continuation.test.ts +19 -1
  300. package/src/lib/continuation.ts +25 -4
  301. package/src/lib/direct-manipulation.ts +2 -0
  302. package/src/lib/door-interaction.ts +14 -0
  303. package/src/lib/download-project-file.ts +17 -0
  304. package/src/lib/editor-level-display.test.ts +96 -0
  305. package/src/lib/editor-level-display.ts +55 -0
  306. package/src/lib/editor-views.ts +178 -0
  307. package/src/lib/export-geometry-wait.test.ts +90 -0
  308. package/src/lib/export-texture-utils.ts +2 -4
  309. package/src/lib/floor-edit-session.ts +153 -0
  310. package/src/lib/floor-footprints.test.ts +563 -0
  311. package/src/lib/floor-footprints.ts +395 -0
  312. package/src/lib/floor-foundation-support.test.ts +284 -0
  313. package/src/lib/floor-opening-draft.ts +218 -0
  314. package/src/lib/floor-openings.test.ts +216 -0
  315. package/src/lib/floor-region-geometry.ts +191 -0
  316. package/src/lib/floor-region-session.test.ts +485 -0
  317. package/src/lib/floor-region-session.ts +589 -0
  318. package/src/lib/floor-region-snap.ts +101 -0
  319. package/src/lib/floor-step-hover.test.ts +32 -0
  320. package/src/lib/floorplan/annotation-visibility.ts +17 -0
  321. package/src/lib/floorplan/dimension-drive.test.ts +360 -0
  322. package/src/lib/floorplan/dimension-drive.ts +479 -0
  323. package/src/lib/floorplan/floorplan-export.test.ts +12 -8
  324. package/src/lib/floorplan/floorplan-export.tsx +32 -8
  325. package/src/lib/floorplan/floorplan-extension.ts +32 -0
  326. package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
  327. package/src/lib/floorplan/floorplan-mode.ts +2 -0
  328. package/src/lib/floorplan/floorplan-pdfkit-document.ts +363 -27
  329. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +177 -8
  330. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +60 -25
  331. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
  332. package/src/lib/floorplan/floorplan-readonly.ts +6 -0
  333. package/src/lib/floorplan/geometry.ts +13 -6
  334. package/src/lib/floorplan/index.ts +0 -9
  335. package/src/lib/floorplan/items.ts +5 -2
  336. package/src/lib/floorplan/marks.test.ts +213 -0
  337. package/src/lib/floorplan/marks.ts +238 -0
  338. package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
  339. package/src/lib/floorplan/open-wall-ends.ts +162 -0
  340. package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
  341. package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
  342. package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
  343. package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
  344. package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
  345. package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
  346. package/src/lib/floorplan/pdfkit-standard-fonts.d.ts +5 -0
  347. package/src/lib/floorplan/schedules.test.ts +189 -0
  348. package/src/lib/floorplan/schedules.ts +339 -0
  349. package/src/lib/floorplan/sheet-export.test.ts +124 -0
  350. package/src/lib/floorplan/sheet-export.ts +318 -0
  351. package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +768 -0
  352. package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
  353. package/src/lib/floorplan/site-plan/contributors.ts +97 -0
  354. package/src/lib/floorplan/site-plan/coverage.ts +230 -0
  355. package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
  356. package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
  357. package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
  358. package/src/lib/floorplan/site-plan/front-edge.ts +157 -0
  359. package/src/lib/floorplan/site-plan/geometry.test.ts +269 -0
  360. package/src/lib/floorplan/site-plan/geometry.ts +142 -0
  361. package/src/lib/floorplan/site-plan/index.ts +55 -0
  362. package/src/lib/floorplan/site-plan/site-annotations.test.ts +483 -0
  363. package/src/lib/floorplan/site-plan/site-annotations.ts +1084 -0
  364. package/src/lib/floorplan/site-plan/site-parts.ts +287 -0
  365. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
  366. package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
  367. package/src/lib/floorplan/site-plan/street-name.ts +302 -0
  368. package/src/lib/floorplan/site-plan/yards.test.ts +70 -0
  369. package/src/lib/floorplan/types.ts +1 -27
  370. package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
  371. package/src/lib/follow-pascal-exterior.test.ts +171 -0
  372. package/src/lib/follow-pascal-exterior.ts +39 -0
  373. package/src/lib/follow-pascal-presentation.test.ts +203 -0
  374. package/src/lib/follow-pascal-presentation.ts +129 -0
  375. package/src/lib/follow-pascal-sight.ts +84 -0
  376. package/src/lib/follow-pascal-targets.test.ts +406 -0
  377. package/src/lib/follow-pascal-targets.ts +198 -0
  378. package/src/lib/follow-pascal.test.ts +350 -0
  379. package/src/lib/follow-pascal.ts +284 -0
  380. package/src/lib/fresh-planar-placement.test.ts +148 -0
  381. package/src/lib/fresh-planar-placement.ts +114 -37
  382. package/src/lib/geometry-script/author.ts +157 -0
  383. package/src/lib/geometry-script/client.ts +159 -0
  384. package/src/lib/geometry-script/finish-scripts.ts +127 -0
  385. package/src/lib/geometry-script/protocol.ts +13 -0
  386. package/src/lib/gesture-lifecycle.test.ts +595 -0
  387. package/src/lib/gesture-lifecycle.ts +290 -0
  388. package/src/lib/glb-export-zone-holes.test.ts +41 -0
  389. package/src/lib/glb-export.test.ts +636 -126
  390. package/src/lib/glb-export.ts +241 -120
  391. package/src/lib/history.test.ts +18 -7
  392. package/src/lib/history.ts +7 -0
  393. package/src/lib/hud-title.ts +109 -0
  394. package/src/lib/hydration-gated-storage.ts +49 -0
  395. package/src/lib/ifc-export.test.ts +174 -0
  396. package/src/lib/ifc-export.ts +201 -0
  397. package/src/lib/ifc-parts.ts +15 -0
  398. package/src/lib/inspector-expanded.test.ts +44 -0
  399. package/src/lib/inspector-expanded.ts +36 -0
  400. package/src/lib/interaction/scope.ts +22 -0
  401. package/src/lib/keyboard-pan.test.ts +129 -7
  402. package/src/lib/keyboard-pan.ts +161 -12
  403. package/src/lib/level-selection.test.ts +57 -0
  404. package/src/lib/level-selection.ts +16 -1
  405. package/src/lib/live-gesture-round-trip.test.ts +360 -0
  406. package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
  407. package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
  408. package/src/lib/lot/clean-ring.test.ts +187 -0
  409. package/src/lib/lot/clean-ring.ts +217 -0
  410. package/src/lib/lot/dossier.test.ts +202 -0
  411. package/src/lib/lot/dossier.ts +498 -0
  412. package/src/lib/lot/drop-in.test.ts +326 -0
  413. package/src/lib/lot/drop-in.ts +392 -0
  414. package/src/lib/lot/index.ts +43 -0
  415. package/src/lib/lot/lot-patch.test.ts +340 -0
  416. package/src/lib/lot/lot-patch.ts +394 -0
  417. package/src/lib/lot/parcel-provider.ts +43 -0
  418. package/src/lib/lot/terrain.test.ts +161 -0
  419. package/src/lib/lot/terrain.ts +266 -0
  420. package/src/lib/material-catalog-model.ts +3 -1
  421. package/src/lib/material-paint-panel-model.ts +9 -5
  422. package/src/lib/material-paint.ts +4 -0
  423. package/src/lib/measurement-kind.test.ts +6 -8
  424. package/src/lib/measurements.test.ts +23 -0
  425. package/src/lib/mezzanine-actions.test.ts +342 -0
  426. package/src/lib/mezzanine-draft.test.ts +501 -0
  427. package/src/lib/mezzanine-draft.ts +278 -0
  428. package/src/lib/mezzanine-messages.ts +26 -0
  429. package/src/lib/mezzanine-selection.test.ts +207 -0
  430. package/src/lib/mezzanine-stairs.ts +46 -0
  431. package/src/lib/model-export.ts +3 -1
  432. package/src/lib/node-action-movement.ts +17 -6
  433. package/src/lib/node-display-label.ts +37 -0
  434. package/src/lib/orbit-floor-clearance.test.ts +201 -0
  435. package/src/lib/orbit-floor-clearance.ts +154 -0
  436. package/src/lib/paint-commit-route.test.ts +37 -0
  437. package/src/lib/paint-commit-route.ts +17 -0
  438. package/src/lib/paint-eyedropper.test.tsx +165 -0
  439. package/src/lib/paint-eyedropper.ts +99 -0
  440. package/src/lib/paint-pass-through.test.ts +122 -0
  441. package/src/lib/paint-pass-through.ts +78 -0
  442. package/src/lib/paint-region-hover.ts +25 -0
  443. package/src/lib/paint-region-mode.ts +154 -0
  444. package/src/lib/paint-regions.test.ts +166 -0
  445. package/src/lib/paint-regions.ts +192 -0
  446. package/src/lib/paint-scope.test.ts +317 -35
  447. package/src/lib/paint-scope.ts +267 -25
  448. package/src/lib/panel-rows.ts +21 -0
  449. package/src/lib/parametric-node-update.ts +16 -7
  450. package/src/lib/plate-paint-affected.test.ts +300 -0
  451. package/src/lib/plate-paint-affected.ts +177 -0
  452. package/src/lib/plugin-chat.ts +65 -0
  453. package/src/lib/plugin-panels.test.ts +25 -0
  454. package/src/lib/plugin-panels.ts +94 -0
  455. package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
  456. package/src/lib/point-ask/__fixtures__/house.ts +81 -0
  457. package/src/lib/point-ask/ask-created.test.ts +195 -0
  458. package/src/lib/point-ask/ask-flow.test.ts +380 -0
  459. package/src/lib/point-ask/build-context.test.ts +173 -0
  460. package/src/lib/point-ask/build-context.ts +94 -0
  461. package/src/lib/point-ask/capture-crop.ts +129 -0
  462. package/src/lib/point-ask/choreography.ts +69 -0
  463. package/src/lib/point-ask/compare.test.ts +145 -0
  464. package/src/lib/point-ask/compare.ts +73 -0
  465. package/src/lib/point-ask/describe-target.test.ts +165 -0
  466. package/src/lib/point-ask/describe-target.ts +234 -0
  467. package/src/lib/point-ask/dictation.ts +79 -0
  468. package/src/lib/point-ask/edge-marker.test.ts +61 -0
  469. package/src/lib/point-ask/edge-marker.ts +27 -0
  470. package/src/lib/point-ask/frame-loop.ts +25 -0
  471. package/src/lib/point-ask/history-marks.test.ts +94 -0
  472. package/src/lib/point-ask/history-marks.ts +39 -0
  473. package/src/lib/point-ask/host-api.ts +32 -0
  474. package/src/lib/point-ask/outline-box.test.ts +30 -0
  475. package/src/lib/point-ask/outline-box.ts +11 -0
  476. package/src/lib/point-ask/overlay-metrics.ts +6 -0
  477. package/src/lib/point-ask/pick.test.ts +220 -0
  478. package/src/lib/point-ask/pick.ts +121 -0
  479. package/src/lib/point-ask/pin-machine.test.ts +494 -0
  480. package/src/lib/point-ask/pin-machine.ts +217 -0
  481. package/src/lib/point-ask/placement.test.ts +203 -0
  482. package/src/lib/point-ask/placement.ts +141 -0
  483. package/src/lib/point-ask/pointer-tracker.ts +34 -0
  484. package/src/lib/point-ask/projector.ts +83 -0
  485. package/src/lib/point-ask/region.test.ts +157 -0
  486. package/src/lib/point-ask/region.ts +136 -0
  487. package/src/lib/point-ask/retry.test.ts +217 -0
  488. package/src/lib/point-ask/retry.ts +37 -0
  489. package/src/lib/point-ask/scene-commit.test.ts +312 -0
  490. package/src/lib/point-ask/scene-commit.ts +32 -0
  491. package/src/lib/point-ask/session.ts +1391 -0
  492. package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
  493. package/src/lib/point-ask/spring-keyframes.ts +38 -0
  494. package/src/lib/point-ask/spring.test.ts +63 -0
  495. package/src/lib/point-ask/spring.ts +107 -0
  496. package/src/lib/point-ask/suggestions.test.ts +80 -0
  497. package/src/lib/point-ask/suggestions.ts +125 -0
  498. package/src/lib/point-ask/target-scope.test.ts +622 -0
  499. package/src/lib/point-ask/target-scope.ts +338 -0
  500. package/src/lib/point-ask/types.ts +48 -0
  501. package/src/lib/portable-export.test.ts +34 -1
  502. package/src/lib/portable-export.ts +119 -53
  503. package/src/lib/print-feature-thickness.ts +10 -1
  504. package/src/lib/print-roof-solids.test.ts +39 -1
  505. package/src/lib/print-roof-solids.ts +43 -10
  506. package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
  507. package/src/lib/print-shell-compiler.ts +1 -0
  508. package/src/lib/registered-draft-snap.ts +12 -0
  509. package/src/lib/roof-duplication.ts +10 -2
  510. package/src/lib/room-assembly-overlay.test.ts +275 -0
  511. package/src/lib/room-assembly-overlay.ts +265 -0
  512. package/src/lib/room-built-on.test.ts +379 -0
  513. package/src/lib/room-built-on.ts +138 -0
  514. package/src/lib/room-construction-commands.ts +712 -0
  515. package/src/lib/room-construction.test.ts +746 -0
  516. package/src/lib/room-construction.ts +385 -0
  517. package/src/lib/room-controls-phase6.test.ts +630 -0
  518. package/src/lib/room-divide-session.ts +523 -0
  519. package/src/lib/room-handle-drag.ts +1068 -0
  520. package/src/lib/room-name-catalog.test.ts +90 -0
  521. package/src/lib/room-name-catalog.ts +168 -0
  522. package/src/lib/room-push-dimensions.test.ts +311 -0
  523. package/src/lib/room-push-dimensions.ts +251 -0
  524. package/src/lib/room-selection-commands.ts +62 -0
  525. package/src/lib/room-selection.test.ts +1017 -0
  526. package/src/lib/room-selection.ts +417 -0
  527. package/src/lib/room-structure-commands.ts +94 -0
  528. package/src/lib/room-structure.test.ts +921 -0
  529. package/src/lib/room-transform-session.ts +570 -0
  530. package/src/lib/room-zone-routing.test.ts +294 -0
  531. package/src/lib/room-zone-routing.ts +197 -0
  532. package/src/lib/scene-clipboard.test.ts +610 -1
  533. package/src/lib/scene-clipboard.ts +269 -58
  534. package/src/lib/scene-material-refs.test.ts +79 -0
  535. package/src/lib/scene-material-refs.ts +79 -0
  536. package/src/lib/scene.test.ts +24 -36
  537. package/src/lib/scene.ts +33 -57
  538. package/src/lib/selection-routing.test.ts +129 -45
  539. package/src/lib/selection-routing.ts +54 -75
  540. package/src/lib/session-writes.test.ts +120 -0
  541. package/src/lib/session-writes.ts +205 -0
  542. package/src/lib/sfx-bus.ts +13 -0
  543. package/src/lib/sfx-player.test.ts +70 -9
  544. package/src/lib/sfx-player.ts +6 -0
  545. package/src/lib/sidebar-panel.ts +62 -0
  546. package/src/lib/site-boundary.test.ts +4 -6
  547. package/src/lib/site-boundary.ts +1 -1
  548. package/src/lib/snapping-mode.test.ts +4 -0
  549. package/src/lib/snapping-mode.ts +8 -1
  550. package/src/lib/stair-levels.test.ts +25 -0
  551. package/src/lib/stair-levels.ts +22 -3
  552. package/src/lib/terrace-draft.test.ts +145 -0
  553. package/src/lib/terrace-draft.ts +214 -0
  554. package/src/lib/tool-cursor-icon.test.ts +16 -0
  555. package/src/lib/tool-cursor-icon.ts +11 -0
  556. package/src/lib/typing-target.ts +9 -0
  557. package/src/lib/units.ts +77 -39
  558. package/src/lib/vector-edges.test.ts +58 -0
  559. package/src/lib/vector-edges.ts +400 -0
  560. package/src/lib/viewer-selection.test.ts +577 -0
  561. package/src/lib/viewer-selection.ts +580 -0
  562. package/src/lib/wall-draw-variant.test.ts +71 -0
  563. package/src/lib/wall-draw-variant.ts +91 -0
  564. package/src/lib/wall-paint-region-session.ts +246 -0
  565. package/src/lib/wall-paint-region.test.ts +373 -0
  566. package/src/lib/wall-placement-pointer.test.ts +42 -0
  567. package/src/lib/wall-placement-pointer.ts +57 -0
  568. package/src/lib/wall-polygon-draft.test.ts +262 -0
  569. package/src/lib/wall-polygon-draft.ts +286 -0
  570. package/src/lib/wall-push.test.ts +453 -0
  571. package/src/lib/wall-region-face.ts +352 -0
  572. package/src/lib/wall-region-handles.test.ts +304 -0
  573. package/src/lib/wall-region-handles.ts +368 -0
  574. package/src/lib/wall-region-list.test.ts +119 -0
  575. package/src/lib/wall-region-list.ts +136 -0
  576. package/src/lib/wall-region-snap.ts +110 -0
  577. package/src/lib/window-changes.ts +35 -0
  578. package/src/lib/window-interaction.ts +14 -0
  579. package/src/lib/zone-content.ts +4 -1
  580. package/src/lib/zone-name.test.ts +17 -0
  581. package/src/lib/zone-name.ts +17 -0
  582. package/src/store/opens-in-select-mode.test.ts +223 -0
  583. package/src/store/paint-mode.test.ts +188 -0
  584. package/src/store/terrain-sculpt-mode.test.ts +19 -21
  585. package/src/store/tool-mode.test.ts +137 -6
  586. package/src/store/use-audio.test.ts +146 -0
  587. package/src/store/use-audio.tsx +127 -3
  588. package/src/store/use-construction-reveal.test.ts +173 -0
  589. package/src/store/use-construction-reveal.ts +145 -0
  590. package/src/store/use-corner-snap-hint.test.ts +24 -0
  591. package/src/store/use-corner-snap-hint.ts +47 -0
  592. package/src/store/use-delete-confirmation.ts +28 -1
  593. package/src/store/use-drawing-view.test.ts +24 -3
  594. package/src/store/use-drawing-view.ts +29 -1
  595. package/src/store/use-editor.tsx +432 -321
  596. package/src/store/use-floorplan-draft-preview.ts +13 -0
  597. package/src/store/use-floorplan-marquee.ts +12 -2
  598. package/src/store/use-follow-pascal.test.ts +62 -0
  599. package/src/store/use-follow-pascal.ts +53 -0
  600. package/src/store/use-point-ask.ts +247 -0
  601. package/src/store/use-quick-measurement-hud.ts +3 -3
  602. package/src/store/use-stair-build-preview.ts +33 -5
  603. package/src/store/use-wall-move-ghosts.ts +4 -2
  604. package/src/store/use-wall-region-handles-selection.test.ts +222 -0
  605. package/src/store/use-wall-region-handles-selection.ts +139 -0
  606. package/src/store/view-layout.test.ts +201 -0
  607. package/src/store/view-layout.ts +199 -0
  608. package/src/test-utils/immediate-animation-frames.ts +16 -0
  609. package/src/test-utils/live-gesture-round-trip.ts +224 -0
  610. package/src/test-utils/render-current-markup.tsx +16 -0
  611. package/src/test-utils/selection-harness.ts +82 -0
  612. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
  613. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
  614. package/src/components/tools/fence/fence-drafting.test.ts +0 -100
  615. package/src/components/tools/fence/fence-drafting.ts +0 -266
  616. package/src/components/tools/stair/stair-click-guard.test.ts +0 -58
  617. package/src/components/tools/stair/stair-click-guard.ts +0 -72
  618. package/src/components/tools/stair/stair-defaults.ts +0 -18
  619. package/src/components/tools/stair/stair-tool.tsx +0 -634
  620. package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
  621. package/src/components/viewer/viewer-units-panel.tsx +0 -116
  622. package/src/lib/floorplan/stairs.ts +0 -489
  623. package/src/lib/level-duplication.test.ts +0 -144
  624. package/src/lib/level-duplication.ts +0 -195
@@ -0,0 +1,551 @@
1
+ 'use client'
2
+
3
+ import {
4
+ type AnyNode,
5
+ type AnyNodeId,
6
+ type FloorFoundationPatch,
7
+ floorFootprintName,
8
+ floorFootprintSupportClass,
9
+ upperFloorHeightControl,
10
+ getLevelDisplayName,
11
+ getLevelElevations,
12
+ getStoredLevelHeight,
13
+ type LevelNode,
14
+ MIN_GROUND_FLOOR_THICKNESS,
15
+ MIN_SLAB_THICKNESS,
16
+ type SlabNode,
17
+ useScene,
18
+ } from '@pascal-app/core'
19
+ import { useViewer } from '@pascal-app/viewer'
20
+ import { useRef, useState } from 'react'
21
+ import {
22
+ applyFloorFoundation,
23
+ beginFootprintHeightPreview,
24
+ beginFootprintThicknessPreview,
25
+ type FootprintPreset,
26
+ footprintHeightMinimum,
27
+ footprintHeightPatch,
28
+ footprintHeightValue,
29
+ footprintPreset,
30
+ footprintsToMoveTogether,
31
+ floorTopAboveGround,
32
+ presetPatch,
33
+ THICK_FLOOR_HINT,
34
+ thickFloorAdvice,
35
+ } from '../../../lib/floor-footprints'
36
+ import { formatLinearMeasurement } from '../../../lib/measurements'
37
+ import { FinishSwatch } from '../controls/finish-swatch'
38
+ import { MetricControl } from '../controls/metric-control'
39
+ import { SegmentedControl } from '../controls/segmented-control'
40
+ import { Button } from '../primitives/button'
41
+ import { PanelWrapper } from './panel-wrapper'
42
+
43
+ /** Unpainted finishes, as the plate renders them (nodes' slab slot defaults). */
44
+ const EDGE_DEFAULT = '#cccccc'
45
+ const FOUNDATION_DEFAULT = 'library:concrete-raw'
46
+
47
+ /**
48
+ * A side section through the footprint's edge, top to bottom in the order it
49
+ * is built. On the ground: the floor top, the slab, the foundation when the
50
+ * footprint is raised on one, the ground — the slab always sits on the
51
+ * foundation (or the ground), so the top is their sum. Upstairs: the floor
52
+ * plate resting on the walls of the storey below. Drawn to a readable scale,
53
+ * not to size.
54
+ */
55
+ export function FloorSection({
56
+ supported,
57
+ raised,
58
+ lift = 0,
59
+ liftLabel = '',
60
+ foundationHeight = 0,
61
+ foundationLabel = '',
62
+ topLabel,
63
+ thickness,
64
+ thicknessLabel,
65
+ }: {
66
+ /** Upstairs (or over a basement): it rests on the walls below, never on a foundation. */
67
+ supported: boolean
68
+ /** On a foundation (ground-bearing footprints only). */
69
+ raised: boolean
70
+ /** Upstairs: meters the top sits above resting on the walls below. */
71
+ lift?: number
72
+ liftLabel?: string
73
+ /** On the ground: the foundation's height, meters. */
74
+ foundationHeight?: number
75
+ foundationLabel?: string
76
+ /** On the ground: the floor top above the ground (foundation + slab). */
77
+ topLabel?: string
78
+ thickness: number
79
+ thicknessLabel: string
80
+ }) {
81
+ const base = 100
82
+ const foundation = !supported && raised ? Math.min(40, 14 + foundationHeight * 30) : 0
83
+ const band = Math.max(7, Math.min(16, thickness * 60))
84
+ // Upstairs a lift moves the plate off its default position (on the walls below).
85
+ const upstairsLift =
86
+ supported && Math.abs(lift) > 0.005
87
+ ? lift > 0
88
+ ? Math.min(18, 6 + lift * 30)
89
+ : -Math.min(10, 4 + -lift * 30)
90
+ : 0
91
+ const bandBottom = base - foundation - upstairsLift
92
+ const bandTop = bandBottom - band
93
+ // Upstairs the lift is measured from resting on the walls below; on the
94
+ // ground the top is measured from the ground.
95
+ const measureFrom = supported ? base - band : base
96
+ const measured = supported ? Math.abs(lift) > 0.005 : true
97
+ const measureLabel = supported ? liftLabel : (topLabel ?? '')
98
+ const left = 64
99
+ const right = 164
100
+ const labels: { y: number; text: string; key: string }[] = [
101
+ { key: 'top', y: bandTop - 9, text: 'Floor top' },
102
+ {
103
+ key: 'plate',
104
+ y: bandTop + band / 2,
105
+ text: supported ? `Floor plate · ${thicknessLabel}` : `Slab · ${thicknessLabel}`,
106
+ },
107
+ ...(foundation
108
+ ? [
109
+ {
110
+ key: 'foundation',
111
+ y: bandBottom + foundation / 2,
112
+ text: foundationLabel ? `Foundation · ${foundationLabel}` : 'Foundation',
113
+ },
114
+ ]
115
+ : []),
116
+ { key: 'base', y: base + 9, text: supported ? 'Walls below' : 'Ground' },
117
+ ]
118
+ return (
119
+ <figure className="flex flex-col gap-1" data-floor-section>
120
+ <svg
121
+ aria-label={
122
+ supported
123
+ ? `Floor plate ${thicknessLabel} thick, resting on the walls below`
124
+ : raised
125
+ ? `Slab ${thicknessLabel} thick on a ${foundationLabel} foundation, floor top ${topLabel ?? ''} above the ground`
126
+ : `Slab ${thicknessLabel} thick on the ground, floor top ${topLabel ?? ''} above the ground`
127
+ }
128
+ className="h-auto w-full text-muted-foreground"
129
+ role="img"
130
+ viewBox="0 0 280 118"
131
+ >
132
+ {supported ? (
133
+ // The walls of the storey below, under the plate's edge.
134
+ <g data-section-walls-below>
135
+ <rect
136
+ className="fill-muted"
137
+ height={118 - bandBottom}
138
+ stroke="currentColor"
139
+ strokeOpacity="0.4"
140
+ width="14"
141
+ x={left + 6}
142
+ y={bandBottom}
143
+ />
144
+ <line
145
+ opacity="0.6"
146
+ stroke="currentColor"
147
+ strokeDasharray="3 3"
148
+ x1="8"
149
+ x2={right}
150
+ y1={base}
151
+ y2={base}
152
+ />
153
+ </g>
154
+ ) : (
155
+ <g data-section-ground>
156
+ <line stroke="currentColor" strokeWidth="1" x1="8" x2="272" y1={base} y2={base} />
157
+ {Array.from({ length: 22 }, (_, i) => (
158
+ <line
159
+ key={i}
160
+ opacity="0.35"
161
+ stroke="currentColor"
162
+ strokeWidth="1"
163
+ x1={12 + i * 12}
164
+ x2={6 + i * 12}
165
+ y1={base + 1}
166
+ y2={base + 7}
167
+ />
168
+ ))}
169
+ </g>
170
+ )}
171
+ {/* A wall standing on the floor. */}
172
+ <rect
173
+ className="fill-muted"
174
+ height={Math.min(26, bandTop - 12)}
175
+ stroke="currentColor"
176
+ strokeOpacity="0.4"
177
+ width="14"
178
+ x={left + 6}
179
+ y={bandTop - Math.min(26, bandTop - 12)}
180
+ />
181
+ {foundation > 0 && (
182
+ <rect
183
+ data-section-foundation
184
+ fill="#8a8a8a"
185
+ height={foundation}
186
+ stroke="currentColor"
187
+ strokeOpacity="0.5"
188
+ width={right - left - 8}
189
+ x={left + 4}
190
+ y={bandBottom}
191
+ />
192
+ )}
193
+ <rect
194
+ className="fill-foreground"
195
+ data-section-band
196
+ height={band}
197
+ width={right - left}
198
+ x={left}
199
+ y={bandTop}
200
+ />
201
+ {/* The floor top. */}
202
+ <line
203
+ className="text-foreground"
204
+ stroke="currentColor"
205
+ strokeWidth="1.5"
206
+ x1={left - 4}
207
+ x2={right + 4}
208
+ y1={bandTop}
209
+ y2={bandTop}
210
+ />
211
+ {measured && measureLabel && (
212
+ <g className="text-foreground" data-section-height>
213
+ <line stroke="currentColor" strokeWidth="1" x1="50" x2="50" y1={bandTop} y2={measureFrom} />
214
+ <line stroke="currentColor" strokeWidth="1" x1="45" x2="55" y1={bandTop} y2={bandTop} />
215
+ <line stroke="currentColor" strokeWidth="1" x1="45" x2="55" y1={measureFrom} y2={measureFrom} />
216
+ {supported && (
217
+ <line
218
+ opacity="0.6"
219
+ stroke="currentColor"
220
+ strokeDasharray="2 3"
221
+ x1="55"
222
+ x2={left}
223
+ y1={measureFrom}
224
+ y2={measureFrom}
225
+ />
226
+ )}
227
+ <text
228
+ className="fill-foreground font-mono"
229
+ dominantBaseline="middle"
230
+ fontSize="10"
231
+ textAnchor="end"
232
+ x="42"
233
+ y={(bandTop + measureFrom) / 2}
234
+ >
235
+ {measureLabel}
236
+ </text>
237
+ </g>
238
+ )}
239
+ {labels.map((label) => (
240
+ <g key={label.key}>
241
+ <line
242
+ opacity="0.6"
243
+ stroke="currentColor"
244
+ x1={right + 4}
245
+ x2={right + 12}
246
+ y1={label.y}
247
+ y2={label.y}
248
+ />
249
+ <text
250
+ className="fill-foreground"
251
+ dominantBaseline="middle"
252
+ fontSize="10"
253
+ x={right + 16}
254
+ y={label.y}
255
+ >
256
+ {label.text}
257
+ </text>
258
+ </g>
259
+ ))}
260
+ </svg>
261
+ <figcaption className="text-right text-[10px] text-muted-foreground/70">
262
+ Not to scale
263
+ </figcaption>
264
+ </figure>
265
+ )
266
+ }
267
+
268
+ type Notice = {
269
+ message: string
270
+ /** The footprints to move together, and the change, when that would be accepted. */
271
+ together: { ids: string[]; patch: FloorFoundationPatch; lower: boolean } | null
272
+ }
273
+
274
+ export function moveTogetherLabel(count: number, lower: boolean) {
275
+ const verb = lower ? 'Lower' : 'Raise'
276
+ return count === 2 ? `${verb} both` : `${verb} all ${count}`
277
+ }
278
+
279
+ /** "Floor plate · Ground floor", upstairs "Floor plate · Floor 1 · rests on Ground floor". */
280
+ export function floorPlateKicker(nodes: Record<string, AnyNode>, plate: SlabNode): string {
281
+ const level = plate.parentId ? nodes[plate.parentId] : undefined
282
+ const levelName = level?.type === 'level' ? ` · ${getLevelDisplayName(level)}` : ''
283
+ // A supported floor rests on the storey right below it.
284
+ let below: LevelNode | null = null
285
+ if (level?.type === 'level' && floorFootprintSupportClass(nodes, plate) === 'supported')
286
+ for (const node of Object.values(nodes))
287
+ if (
288
+ node.type === 'level' &&
289
+ node.parentId === level.parentId &&
290
+ node.level < level.level &&
291
+ (!below || node.level > below.level)
292
+ )
293
+ below = node
294
+ const restsOn = below ? ` · rests on ${getLevelDisplayName(below)}` : ''
295
+ return `Floor plate${levelName}${restsOn}`
296
+ }
297
+
298
+ /**
299
+ * What the panel shows for a footprint. An upper floor rests on the walls
300
+ * below: its height is its floor top, and raising it thickens the plate (the
301
+ * two stay in sync); it has no ground preset and no foundation. A footprint on
302
+ * the ground has two inputs, the slab thickness and the foundation height (0 on
303
+ * the ground); its floor top above the ground is their sum, read-only.
304
+ */
305
+ export function floorFoundationModel(nodes: Record<string, AnyNode>, plate: SlabNode) {
306
+ const upper = upperFloorHeightControl(nodes, plate) !== null
307
+ const height = footprintHeightValue(nodes, plate)
308
+ const levelBase = plate.parentId
309
+ ? (getLevelElevations(nodes as Record<AnyNodeId, AnyNode>).get(plate.parentId as AnyNodeId)
310
+ ?.baseY ?? 0)
311
+ : 0
312
+ return {
313
+ upper,
314
+ kicker: floorPlateKicker(nodes, plate),
315
+ preset: upper ? ('ground' as const) : footprintPreset(nodes, plate),
316
+ /** Upstairs: the floor top (level-local). On the ground: the foundation height. */
317
+ height,
318
+ /** Upstairs: the floor top above the ground. On the ground: above the ground (foundation + slab). */
319
+ floorTop: upper ? levelBase + height : floorTopAboveGround(nodes, plate),
320
+ minHeight: footprintHeightMinimum(nodes, plate),
321
+ minThickness: upper ? MIN_SLAB_THICKNESS : MIN_GROUND_FLOOR_THICKNESS,
322
+ underFloor: !upper,
323
+ thickFloor: thickFloorAdvice(nodes, plate),
324
+ }
325
+ }
326
+
327
+ /**
328
+ * "Floor & foundation" for one footprint (the house, a shed, an upper storey),
329
+ * in one column in the order it is built: the section, the slab (thickness,
330
+ * edge finish), then what is under the floor — on a ground-bearing footprint
331
+ * only — and the floor top that follows. Scrubbing the foundation height moves
332
+ * the building live and lands as one undo step.
333
+ */
334
+ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClose: () => void }) {
335
+ const nodes = useScene((state) => state.nodes)
336
+ const unit = useViewer((s) => s.unit)
337
+ const metricNotation = useViewer((s) => s.metricNotation)
338
+ const [notice, setNotice] = useState<Notice | null>(null)
339
+ const scrub = useRef<ReturnType<typeof beginFootprintHeightPreview> | null>(null)
340
+ const thicknessScrub = useRef<ReturnType<typeof beginFootprintThicknessPreview> | null>(null)
341
+ const plate = nodes[node.id as AnyNodeId]
342
+ if (plate?.type !== 'slab' || plate.plateRole !== 'base') return null
343
+ const length = (meters: number) => formatLinearMeasurement(meters, unit, metricNotation)
344
+ const { upper, kicker, preset, height, floorTop, minHeight, minThickness, underFloor, thickFloor } =
345
+ floorFoundationModel(nodes, plate)
346
+ const level = plate.parentId ? nodes[plate.parentId as AnyNodeId] : undefined
347
+ const maxHeight = level?.type === 'level' ? getStoredLevelHeight(level) - 0.5 : 3
348
+ // A refused change says why, in the panel's words, never as an error; when
349
+ // only the shared-storey rule refused it, it offers to move every footprint
350
+ // under that storey together (checked once the change settles, not per frame).
351
+ const report = (message: string | null, patch?: FloorFoundationPatch) => {
352
+ if (!message) return setNotice(null)
353
+ const current = useScene.getState().nodes
354
+ const together = patch ? footprintsToMoveTogether(current, plate.id, patch) : null
355
+ const latest = current[plate.id as AnyNodeId]
356
+ const lower =
357
+ latest?.type !== 'slab'
358
+ ? false
359
+ : patch?.thickness !== undefined
360
+ ? patch.thickness < latest.thickness
361
+ : patch?.foundationHeight !== undefined
362
+ ? patch.foundationHeight < footprintHeightValue(current, latest)
363
+ : patch?.floorHeight == null ||
364
+ patch.floorHeight < (latest.floorHeight ?? latest.elevation)
365
+ setNotice({ message, together: together && patch ? { ids: together, patch, lower } : null })
366
+ }
367
+ const heightPatchFor = (value: number) => {
368
+ const current = useScene.getState().nodes
369
+ const latest = current[plate.id as AnyNodeId]
370
+ return latest?.type === 'slab' ? footprintHeightPatch(current, latest, value) : undefined
371
+ }
372
+ // A scrub previews live and lands once on release; a typed value lands directly.
373
+ const previewHeight = (value: number) => {
374
+ scrub.current ??= beginFootprintHeightPreview(plate.id)
375
+ report(scrub.current.preview(value))
376
+ }
377
+ const commitHeight = (value: number) => {
378
+ const session = scrub.current ?? beginFootprintHeightPreview(plate.id)
379
+ scrub.current = null
380
+ const message = session.commit(value)
381
+ report(message, message ? heightPatchFor(value) : undefined)
382
+ }
383
+ // Upstairs the underside stays on the walls below: the thickness is the one
384
+ // control, and it moves the floor top (and the storeys above) with it.
385
+ const underside = height - plate.thickness
386
+
387
+ return (
388
+ <PanelWrapper
389
+ icon="/icons/floor.webp"
390
+ kicker={kicker}
391
+ onClose={onClose}
392
+ title={floorFootprintName(nodes, plate)}
393
+ >
394
+ <div
395
+ className="flex flex-col gap-3 px-4 pt-3 pb-4 text-sm"
396
+ data-floor-foundation={plate.id}
397
+ data-support={upper ? 'supported' : 'ground-bearing'}
398
+ >
399
+ <FloorSection
400
+ foundationHeight={upper ? 0 : height}
401
+ foundationLabel={length(height)}
402
+ raised={preset === 'raised'}
403
+ supported={upper}
404
+ thickness={plate.thickness}
405
+ thicknessLabel={length(plate.thickness)}
406
+ topLabel={upper ? undefined : length(floorTop)}
407
+ />
408
+ <section className="flex flex-col gap-1.5" data-floor-plate-group>
409
+ <h3 className="font-medium text-muted-foreground text-xs">
410
+ {upper ? 'Floor plate' : 'Slab'}
411
+ </h3>
412
+ {upper ? (
413
+ <div title="A thicker floor grows upward: the rooms below keep their height.">
414
+ <MetricControl
415
+ className="h-9 text-xs"
416
+ label="Floor thickness"
417
+ max={Math.max(MIN_SLAB_THICKNESS, maxHeight - underside)}
418
+ min={Math.max(MIN_SLAB_THICKNESS, minHeight - underside)}
419
+ onChange={(next) => previewHeight(underside + next)}
420
+ onCommit={(next) => commitHeight(underside + next)}
421
+ precision={2}
422
+ restoreOnCommit={false}
423
+ step={0.01}
424
+ unit="m"
425
+ value={plate.thickness}
426
+ />
427
+ </div>
428
+ ) : (
429
+ <div title="The slab sits on the foundation (or the ground): a thicker slab raises the floor top and everything on it.">
430
+ <MetricControl
431
+ className="h-9 text-xs"
432
+ label="Slab thickness"
433
+ max={1}
434
+ min={minThickness}
435
+ onChange={(next) => {
436
+ thicknessScrub.current ??= beginFootprintThicknessPreview(plate.id)
437
+ report(thicknessScrub.current.preview(Math.max(minThickness, next)))
438
+ }}
439
+ onCommit={(value) => {
440
+ const session =
441
+ thicknessScrub.current ?? beginFootprintThicknessPreview(plate.id)
442
+ thicknessScrub.current = null
443
+ const patch = { thickness: Math.max(minThickness, value) }
444
+ const message = session.commit(patch.thickness)
445
+ report(message, message ? patch : undefined)
446
+ }}
447
+ precision={2}
448
+ restoreOnCommit={false}
449
+ step={0.01}
450
+ unit="m"
451
+ value={plate.thickness}
452
+ />
453
+ </div>
454
+ )}
455
+ {upper && (
456
+ <p className="px-0.5 text-muted-foreground text-xs" data-floor-top>
457
+ Floor top +{length(floorTop)}
458
+ </p>
459
+ )}
460
+ <FinishSwatch
461
+ fallback={EDGE_DEFAULT}
462
+ hint="The floor's outside edge, all around the footprint."
463
+ label="Edge finish"
464
+ node={plate}
465
+ role="edge"
466
+ value={plate.slots?.edge}
467
+ />
468
+ {thickFloor && (
469
+ <p className="text-muted-foreground text-xs" data-thick-floor-hint>
470
+ {THICK_FLOOR_HINT}
471
+ </p>
472
+ )}
473
+ </section>
474
+ {underFloor && (
475
+ <section className="flex flex-col gap-1.5" data-under-floor-group>
476
+ <h3 className="font-medium text-muted-foreground text-xs">Under the floor</h3>
477
+ <SegmentedControl
478
+ onChange={(next: FootprintPreset) => {
479
+ if (next === preset) return
480
+ const patch = presetPatch(nodes, plate, next)
481
+ report(applyFloorFoundation(plate.id, patch), patch)
482
+ }}
483
+ options={[
484
+ { label: 'On the ground', value: 'ground' },
485
+ { label: 'Raised on a foundation', value: 'raised' },
486
+ ]}
487
+ value={preset}
488
+ />
489
+ {preset === 'raised' && (
490
+ <>
491
+ <div title="The foundation stands on the ground; the slab sits on it.">
492
+ <MetricControl
493
+ className="h-9 text-xs"
494
+ label="Foundation height"
495
+ max={Math.max(0.05, maxHeight)}
496
+ min={minHeight}
497
+ onChange={previewHeight}
498
+ onCommit={commitHeight}
499
+ precision={2}
500
+ restoreOnCommit={false}
501
+ step={0.05}
502
+ unit="m"
503
+ value={height}
504
+ />
505
+ </div>
506
+ <FinishSwatch
507
+ fallback={FOUNDATION_DEFAULT}
508
+ label="Foundation finish"
509
+ node={plate}
510
+ role="foundation"
511
+ value={
512
+ typeof plate.foundation?.material === 'string'
513
+ ? plate.foundation.material
514
+ : undefined
515
+ }
516
+ />
517
+ </>
518
+ )}
519
+ <p className="px-0.5 text-muted-foreground text-xs" data-floor-top>
520
+ Floor top {length(floorTop)}
521
+ </p>
522
+ </section>
523
+ )}
524
+ {notice && (
525
+ <div
526
+ className="flex flex-col items-start gap-2 rounded-lg bg-accent/30 px-2.5 py-2"
527
+ data-floor-foundation-notice
528
+ >
529
+ <p className="text-muted-foreground text-xs" role="status">
530
+ {notice.message}
531
+ </p>
532
+ {notice.together && (
533
+ <Button
534
+ className="rounded-full"
535
+ onClick={() => {
536
+ const { ids, patch } = notice.together!
537
+ report(applyFloorFoundation(ids, patch))
538
+ }}
539
+ size="sm"
540
+ type="button"
541
+ variant="outline"
542
+ >
543
+ {moveTogetherLabel(notice.together.ids.length, notice.together.lower)}
544
+ </Button>
545
+ )}
546
+ </div>
547
+ )}
548
+ </div>
549
+ </PanelWrapper>
550
+ )
551
+ }
@@ -0,0 +1,143 @@
1
+ 'use client'
2
+
3
+ import {
4
+ type AnyNodeId,
5
+ adjacentLevelId,
6
+ area,
7
+ floorOpeningHints,
8
+ getLevelDisplayName,
9
+ useScene,
10
+ } from '@pascal-app/core'
11
+ import { useViewer } from '@pascal-app/viewer'
12
+ import {
13
+ deleteFloorOpening,
14
+ finishFloorOpening,
15
+ openingRoom,
16
+ } from '../../../lib/floor-edit-session'
17
+ import { formatAreaLabel } from '../../../lib/measurements'
18
+ import { Button } from '../primitives/button'
19
+ import { Switch } from '../primitives/switch'
20
+ import { PanelWrapper } from './panel-wrapper'
21
+
22
+ /**
23
+ * A floor (or ceiling) opening: where it is, what it cuts, and the switch for
24
+ * the adjacent surface — "Also cut the ceiling below" for an opening drawn on
25
+ * a floor, "Also cut the floor above" for one drawn on a ceiling. A hatch in a
26
+ * mezzanine cuts only the mezzanine. Its shape is edited with the corner
27
+ * handles in the scene. Done and Delete return to the room.
28
+ */
29
+ export function FloorOpeningPanel({ openingId }: { openingId: string }) {
30
+ const nodes = useScene((state) => state.nodes)
31
+ const unit = useViewer((s) => s.unit)
32
+ const readOnly = useScene((state) => state.readOnly)
33
+ const opening = nodes[openingId as AnyNodeId]
34
+ if (opening?.type !== 'floor-opening') return null
35
+ const onCeiling = opening.drawnOn === 'ceiling'
36
+ const zoneId = openingRoom(nodes, opening)
37
+ const zone = zoneId ? nodes[zoneId as AnyNodeId] : undefined
38
+ const roomName = zone?.type === 'zone' ? zone.name?.trim() || 'Room' : null
39
+ const level = opening.parentId ? nodes[opening.parentId as AnyNodeId] : undefined
40
+ const adjacentId = opening.parentId
41
+ ? adjacentLevelId(nodes, opening.parentId, onCeiling ? 1 : -1)
42
+ : undefined
43
+ const adjacent = adjacentId ? nodes[adjacentId as AnyNodeId] : undefined
44
+ const hosted = !!opening.hostZoneId
45
+ const owned =
46
+ (opening.source === 'stair' || opening.source === 'elevator') &&
47
+ !!opening.ownerId &&
48
+ nodes[opening.ownerId as AnyNodeId]?.type === opening.source
49
+ const hints = floorOpeningHints(nodes, opening)
50
+ const title = opening.source === 'stair'
51
+ ? 'Stair opening'
52
+ : opening.source === 'elevator'
53
+ ? 'Elevator opening'
54
+ : onCeiling ? 'Ceiling opening' : 'Floor opening'
55
+ const kicker = [
56
+ 'Opening',
57
+ roomName ? `in ${roomName}` : null,
58
+ level?.type === 'level' ? getLevelDisplayName(level) : null,
59
+ ]
60
+ .filter(Boolean)
61
+ .join(' · ')
62
+ const switchLabel = onCeiling ? 'Also cut the floor above' : 'Also cut the ceiling below'
63
+ const setAdjacent = (next: boolean) =>
64
+ useScene.getState().updateNode(opening.id, { cutsAdjacent: next })
65
+
66
+ return (
67
+ <PanelWrapper
68
+ icon="/icons/floor.webp"
69
+ kicker={kicker}
70
+ onClose={() => finishFloorOpening(opening.id)}
71
+ title={title}
72
+ >
73
+ <div className="flex flex-col gap-3 px-4 pt-3 pb-4 text-sm" data-floor-opening-panel={opening.id}>
74
+ <p className="text-muted-foreground text-xs">
75
+ {formatAreaLabel(area([{ outer: opening.polygon, holes: [] }]), unit, 2)}
76
+ {owned ? ' · Shape follows its owner.' : ' · Drag its corners to change the shape.'}
77
+ </p>
78
+ {owned && (
79
+ <button
80
+ className="text-left text-xs text-primary underline"
81
+ data-opening-owner={opening.ownerId}
82
+ onClick={() => useViewer.getState().setSelection({ selectedIds: [opening.ownerId!] })}
83
+ type="button"
84
+ >
85
+ Owned by {opening.source === 'stair' ? 'Staircase' : 'Elevator'}
86
+ </button>
87
+ )}
88
+ {hosted ? (
89
+ <p className="text-muted-foreground text-xs" data-opening-hosted>
90
+ A hatch in the mezzanine: it cuts only the mezzanine’s floor.
91
+ </p>
92
+ ) : owned ? null : (
93
+ <label className="flex items-start gap-3" data-opening-adjacent>
94
+ <span className="flex flex-1 flex-col gap-0.5">
95
+ <span className="font-medium text-[13px] text-foreground">{switchLabel}</span>
96
+ <span className="text-muted-foreground text-xs">
97
+ {adjacent?.type === 'level'
98
+ ? `On ${getLevelDisplayName(adjacent)}, as one void with this opening.`
99
+ : onCeiling
100
+ ? 'There is no floor above this storey.'
101
+ : 'There is no storey below.'}
102
+ </span>
103
+ </span>
104
+ <Switch
105
+ aria-label={switchLabel}
106
+ checked={opening.cutsAdjacent && adjacent?.type === 'level'}
107
+ disabled={readOnly || adjacent?.type !== 'level'}
108
+ onCheckedChange={setAdjacent}
109
+ />
110
+ </label>
111
+ )}
112
+ {hints.map((hint) => (
113
+ <p className="text-muted-foreground text-xs" data-opening-hint={hint.code} key={hint.code}>
114
+ {hint.message}
115
+ </p>
116
+ ))}
117
+ <div className="flex items-center gap-2">
118
+ <Button
119
+ className="flex-1 rounded-full"
120
+ data-opening-done
121
+ onClick={() => finishFloorOpening(opening.id)}
122
+ size="sm"
123
+ type="button"
124
+ variant="outline"
125
+ >
126
+ Done
127
+ </Button>
128
+ {!owned && <Button
129
+ className="flex-1 rounded-full"
130
+ data-opening-delete
131
+ disabled={readOnly}
132
+ onClick={() => deleteFloorOpening(opening.id)}
133
+ size="sm"
134
+ type="button"
135
+ variant="outline"
136
+ >
137
+ Delete
138
+ </Button>}
139
+ </div>
140
+ </div>
141
+ </PanelWrapper>
142
+ )
143
+ }