@pascal-app/editor 1.0.7 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (336) hide show
  1. package/package.json +9 -15
  2. package/src/components/editor/action-menu-button.tsx +1 -1
  3. package/src/components/editor/alignment-3d-guide-layer.tsx +1 -1
  4. package/src/components/editor/custom-camera-controls.tsx +101 -45
  5. package/src/components/editor/duplicate-node.ts +2 -1
  6. package/src/components/editor/editor-layout-v2.tsx +1 -1
  7. package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
  8. package/src/components/editor/first-person-controls.tsx +20 -1983
  9. package/src/components/editor/fit-scene-framing.test.ts +318 -0
  10. package/src/components/editor/fit-scene-framing.ts +142 -0
  11. package/src/components/editor/floating-action-menu.tsx +5 -3
  12. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  13. package/src/components/editor/floor-openings-overlay.tsx +1 -1
  14. package/src/components/editor/floor-region-controls.tsx +8 -4
  15. package/src/components/editor/floorplan-navigation-presentation.test.ts +0 -40
  16. package/src/components/editor/floorplan-navigation-presentation.ts +0 -66
  17. package/src/components/editor/floorplan-panel.tsx +199 -30
  18. package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
  19. package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
  20. package/src/components/editor/follow-pascal.test.tsx +1 -2
  21. package/src/components/editor/follow-pascal.tsx +1 -1
  22. package/src/components/editor/footprint-height-handle.tsx +2 -2
  23. package/src/components/editor/group-actions.ts +1 -2
  24. package/src/components/editor/group-floating-action-menu.tsx +1 -1
  25. package/src/components/editor/group-move-3d.ts +1 -2
  26. package/src/components/editor/group-rotate-handle.tsx +1 -2
  27. package/src/components/editor/group-transform-shared.ts +3 -1
  28. package/src/components/editor/handles/use-handle-drag.ts +1 -2
  29. package/src/components/editor/index.tsx +43 -13
  30. package/src/components/editor/inspector-extension-actions.tsx +1 -1
  31. package/src/components/editor/measurement-pill.tsx +1 -1
  32. package/src/components/editor/node-arrow-handles.tsx +2 -2
  33. package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
  34. package/src/components/editor/open-wall-ends-hint.tsx +1 -1
  35. package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
  36. package/src/components/editor/quick-measurement-card.tsx +1 -1
  37. package/src/components/editor/room-controls.tsx +10 -6
  38. package/src/components/editor/room-handles-2d.tsx +3 -3
  39. package/src/components/editor/room-handles.tsx +4 -5
  40. package/src/components/editor/room-highlight.tsx +6 -105
  41. package/src/components/editor/room-labels.test.ts +8 -822
  42. package/src/components/editor/room-labels.tsx +41 -1670
  43. package/src/components/editor/room-webgpu-materials.test.ts +1 -1
  44. package/src/components/editor/selection-manager.tsx +5 -3
  45. package/src/components/editor/site-edge-labels.tsx +7 -3
  46. package/src/components/editor/snapshot-capture-overlay.tsx +2 -5
  47. package/src/components/editor/use-floorplan-background-placement.ts +2 -1
  48. package/src/components/editor/views/plugins-view.tsx +1 -1
  49. package/src/components/editor/views/view-bar.tsx +9 -9
  50. package/src/components/editor/wall-measurement-label.tsx +31 -15
  51. package/src/components/editor/wall-move-side-handles.tsx +11 -7
  52. package/src/components/editor/wall-pull-grip.tsx +2 -2
  53. package/src/components/editor/wall-region-handles.tsx +2 -2
  54. package/src/components/editor/wall-snap-beacon-layer.tsx +53 -32
  55. package/src/components/editor-2d/floorplan-group-move.tsx +3 -4
  56. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +13 -13
  57. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +2 -3
  58. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
  59. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +23 -6
  60. package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +0 -37
  61. package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +0 -30
  62. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +13 -3
  63. package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
  64. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +14 -3
  65. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
  66. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
  67. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +30 -10
  68. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +39 -35
  69. package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
  70. package/src/components/systems/roof/roof-edit-system.tsx +33 -21
  71. package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
  72. package/src/components/tools/distribution/distribution-run-tool.tsx +151 -33
  73. package/src/components/tools/item/move-tool.tsx +1 -6
  74. package/src/components/tools/item/placement-history.test.tsx +1 -1
  75. package/src/components/tools/item/use-placement-coordinator.tsx +3 -4
  76. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +7 -3
  77. package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
  78. package/src/components/tools/registry/move-registry-node-tool.tsx +39 -16
  79. package/src/components/tools/shared/cursor-sphere.tsx +1 -1
  80. package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
  81. package/src/components/tools/shared/placement-box.tsx +2 -6
  82. package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
  83. package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
  84. package/src/components/tools/shared/segment-angle.ts +0 -4
  85. package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
  86. package/src/components/tools/tool-manager.tsx +1 -24
  87. package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
  88. package/src/components/tools/wall/wall-typed-entry.ts +70 -0
  89. package/src/components/ui/action-menu/camera-actions.tsx +2 -2
  90. package/src/components/ui/action-menu/control-modes.tsx +1 -1
  91. package/src/components/ui/action-menu/follow-pascal-pill.tsx +1 -1
  92. package/src/components/ui/action-menu/index.tsx +3 -3
  93. package/src/components/ui/action-menu/measurement-control.tsx +3 -3
  94. package/src/components/ui/action-menu/placement.ts +0 -18
  95. package/src/components/ui/action-menu/point-ask-button.tsx +1 -1
  96. package/src/components/ui/action-menu/view-toggles.tsx +5 -5
  97. package/src/components/ui/controls/finish-swatch.tsx +1 -1
  98. package/src/components/ui/controls/material-paint-panel.tsx +2 -2
  99. package/src/components/ui/controls/scene-material-list.tsx +2 -2
  100. package/src/components/ui/controls/select-control.tsx +1 -1
  101. package/src/components/ui/controls/slider-control.tsx +18 -6
  102. package/src/components/ui/controls/terrain-sculpt-panel.tsx +1 -1
  103. package/src/components/ui/controls/tool-options-panel.tsx +74 -44
  104. package/src/components/ui/floating-level-selector.tsx +3 -3
  105. package/src/components/ui/helpers/contextual-helper-panel.tsx +1 -1
  106. package/src/components/ui/helpers/helper-manager.test.tsx +54 -1
  107. package/src/components/ui/helpers/helper-manager.tsx +55 -1
  108. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  109. package/src/components/ui/panels/collections/collections-popover.tsx +2 -2
  110. package/src/components/ui/panels/floor-foundation-panel.tsx +3 -3
  111. package/src/components/ui/panels/floor-opening-panel.tsx +5 -5
  112. package/src/components/ui/panels/mobile-selection-bar.tsx +28 -24
  113. package/src/components/ui/panels/multi-height-mode.tsx +3 -3
  114. package/src/components/ui/panels/multi-selection-panel.tsx +1 -1
  115. package/src/components/ui/panels/node-display.ts +1 -1
  116. package/src/components/ui/panels/panel-manager.tsx +5 -3
  117. package/src/components/ui/panels/panel-wrapper.tsx +42 -19
  118. package/src/components/ui/panels/parametric-inspector.tsx +1 -1
  119. package/src/components/ui/panels/room-construction-rows.tsx +7 -7
  120. package/src/components/ui/panels/room-name-options.ts +1 -5
  121. package/src/components/ui/panels/room-openings-field.tsx +1 -1
  122. package/src/components/ui/panels/room-panel.tsx +7 -9
  123. package/src/components/ui/primitives/color-dot.tsx +2 -2
  124. package/src/components/ui/primitives/opacity-control.tsx +2 -2
  125. package/src/components/ui/primitives/sidebar.tsx +3 -3
  126. package/src/components/ui/right-stack.test.ts +13 -6
  127. package/src/components/ui/right-stack.tsx +22 -11
  128. package/src/components/ui/sidebar/icon-rail.tsx +1 -1
  129. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  130. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
  131. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +1 -1
  132. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +2 -2
  133. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +1 -1
  134. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +2 -6
  135. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
  136. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +1 -1
  137. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +1 -1
  138. package/src/components/ui/sidebar/panels/site-panel/index.tsx +3 -10
  139. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +1 -1
  140. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
  141. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +46 -7
  142. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +1 -1
  143. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +1 -1
  144. package/src/components/ui/sidebar/panels/site-panel/tree-node-drag.tsx +14 -5
  145. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -2
  146. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +1 -1
  147. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +3 -3
  148. package/src/components/ui/sidebar/tab-bar.tsx +1 -1
  149. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +2 -2
  150. package/src/components/ui/wall-type/wall-type-menu.tsx +1 -1
  151. package/src/components/viewer-overlay.tsx +1 -2
  152. package/src/hooks/use-auto-frame.ts +1 -2
  153. package/src/hooks/use-keyboard.ts +1 -2
  154. package/src/hooks/use-selected-room.ts +5 -7
  155. package/src/i18n/floor-footprint-name.ts +1 -1
  156. package/src/i18n/locales/en/messages.po +1036 -306
  157. package/src/i18n/locales/en/messages.ts +1 -1
  158. package/src/i18n/locales/en-XA/messages.ts +1 -1
  159. package/src/i18n/locales/es/messages.po +2267 -439
  160. package/src/i18n/locales/fr/messages.po +2267 -439
  161. package/src/i18n/locales/ja/messages.po +2267 -439
  162. package/src/i18n/locales/zh-CN/messages.po +2267 -439
  163. package/src/i18n/locales.test.ts +1 -1
  164. package/src/index.tsx +183 -178
  165. package/src/lib/below-grade.test.tsx +4 -4
  166. package/src/lib/camera-facing.test.ts +1 -1
  167. package/src/lib/camera-facing.ts +2 -3
  168. package/src/lib/contextual-help.ts +6 -0
  169. package/src/lib/editor-level-display.ts +1 -1
  170. package/src/lib/elevation-guides.ts +21 -6
  171. package/src/lib/elevator-support.ts +1 -72
  172. package/src/lib/floor-footprints.test.ts +3 -3
  173. package/src/lib/floor-footprints.ts +1 -9
  174. package/src/lib/floor-foundation-support.test.ts +8 -3
  175. package/src/lib/floor-opening-draft.ts +2 -2
  176. package/src/lib/floor-region-session.ts +1 -1
  177. package/src/lib/floorplan/annotation-visibility.test.ts +1 -1
  178. package/src/lib/floorplan/annotation-visibility.ts +1 -1
  179. package/src/lib/floorplan/floorplan-export.test.ts +1 -1
  180. package/src/lib/floorplan/floorplan-export.tsx +8 -11
  181. package/src/lib/floorplan/floorplan-extension.ts +4 -157
  182. package/src/lib/floorplan/floorplan-mode.ts +2 -1
  183. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +1 -1
  184. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +8 -6
  185. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +3 -4
  186. package/src/lib/floorplan/geometry.ts +2 -112
  187. package/src/lib/floorplan/index.ts +0 -9
  188. package/src/lib/floorplan/items.ts +2 -1
  189. package/src/lib/floorplan/open-wall-ends.ts +2 -1
  190. package/src/lib/floorplan/schedules.ts +6 -3
  191. package/src/lib/floorplan/sheet-export.ts +7 -3
  192. package/src/lib/floorplan/site-plan/site-parts.ts +7 -1
  193. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +1 -1
  194. package/src/lib/follow-pascal-presentation.ts +1 -1
  195. package/src/lib/gesture-lifecycle.ts +3 -1
  196. package/src/lib/glb-export.ts +1 -1
  197. package/src/lib/interaction/scope.ts +1 -0
  198. package/src/lib/layer-thickness.ts +1 -1
  199. package/src/lib/level-selection.test.ts +18 -2
  200. package/src/lib/level-selection.ts +31 -8
  201. package/src/lib/linear-display.ts +1 -1
  202. package/src/lib/live-gesture-round-trip.test.ts +1 -1
  203. package/src/lib/material-paint.ts +1 -1
  204. package/src/lib/measurement-label.test.ts +1 -1
  205. package/src/lib/measurements.test.ts +7 -5
  206. package/src/lib/mezzanine-selection.test.ts +1 -1
  207. package/src/lib/node-action-movement.test.tsx +115 -0
  208. package/src/lib/node-action-movement.ts +2 -0
  209. package/src/lib/paint-region-mode.ts +1 -1
  210. package/src/lib/placement-metadata.ts +4 -0
  211. package/src/lib/point-ask/describe-target.ts +6 -8
  212. package/src/lib/point-ask/region.ts +1 -1
  213. package/src/lib/point-ask/session.ts +1 -2
  214. package/src/lib/print-shell-compiler.ts +12 -1
  215. package/src/lib/print-wall-solids.test.ts +11 -0
  216. package/src/lib/print-wall-solids.ts +9 -0
  217. package/src/lib/roof-duplication.test.ts +100 -0
  218. package/src/lib/roof-duplication.ts +30 -5
  219. package/src/lib/room-built-on.test.ts +2 -5
  220. package/src/lib/room-built-on.ts +1 -15
  221. package/src/lib/room-construction-commands.ts +3 -2
  222. package/src/lib/room-controls-phase6.test.ts +1 -1
  223. package/src/lib/room-handle-drag.ts +8 -37
  224. package/src/lib/room-name-catalog.test.ts +2 -1
  225. package/src/lib/room-name-messages.test.ts +2 -1
  226. package/src/lib/room-name-messages.ts +1 -118
  227. package/src/lib/room-name-storage.test.ts +2 -1
  228. package/src/lib/room-push-dimensions.test.ts +1 -1
  229. package/src/lib/room-selection-commands.ts +2 -2
  230. package/src/lib/room-selection.test.ts +6 -6
  231. package/src/lib/room-transform-session.ts +1 -2
  232. package/src/lib/room-zone-routing.ts +2 -2
  233. package/src/lib/scene-clipboard.ts +2 -1
  234. package/src/lib/selection-routing.test.ts +0 -28
  235. package/src/lib/selection-routing.ts +1 -40
  236. package/src/lib/sfx-bus.ts +8 -32
  237. package/src/lib/sfx-player.test.ts +72 -1
  238. package/src/lib/sfx-player.ts +49 -337
  239. package/src/lib/snapping-mode.test.ts +23 -1
  240. package/src/lib/viewer-selection.test.ts +38 -10
  241. package/src/lib/wall-pull.test.ts +1 -1
  242. package/src/lib/wall-pull.ts +1 -27
  243. package/src/lib/wall-region-list.ts +3 -1
  244. package/src/lib/window-interaction.test.ts +4 -2
  245. package/src/store/use-editor.tsx +22 -26
  246. package/src/store/use-floorplan-annotation-visibility.ts +5 -5
  247. package/src/store/use-placement-typing.test.ts +165 -0
  248. package/src/store/use-placement-typing.ts +186 -0
  249. package/src/store/use-wall-region-handles-selection.ts +1 -2
  250. package/src/components/editor/__bench__/room-label-placement.bench.ts +0 -108
  251. package/src/components/editor/first-person/build-collider-world.test.ts +0 -596
  252. package/src/components/editor/first-person/build-collider-world.ts +0 -549
  253. package/src/components/editor/first-person/ground-holes.test.ts +0 -128
  254. package/src/components/editor/first-person/ground-holes.ts +0 -130
  255. package/src/components/editor/first-person/mechanism-targets.test.ts +0 -162
  256. package/src/components/editor/first-person/mechanism-targets.ts +0 -88
  257. package/src/components/editor/first-person/terrain-collider.test.ts +0 -132
  258. package/src/components/editor/first-person/terrain-collider.ts +0 -212
  259. package/src/components/editor/floorplan-camera-sync.test.ts +0 -249
  260. package/src/components/editor/floorplan-camera-sync.ts +0 -213
  261. package/src/components/editor/localized-html.tsx +0 -22
  262. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +0 -236
  263. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +0 -708
  264. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +0 -385
  265. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +0 -670
  266. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +0 -89
  267. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +0 -106
  268. package/src/components/editor-2d/svg-paths.ts +0 -124
  269. package/src/components/tools/elevator/elevator-defaults.ts +0 -8
  270. package/src/components/tools/elevator/elevator-tool.tsx +0 -372
  271. package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
  272. package/src/components/ui/action-menu/action-button.tsx +0 -89
  273. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +0 -42
  274. package/src/components/ui/primitives/button.tsx +0 -69
  275. package/src/components/ui/primitives/dropdown-menu.tsx +0 -232
  276. package/src/components/ui/primitives/popover.tsx +0 -42
  277. package/src/components/ui/primitives/switch.tsx +0 -29
  278. package/src/components/ui/primitives/tooltip.tsx +0 -57
  279. package/src/components/ui/scene-loader.tsx +0 -73
  280. package/src/components/viewer/floorplan-compass-button.tsx +0 -67
  281. package/src/components/viewer/floorplan-preview-geometry.test.ts +0 -107
  282. package/src/components/viewer/floorplan-preview-geometry.ts +0 -281
  283. package/src/components/viewer/floorplan-preview-navigation.test.ts +0 -43
  284. package/src/components/viewer/floorplan-preview-navigation.ts +0 -62
  285. package/src/components/viewer/floorplan-preview-visibility.test.ts +0 -35
  286. package/src/components/viewer/floorplan-preview-visibility.ts +0 -19
  287. package/src/components/viewer/floorplan-preview.tsx +0 -1065
  288. package/src/components/viewer/render-settings-menu.tsx +0 -294
  289. package/src/components/viewer/sky-control.tsx +0 -710
  290. package/src/components/viewer/use-viewer-camera-navigation-sync.ts +0 -140
  291. package/src/components/viewer/use-viewer-ui-sounds.test.ts +0 -15
  292. package/src/components/viewer/use-viewer-ui-sounds.ts +0 -46
  293. package/src/components/viewer/viewer-controls-bar.tsx +0 -496
  294. package/src/components/viewer/viewer-hover-label.tsx +0 -42
  295. package/src/components/viewer/viewer-rooms.tsx +0 -248
  296. package/src/components/viewer/viewer-scene-header.tsx +0 -286
  297. package/src/components/viewer/viewer-selection-manager.tsx +0 -124
  298. package/src/components/viewer/viewer-stage-modes.test.ts +0 -38
  299. package/src/components/viewer/viewer-stage-modes.ts +0 -49
  300. package/src/components/viewer/viewer-stage-switcher.tsx +0 -97
  301. package/src/components/viewer/viewer-stage.test.tsx +0 -47
  302. package/src/components/viewer/viewer-stage.tsx +0 -221
  303. package/src/components/walkthrough-hud.tsx +0 -193
  304. package/src/hooks/use-mobile.ts +0 -19
  305. package/src/i18n/level-name.ts +0 -39
  306. package/src/i18n/scene-theme-name.ts +0 -16
  307. package/src/lib/below-grade.ts +0 -133
  308. package/src/lib/camera-pose.test.ts +0 -237
  309. package/src/lib/camera-pose.ts +0 -172
  310. package/src/lib/door-interaction.ts +0 -102
  311. package/src/lib/floorplan/floorplan-extension.test.ts +0 -42
  312. package/src/lib/floorplan/floorplan-readonly.test.ts +0 -47
  313. package/src/lib/floorplan/floorplan-readonly.ts +0 -95
  314. package/src/lib/floorplan/marks.test.ts +0 -213
  315. package/src/lib/floorplan/marks.ts +0 -238
  316. package/src/lib/measurement-label.ts +0 -68
  317. package/src/lib/measurements.ts +0 -225
  318. package/src/lib/node-display-label.ts +0 -37
  319. package/src/lib/room-assembly-overlay.test.ts +0 -275
  320. package/src/lib/room-assembly-overlay.ts +0 -265
  321. package/src/lib/room-selection.ts +0 -417
  322. package/src/lib/scene-bounds.test.ts +0 -183
  323. package/src/lib/scene-bounds.ts +0 -169
  324. package/src/lib/typing-target.ts +0 -9
  325. package/src/lib/viewer-selection.ts +0 -595
  326. package/src/lib/window-interaction.ts +0 -142
  327. package/src/lib/zone-content.ts +0 -133
  328. package/src/store/camera-pose-store.test.ts +0 -29
  329. package/src/store/camera-pose-store.ts +0 -27
  330. package/src/store/navigation-sync-pose-store.test.ts +0 -34
  331. package/src/store/navigation-sync-pose-store.ts +0 -27
  332. package/src/store/use-audio.test.ts +0 -146
  333. package/src/store/use-audio.tsx +0 -169
  334. package/src/store/use-first-person-hud.ts +0 -40
  335. package/src/store/use-follow-pascal.test.ts +0 -62
  336. package/src/store/use-follow-pascal.ts +0 -53
@@ -1,133 +0,0 @@
1
- import {
2
- type AnyNode,
3
- type AnyNodeId,
4
- buildingGroundBox,
5
- type GroundBox,
6
- type LevelGradeStanding,
7
- levelGradeStanding,
8
- levelGroundBox,
9
- useScene,
10
- } from '@pascal-app/core'
11
- import { useViewer } from '@pascal-app/viewer'
12
- import { useEffect } from 'react'
13
-
14
- type LevelDisplay = {
15
- hideLevelsAboveSelection: boolean
16
- selection: { levelId: string | null; buildingId?: string | null }
17
- }
18
-
19
- /**
20
- * The shown level's standing against the ground: the storeys above the selected level are
21
- * hidden and that level's floor lies under the ground — all of it, or the part of it a
22
- * hillside stands over — which is then veiled over it.
23
- */
24
- export function gradeStandingShown(
25
- viewer: LevelDisplay,
26
- nodes: Record<AnyNodeId, AnyNode>,
27
- ): LevelGradeStanding {
28
- const { levelId } = viewer.selection
29
- if (!viewer.hideLevelsAboveSelection || levelId === null) return 'at-grade'
30
- return levelGradeStanding(nodes, levelId)
31
- }
32
-
33
- /** Whether the level shown lies below grade, on all of its footprint or part of it. */
34
- export function belowGradeShown(viewer: LevelDisplay, nodes: Record<AnyNodeId, AnyNode>): boolean {
35
- return gradeStandingShown(viewer, nodes) !== 'at-grade'
36
- }
37
-
38
- /**
39
- * Keeps the viewer's `belowGrade` and `gradeStanding` on the shown level while the
40
- * selection, the level display and the scene change. The returned function stops, and the
41
- * ground stands solid again.
42
- */
43
- export function followBelowGrade(): () => void {
44
- const update = () => {
45
- const gradeStanding = gradeStandingShown(useViewer.getState(), useScene.getState().nodes)
46
- const viewer = useViewer.getState()
47
- if (
48
- viewer.gradeStanding !== gradeStanding ||
49
- viewer.belowGrade !== (gradeStanding !== 'at-grade')
50
- )
51
- useViewer.setState({ gradeStanding, belowGrade: gradeStanding !== 'at-grade' })
52
- }
53
- update()
54
- // The flag itself is watched too: the other host's level display, handing over, resets it.
55
- const stopViewer = useViewer.subscribe((state, previous) => {
56
- if (
57
- state.selection.levelId !== previous.selection.levelId ||
58
- state.hideLevelsAboveSelection !== previous.hideLevelsAboveSelection ||
59
- state.belowGrade !== previous.belowGrade ||
60
- state.gradeStanding !== previous.gradeStanding
61
- )
62
- update()
63
- })
64
- const stopScene = useScene.subscribe((state, previous) => {
65
- if (state.nodes !== previous.nodes) update()
66
- })
67
- return () => {
68
- stopViewer()
69
- stopScene()
70
- useViewer.setState({ belowGrade: false, gradeStanding: 'at-grade' })
71
- }
72
- }
73
-
74
- /** A host's level display marks a level below grade as shown while `active`. */
75
- export function useBelowGrade(active: boolean) {
76
- useEffect(() => (active ? followBelowGrade() : undefined), [active])
77
- }
78
-
79
- /**
80
- * The box the ground window opens for: the shown level while the editor's level display
81
- * hides what is above it, the whole building when it stands whole (Preview).
82
- */
83
- export function groundWindowBoxShown(
84
- viewer: LevelDisplay,
85
- nodes: Record<AnyNodeId, AnyNode>,
86
- ): GroundBox | null {
87
- const { levelId, buildingId } = viewer.selection
88
- if (viewer.hideLevelsAboveSelection && levelId !== null) return levelGroundBox(nodes, levelId)
89
- return buildingGroundBox(nodes, buildingId ?? null)
90
- }
91
-
92
- function sameBox(a: GroundBox | null, b: GroundBox | null): boolean {
93
- if (a === null || b === null) return a === b
94
- return (
95
- a.min.every((value, axis) => value === b.min[axis]) &&
96
- a.max.every((value, axis) => value === b.max[axis])
97
- )
98
- }
99
-
100
- /**
101
- * Keeps the viewer's `groundWindowBox` on {@link groundWindowBoxShown} while the selection,
102
- * the level display and the scene change. The returned function stops, and the window closes.
103
- */
104
- export function followGroundWindowBox(): () => void {
105
- const update = () => {
106
- const box = groundWindowBoxShown(useViewer.getState(), useScene.getState().nodes)
107
- if (!sameBox(useViewer.getState().groundWindowBox, box))
108
- useViewer.setState({ groundWindowBox: box })
109
- }
110
- update()
111
- const stopViewer = useViewer.subscribe((state, previous) => {
112
- if (
113
- state.selection.levelId !== previous.selection.levelId ||
114
- state.selection.buildingId !== previous.selection.buildingId ||
115
- state.hideLevelsAboveSelection !== previous.hideLevelsAboveSelection ||
116
- state.groundWindowBox !== previous.groundWindowBox
117
- )
118
- update()
119
- })
120
- const stopScene = useScene.subscribe((state, previous) => {
121
- if (state.nodes !== previous.nodes) update()
122
- })
123
- return () => {
124
- stopViewer()
125
- stopScene()
126
- useViewer.setState({ groundWindowBox: null })
127
- }
128
- }
129
-
130
- /** The ground window follows the shown level or building while `active` (not on the site). */
131
- export function useGroundWindowBox(active: boolean) {
132
- useEffect(() => (active ? followGroundWindowBox() : undefined), [active])
133
- }
@@ -1,237 +0,0 @@
1
- import { describe, expect, test } from 'bun:test'
2
- import {
3
- normalizeCameraPose,
4
- planCameraPoseApplication,
5
- publishInitialCameraPose,
6
- releaseCameraPoseEventSuppression,
7
- stepCameraPoseInterpolation,
8
- withCameraPoseDistance,
9
- } from './camera-pose'
10
-
11
- describe('camera pose', () => {
12
- test('normalizes a finite pose without retaining input tuples', () => {
13
- const position: [number, number, number] = [1, 2, 3]
14
- const target: [number, number, number] = [4, 5, 6]
15
- const pose = normalizeCameraPose({
16
- position,
17
- target,
18
- projection: 'perspective',
19
- fov: 50,
20
- viewWidth: 12,
21
- })
22
-
23
- expect(pose).toEqual({
24
- fov: 50,
25
- position,
26
- projection: 'perspective',
27
- target,
28
- viewWidth: 12,
29
- })
30
- expect(pose?.position).not.toBe(position)
31
- expect(pose?.target).not.toBe(target)
32
- })
33
-
34
- test('rejects non-finite, malformed, and unknown camera poses', () => {
35
- expect(normalizeCameraPose(null)).toBeNull()
36
- expect(
37
- normalizeCameraPose({
38
- position: [1, 2],
39
- target: [4, 5, 6],
40
- projection: 'perspective',
41
- }),
42
- ).toBeNull()
43
- expect(
44
- normalizeCameraPose({
45
- position: [1, Number.NaN, 3],
46
- target: [4, 5, 6],
47
- projection: 'perspective',
48
- }),
49
- ).toBeNull()
50
- expect(
51
- normalizeCameraPose({
52
- position: [1, 2, 3],
53
- target: [4, 5, Number.POSITIVE_INFINITY],
54
- projection: 'perspective',
55
- }),
56
- ).toBeNull()
57
- expect(
58
- normalizeCameraPose({
59
- position: [1, 2, 3],
60
- target: [4, 5, 6],
61
- projection: 'panoramic',
62
- }),
63
- ).toBeNull()
64
- expect(
65
- normalizeCameraPose({
66
- position: [1, 2, 3],
67
- target: [4, 5, 6],
68
- projection: 'perspective',
69
- fov: Number.NaN,
70
- }),
71
- ).toBeNull()
72
- expect(
73
- normalizeCameraPose({
74
- position: [1, 2, 3],
75
- target: [4, 5, 6],
76
- projection: 'orthographic',
77
- viewWidth: 0,
78
- }),
79
- ).toBeNull()
80
- expect(
81
- normalizeCameraPose({
82
- position: [1, 2, 3],
83
- target: [4, 5, 6],
84
- projection: 'orthographic',
85
- viewWidth: Number.POSITIVE_INFINITY,
86
- }),
87
- ).toBeNull()
88
- })
89
-
90
- test('plans only perspective fov application and clamps it to a safe range', () => {
91
- expect(
92
- planCameraPoseApplication({
93
- position: [1, 2, 3],
94
- target: [4, 5, 6],
95
- projection: 'perspective',
96
- fov: 0,
97
- })?.perspectiveFov,
98
- ).toBe(1)
99
- expect(
100
- planCameraPoseApplication({
101
- position: [1, 2, 3],
102
- target: [4, 5, 6],
103
- projection: 'perspective',
104
- fov: 200,
105
- })?.perspectiveFov,
106
- ).toBe(179)
107
- expect(
108
- planCameraPoseApplication({
109
- position: [1, 2, 3],
110
- target: [4, 5, 6],
111
- projection: 'orthographic',
112
- fov: 70,
113
- })?.perspectiveFov,
114
- ).toBeNull()
115
- })
116
-
117
- test('interpolates toward the latest pose and converges exactly', () => {
118
- const destination = {
119
- position: [12, 6, -4] as [number, number, number],
120
- target: [2, 1, 3] as [number, number, number],
121
- projection: 'perspective' as const,
122
- }
123
- const first = stepCameraPoseInterpolation([0, 0, 0], [0, 0, 0], destination, 0.016)
124
- expect(first.settled).toBe(false)
125
- expect(first.position[0]).toBeGreaterThan(0)
126
- expect(first.position[0]).toBeLessThan(destination.position[0])
127
-
128
- let position = first.position
129
- let target = first.target
130
- let settled = first.settled
131
- for (let frame = 0; frame < 240 && !settled; frame += 1) {
132
- const step = stepCameraPoseInterpolation(position, target, destination, 0.016)
133
- position = step.position
134
- target = step.target
135
- settled = step.settled
136
- }
137
-
138
- expect(settled).toBe(true)
139
- expect(position).toEqual(destination.position)
140
- expect(target).toEqual(destination.target)
141
- })
142
-
143
- test('settles an already-current pose without advancing on an invalid delta', () => {
144
- const destination = {
145
- position: [1, 2, 3] as [number, number, number],
146
- target: [4, 5, 6] as [number, number, number],
147
- projection: 'orthographic' as const,
148
- }
149
- expect(
150
- stepCameraPoseInterpolation(
151
- destination.position,
152
- destination.target,
153
- destination,
154
- Number.NaN,
155
- ),
156
- ).toEqual({
157
- position: destination.position,
158
- settled: true,
159
- target: destination.target,
160
- })
161
- expect(stepCameraPoseInterpolation([0, 0, 0], [0, 0, 0], destination, Number.NaN)).toEqual({
162
- position: [0, 0, 0],
163
- settled: false,
164
- target: [0, 0, 0],
165
- })
166
- })
167
-
168
- test('publishes the initial pose before any camera control update event', () => {
169
- let publishCount = 0
170
-
171
- publishInitialCameraPose(() => {
172
- publishCount += 1
173
- })
174
-
175
- expect(publishCount).toBe(1)
176
- })
177
-
178
- test('publishes the settled pose when programmatic interpolation releases suppression', () => {
179
- const suppression = { current: true }
180
- let publishCount = 0
181
-
182
- releaseCameraPoseEventSuppression(suppression, () => {
183
- publishCount += 1
184
- })
185
-
186
- expect(suppression.current).toBe(false)
187
- expect(publishCount).toBe(1)
188
- })
189
-
190
- test('retargets the bounded interpolation owner to the latest pose', () => {
191
- const first = stepCameraPoseInterpolation(
192
- [0, 0, 0],
193
- [0, 0, 0],
194
- {
195
- position: [10, 0, 0],
196
- target: [5, 0, 0],
197
- projection: 'perspective',
198
- },
199
- 0.016,
200
- )
201
- const retargeted = stepCameraPoseInterpolation(
202
- first.position,
203
- first.target,
204
- {
205
- position: [-10, 0, 0],
206
- target: [-5, 0, 0],
207
- projection: 'perspective',
208
- },
209
- 0.1,
210
- )
211
-
212
- expect(retargeted.position[0]).toBeLessThan(first.position[0])
213
- expect(retargeted.target[0]).toBeLessThan(first.target[0])
214
- })
215
-
216
- test('preserves view direction while adapting distance to the local viewport', () => {
217
- const pose = {
218
- position: [4, 6, 12] as [number, number, number],
219
- target: [1, 2, 3] as [number, number, number],
220
- projection: 'perspective' as const,
221
- viewWidth: 20,
222
- }
223
- const adapted = withCameraPoseDistance(pose, 5)
224
- const sourceDirection = pose.position.map((value, index) => value - pose.target[index]!)
225
- const adaptedDirection = adapted.position.map((value, index) => value - adapted.target[index]!)
226
- const sourceLength = Math.hypot(...sourceDirection)
227
-
228
- expect(Math.hypot(...adaptedDirection)).toBeCloseTo(5)
229
- const expectedDirection = sourceDirection.map((component) => component * (5 / sourceLength))
230
- expect(adaptedDirection[0]).toBeCloseTo(expectedDirection[0]!)
231
- expect(adaptedDirection[1]).toBeCloseTo(expectedDirection[1]!)
232
- expect(adaptedDirection[2]).toBeCloseTo(expectedDirection[2]!)
233
- expect(adapted.target).toEqual(pose.target)
234
- expect(adapted.target).not.toBe(pose.target)
235
- expect(pose.position).toEqual([4, 6, 12])
236
- })
237
- })
@@ -1,172 +0,0 @@
1
- import type { CameraPose } from '@pascal-app/core'
2
-
3
- const MIN_PERSPECTIVE_FOV = 1
4
- const MAX_PERSPECTIVE_FOV = 179
5
- const INTERPOLATION_TIME_CONSTANT_SECONDS = 0.08
6
- const MAX_INTERPOLATION_DELTA_SECONDS = 0.1
7
- const INTERPOLATION_SETTLE_DISTANCE = 0.001
8
-
9
- export type CameraPoseApplicationPlan = {
10
- pose: CameraPose
11
- perspectiveFov: number | null
12
- }
13
-
14
- export type CameraPoseInterpolationStep = {
15
- position: [number, number, number]
16
- settled: boolean
17
- target: [number, number, number]
18
- }
19
-
20
- export function publishInitialCameraPose(publishCurrentPose: () => void): void {
21
- publishCurrentPose()
22
- }
23
-
24
- export function releaseCameraPoseEventSuppression(
25
- suppression: { current: boolean },
26
- publishCurrentPose: () => void,
27
- ): void {
28
- suppression.current = false
29
- publishCurrentPose()
30
- }
31
-
32
- function finiteVectorTuple(value: unknown): [number, number, number] | null {
33
- if (
34
- !(
35
- Array.isArray(value) &&
36
- value.length === 3 &&
37
- value.every((component) => typeof component === 'number' && Number.isFinite(component))
38
- )
39
- ) {
40
- return null
41
- }
42
-
43
- return [value[0], value[1], value[2]]
44
- }
45
-
46
- export function normalizeCameraPose(value: unknown): CameraPose | null {
47
- if (!(typeof value === 'object' && value !== null)) {
48
- return null
49
- }
50
-
51
- const candidate = value as Partial<CameraPose>
52
- const position = finiteVectorTuple(candidate.position)
53
- const target = finiteVectorTuple(candidate.target)
54
- if (
55
- !(
56
- position &&
57
- target &&
58
- (candidate.projection === 'perspective' || candidate.projection === 'orthographic')
59
- )
60
- ) {
61
- return null
62
- }
63
-
64
- if (
65
- candidate.fov !== undefined &&
66
- !(typeof candidate.fov === 'number' && Number.isFinite(candidate.fov))
67
- ) {
68
- return null
69
- }
70
-
71
- if (
72
- candidate.viewWidth !== undefined &&
73
- !(
74
- typeof candidate.viewWidth === 'number' &&
75
- Number.isFinite(candidate.viewWidth) &&
76
- candidate.viewWidth > 0
77
- )
78
- ) {
79
- return null
80
- }
81
-
82
- return {
83
- ...(candidate.fov === undefined ? {} : { fov: candidate.fov }),
84
- position,
85
- projection: candidate.projection,
86
- target,
87
- ...(candidate.viewWidth === undefined ? {} : { viewWidth: candidate.viewWidth }),
88
- }
89
- }
90
-
91
- export function planCameraPoseApplication(value: unknown): CameraPoseApplicationPlan | null {
92
- const pose = normalizeCameraPose(value)
93
- if (!pose) {
94
- return null
95
- }
96
-
97
- return {
98
- pose,
99
- perspectiveFov:
100
- pose.projection === 'perspective' && pose.fov !== undefined
101
- ? Math.min(Math.max(pose.fov, MIN_PERSPECTIVE_FOV), MAX_PERSPECTIVE_FOV)
102
- : null,
103
- }
104
- }
105
-
106
- function interpolateVectorTuple(
107
- current: [number, number, number],
108
- destination: [number, number, number],
109
- alpha: number,
110
- ): { settled: boolean; value: [number, number, number] } {
111
- const dx = destination[0] - current[0]
112
- const dy = destination[1] - current[1]
113
- const dz = destination[2] - current[2]
114
- const settleDistanceSquared = INTERPOLATION_SETTLE_DISTANCE ** 2
115
- if (dx * dx + dy * dy + dz * dz <= settleDistanceSquared) {
116
- return { settled: true, value: [...destination] }
117
- }
118
-
119
- const value: [number, number, number] = [
120
- current[0] + dx * alpha,
121
- current[1] + dy * alpha,
122
- current[2] + dz * alpha,
123
- ]
124
- const remainingX = destination[0] - value[0]
125
- const remainingY = destination[1] - value[1]
126
- const remainingZ = destination[2] - value[2]
127
- const settled =
128
- remainingX * remainingX + remainingY * remainingY + remainingZ * remainingZ <=
129
- settleDistanceSquared
130
-
131
- return { settled, value: settled ? [...destination] : value }
132
- }
133
-
134
- export function withCameraPoseDistance(pose: CameraPose, distance: number): CameraPose {
135
- const dx = pose.position[0] - pose.target[0]
136
- const dy = pose.position[1] - pose.target[1]
137
- const dz = pose.position[2] - pose.target[2]
138
- const currentDistance = Math.hypot(dx, dy, dz)
139
- if (!(Number.isFinite(distance) && distance > 0 && currentDistance > 0)) {
140
- return { ...pose, position: [...pose.position], target: [...pose.target] }
141
- }
142
-
143
- const scale = distance / currentDistance
144
- return {
145
- ...pose,
146
- position: [
147
- pose.target[0] + dx * scale,
148
- pose.target[1] + dy * scale,
149
- pose.target[2] + dz * scale,
150
- ],
151
- target: [...pose.target],
152
- }
153
- }
154
-
155
- export function stepCameraPoseInterpolation(
156
- currentPosition: [number, number, number],
157
- currentTarget: [number, number, number],
158
- destination: CameraPose,
159
- deltaSeconds: number,
160
- ): CameraPoseInterpolationStep {
161
- const finiteDelta = Number.isFinite(deltaSeconds) ? deltaSeconds : 0
162
- const elapsed = Math.min(Math.max(finiteDelta, 0), MAX_INTERPOLATION_DELTA_SECONDS)
163
- const alpha = 1 - Math.exp(-elapsed / INTERPOLATION_TIME_CONSTANT_SECONDS)
164
- const position = interpolateVectorTuple(currentPosition, destination.position, alpha)
165
- const target = interpolateVectorTuple(currentTarget, destination.target, alpha)
166
-
167
- return {
168
- position: position.value,
169
- settled: position.settled && target.settled,
170
- target: target.value,
171
- }
172
- }
@@ -1,102 +0,0 @@
1
- import {
2
- type AnyNodeId,
3
- type DoorInteractiveState,
4
- isOperationDoorType,
5
- useInteractive,
6
- useScene,
7
- } from '@pascal-app/core'
8
-
9
- export const DOOR_SWING_OPEN_ANGLE = Math.PI / 2
10
- export const DOOR_TOGGLE_ANIMATION_MS = 520
11
-
12
- export { isOperationDoorType }
13
-
14
- type DoorOpenAnimationOptions = {
15
- persist?: boolean
16
- }
17
-
18
- export function getDisplayedDoorValue(
19
- doorId: AnyNodeId,
20
- field: keyof DoorInteractiveState,
21
- nodeValue: number | undefined,
22
- ) {
23
- const interactive = useInteractive.getState()
24
- const runtimeValue = interactive.doors[doorId]?.[field]
25
- if (runtimeValue !== undefined) return runtimeValue
26
-
27
- const queuedValue = interactive.doorAnimations[doorId]?.from
28
- if (queuedValue !== undefined) return queuedValue
29
-
30
- return nodeValue ?? 0
31
- }
32
-
33
- function startDoorOpenAnimation(
34
- doorId: AnyNodeId,
35
- field: keyof DoorInteractiveState,
36
- from: number,
37
- to: number,
38
- options?: DoorOpenAnimationOptions,
39
- ) {
40
- useInteractive.getState().startDoorAnimation(doorId, {
41
- field,
42
- from,
43
- to,
44
- startedAt: null,
45
- durationMs: DOOR_TOGGLE_ANIMATION_MS,
46
- persist: options?.persist ?? true,
47
- })
48
- }
49
-
50
- export function toggleDoorOpenState(doorId: AnyNodeId, options?: DoorOpenAnimationOptions) {
51
- const node = useScene.getState().nodes[doorId]
52
- if (node?.type !== 'door' || node.openingKind === 'opening') return
53
-
54
- if (isOperationDoorType(node.doorType)) {
55
- const currentOpenAmount = getDisplayedDoorValue(doorId, 'operationState', node.operationState)
56
- startDoorOpenAnimation(
57
- doorId,
58
- 'operationState',
59
- currentOpenAmount,
60
- currentOpenAmount >= 0.5 ? 0 : 1,
61
- options,
62
- )
63
- return
64
- }
65
-
66
- const currentSwingAngle = getDisplayedDoorValue(doorId, 'swingAngle', node.swingAngle)
67
- startDoorOpenAnimation(
68
- doorId,
69
- 'swingAngle',
70
- currentSwingAngle,
71
- currentSwingAngle >= DOOR_SWING_OPEN_ANGLE / 2 ? 0 : DOOR_SWING_OPEN_ANGLE,
72
- options,
73
- )
74
- }
75
-
76
- export function closeDoorOpenState(doorId: AnyNodeId, options?: DoorOpenAnimationOptions) {
77
- const node = useScene.getState().nodes[doorId]
78
- if (node?.type !== 'door' || node.openingKind === 'opening') return
79
-
80
- if (isOperationDoorType(node.doorType)) {
81
- const currentOpenAmount = getDisplayedDoorValue(doorId, 'operationState', node.operationState)
82
- startDoorOpenAnimation(doorId, 'operationState', currentOpenAmount, 0, options)
83
- return
84
- }
85
-
86
- const currentSwingAngle = getDisplayedDoorValue(doorId, 'swingAngle', node.swingAngle)
87
- startDoorOpenAnimation(doorId, 'swingAngle', currentSwingAngle, 0, options)
88
- }
89
-
90
- export function openDoorOpenState(doorId: AnyNodeId, options?: DoorOpenAnimationOptions) {
91
- const node = useScene.getState().nodes[doorId]
92
- if (node?.type !== 'door' || node.openingKind === 'opening') return
93
-
94
- if (isOperationDoorType(node.doorType)) {
95
- const currentOpenAmount = getDisplayedDoorValue(doorId, 'operationState', node.operationState)
96
- startDoorOpenAnimation(doorId, 'operationState', currentOpenAmount, 1, options)
97
- return
98
- }
99
-
100
- const currentSwingAngle = getDisplayedDoorValue(doorId, 'swingAngle', node.swingAngle)
101
- startDoorOpenAnimation(doorId, 'swingAngle', currentSwingAngle, DOOR_SWING_OPEN_ANGLE, options)
102
- }
@@ -1,42 +0,0 @@
1
- import { describe, expect, test } from 'bun:test'
2
- import type { GeometryContext } from '@pascal-app/core'
3
- import {
4
- createFloorplanContextExtensions,
5
- normalizeFloorplanWallDimensionFaces,
6
- readFloorplanContext,
7
- } from './floorplan-extension'
8
-
9
- function context(extensions?: Readonly<Record<string, unknown>>): GeometryContext {
10
- return {
11
- resolve: () => undefined,
12
- children: [],
13
- siblings: [],
14
- parent: null,
15
- extensions,
16
- }
17
- }
18
-
19
- describe('floor-plan context extensions', () => {
20
- test('measures walls to both faces by default', () => {
21
- expect(readFloorplanContext(context()).wallDimensionFaces).toBe('both')
22
- })
23
-
24
- test('carries the Measure walls choice', () => {
25
- for (const wallDimensionFaces of ['interior', 'exterior', 'both'] as const) {
26
- const extensions = createFloorplanContextExtensions({ wallDimensionFaces })
27
- expect(readFloorplanContext(context(extensions)).wallDimensionFaces).toBe(wallDimensionFaces)
28
- }
29
- })
30
-
31
- test('normalizes stale persisted values, including the retired references, to Both', () => {
32
- for (const stale of ['finished-faces', 'centerline', 'stud-faces', 'unknown', null]) {
33
- expect(normalizeFloorplanWallDimensionFaces(stale)).toBe('both')
34
- }
35
- })
36
-
37
- test('lets presentation code suppress automatic annotation construction', () => {
38
- expect(readFloorplanContext(context()).automaticDimensions).toBe(true)
39
- const extensions = createFloorplanContextExtensions({ automaticDimensions: false })
40
- expect(readFloorplanContext(context(extensions)).automaticDimensions).toBe(false)
41
- })
42
- })