@pascal-app/editor 1.0.4 → 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 (309) hide show
  1. package/README.md +106 -0
  2. package/package.json +39 -37
  3. package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
  4. package/src/components/editor/bake-thumbnail.tsx +6 -15
  5. package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
  6. package/src/components/editor/construction-reveal-audio.tsx +40 -0
  7. package/src/components/editor/custom-camera-controls.tsx +81 -8
  8. package/src/components/editor/editor-layout-mobile.tsx +1 -1
  9. package/src/components/editor/editor-layout-v2.tsx +1 -1
  10. package/src/components/editor/export-manager.tsx +14 -19
  11. package/src/components/editor/first-person/build-collider-world.test.ts +269 -0
  12. package/src/components/editor/first-person/build-collider-world.ts +22 -5
  13. package/src/components/editor/first-person/ground-holes.test.ts +128 -0
  14. package/src/components/editor/first-person/ground-holes.ts +130 -0
  15. package/src/components/editor/first-person/mechanism-targets.test.ts +89 -2
  16. package/src/components/editor/first-person/mechanism-targets.ts +52 -0
  17. package/src/components/editor/first-person-controls.tsx +19 -11
  18. package/src/components/editor/floor-aware-camera-controls.test.ts +24 -0
  19. package/src/components/editor/floorplan-mode-coordinator.tsx +2 -2
  20. package/src/components/editor/floorplan-panel.tsx +72 -23
  21. package/src/components/editor/follow-pascal.test.tsx +912 -0
  22. package/src/components/editor/follow-pascal.tsx +354 -0
  23. package/src/components/editor/group-actions.ts +2 -0
  24. package/src/components/editor/group-move-3d.ts +2 -0
  25. package/src/components/editor/group-rotate-handle.tsx +2 -0
  26. package/src/components/editor/group-transform-shared.test.ts +460 -0
  27. package/src/components/editor/index.tsx +166 -79
  28. package/src/components/editor/riser-diagram-panel.tsx +14 -7
  29. package/src/components/editor/room-highlight.tsx +13 -5
  30. package/src/components/editor/room-labels.test.ts +621 -128
  31. package/src/components/editor/room-labels.tsx +1090 -190
  32. package/src/components/editor/selection-manager.tsx +38 -117
  33. package/src/components/editor/slab-hole-highlights.tsx +2 -2
  34. package/src/components/editor/snapshot-capture-overlay.tsx +3 -2
  35. package/src/components/editor/snapshot-capture.test.ts +69 -58
  36. package/src/components/editor/snapshot-capture.ts +7 -1
  37. package/src/components/editor/thumbnail-generator.test.ts +350 -4
  38. package/src/components/editor/thumbnail-generator.tsx +128 -54
  39. package/src/components/editor/use-floorplan-background-placement.ts +3 -1
  40. package/src/components/editor/views/plugins-view.tsx +1 -8
  41. package/src/components/editor/views/view-bar.test.tsx +54 -0
  42. package/src/components/editor/views/view-bar.tsx +140 -17
  43. package/src/components/editor/wall-snap-beacon-layer.tsx +5 -4
  44. package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
  45. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +2 -2
  46. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +3 -4
  47. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +1 -1
  48. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +29 -7
  49. package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
  50. package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
  51. package/src/components/point-ask/bubble.tsx +538 -0
  52. package/src/components/point-ask/hover-chip.tsx +91 -0
  53. package/src/components/point-ask/mode-pill.tsx +92 -0
  54. package/src/components/point-ask/optional-motion.ts +22 -0
  55. package/src/components/point-ask/outline-corners.tsx +93 -0
  56. package/src/components/point-ask/pins.tsx +711 -0
  57. package/src/components/point-ask/point-ask-layer.tsx +83 -0
  58. package/src/components/point-ask/point-ask-styles.ts +105 -0
  59. package/src/components/point-ask/region-marquee-styles.ts +15 -0
  60. package/src/components/point-ask/region-marquee.tsx +64 -0
  61. package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
  62. package/src/components/systems/roof/roof-edit-system.tsx +1 -0
  63. package/src/components/tools/distribution/connection-compatibility.test.ts +34 -0
  64. package/src/components/tools/distribution/connection-compatibility.ts +43 -0
  65. package/src/components/tools/distribution/distribution-public.ts +9 -0
  66. package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
  67. package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
  68. package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
  69. package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
  70. package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
  71. package/src/components/tools/distribution/draw-alignment.ts +35 -0
  72. package/src/components/tools/distribution/ports.test.ts +56 -0
  73. package/src/components/tools/distribution/ports.ts +378 -0
  74. package/src/components/tools/distribution/run-cursor.test.ts +135 -0
  75. package/src/components/tools/distribution/run-cursor.ts +36 -0
  76. package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
  77. package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
  78. package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
  79. package/src/components/tools/distribution/run-port-snap.ts +25 -0
  80. package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
  81. package/src/components/tools/distribution/run-screen-direction.ts +30 -0
  82. package/src/components/tools/elevator/elevator-tool.tsx +3 -6
  83. package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
  84. package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
  85. package/src/components/tools/item/placement-history.test.tsx +38 -0
  86. package/src/components/tools/item/placement-strategies.test.ts +46 -1
  87. package/src/components/tools/item/placement-strategies.ts +3 -6
  88. package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
  89. package/src/components/tools/item/use-draft-node.test.tsx +94 -94
  90. package/src/components/tools/item/use-placement-coordinator.tsx +55 -49
  91. package/src/components/tools/registry/item-surface-move.test.ts +62 -63
  92. package/src/components/tools/registry/item-surface-move.ts +2 -0
  93. package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
  94. package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
  95. package/src/components/tools/registry/move-registry-node-tool.tsx +63 -29
  96. package/src/components/tools/tool-manager.tsx +14 -5
  97. package/src/components/tools/wall/wall-drafting.test.ts +170 -57
  98. package/src/components/ui/action-menu/action-button.tsx +6 -1
  99. package/src/components/ui/action-menu/camera-actions.tsx +7 -3
  100. package/src/components/ui/action-menu/control-modes.tsx +13 -7
  101. package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
  102. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
  103. package/src/components/ui/action-menu/index.tsx +56 -33
  104. package/src/components/ui/action-menu/measurement-control.tsx +11 -9
  105. package/src/components/ui/action-menu/placement.ts +20 -0
  106. package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
  107. package/src/components/ui/action-menu/view-toggles.tsx +47 -42
  108. package/src/components/ui/build-panel/build-panel.tsx +10 -3
  109. package/src/components/ui/command-palette/editor-commands.tsx +3 -12
  110. package/src/components/ui/command-palette/index.tsx +45 -15
  111. package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
  112. package/src/components/ui/controls/action-button.tsx +5 -2
  113. package/src/components/ui/controls/metric-control.tsx +1 -1
  114. package/src/components/ui/controls/segmented-control.tsx +21 -8
  115. package/src/components/ui/controls/select-control.tsx +1 -1
  116. package/src/components/ui/controls/slider-control.tsx +1 -1
  117. package/src/components/ui/controls/toggle-control.tsx +3 -3
  118. package/src/components/ui/controls/tool-options-panel.tsx +1 -1
  119. package/src/components/ui/floating-level-selector.tsx +150 -45
  120. package/src/components/ui/helpers/contextual-helper-panel.tsx +32 -7
  121. package/src/components/ui/helpers/helper-manager.test.tsx +54 -2
  122. package/src/components/ui/helpers/helper-manager.tsx +7 -1
  123. package/src/components/ui/helpers/item-helper.tsx +9 -1
  124. package/src/components/ui/panels/collections/collections-popover.tsx +8 -8
  125. package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
  126. package/src/components/ui/panels/mobile-selection-bar.tsx +6 -3
  127. package/src/components/ui/panels/multi-selection-panel.tsx +120 -3
  128. package/src/components/ui/panels/panel-wrapper.tsx +53 -27
  129. package/src/components/ui/panels/parametric-field-control.tsx +2 -2
  130. package/src/components/ui/panels/parametric-inspector.tsx +3 -3
  131. package/src/components/ui/panels/room-panel.tsx +65 -0
  132. package/src/components/ui/primitives/badge.tsx +8 -0
  133. package/src/components/ui/primitives/sidebar.tsx +1 -1
  134. package/src/components/ui/right-stack.tsx +21 -2
  135. package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
  136. package/src/components/ui/sidebar/icon-rail.tsx +3 -3
  137. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  138. package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
  139. package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
  140. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
  141. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
  142. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
  143. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +107 -23
  144. package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
  145. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
  146. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
  147. package/src/components/ui/sidebar/panels/site-panel/index.tsx +5 -12
  148. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +13 -10
  149. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +2 -2
  150. package/src/components/ui/sidebar/tab-bar.tsx +4 -4
  151. package/src/components/viewer/viewer-controls-bar.tsx +18 -6
  152. package/src/components/viewer/viewer-rooms.tsx +16 -0
  153. package/src/hooks/use-auto-save.integration.test.tsx +96 -38
  154. package/src/hooks/use-auto-save.ts +26 -1
  155. package/src/hooks/use-ceiling-events.test.ts +123 -118
  156. package/src/hooks/use-grid-events.test.ts +105 -105
  157. package/src/hooks/use-keyboard.test.ts +65 -16
  158. package/src/hooks/use-keyboard.ts +29 -12
  159. package/src/hooks/use-placement-notice.test.ts +24 -1
  160. package/src/hooks/use-placement-notice.ts +37 -6
  161. package/src/index.tsx +71 -24
  162. package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
  163. package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
  164. package/src/lib/add-storey.test.ts +177 -0
  165. package/src/lib/add-storey.ts +59 -0
  166. package/src/lib/below-grade.test.tsx +171 -0
  167. package/src/lib/below-grade.ts +133 -0
  168. package/src/lib/camera-facing.test.ts +24 -0
  169. package/src/lib/camera-facing.ts +24 -0
  170. package/src/lib/direct-manipulation.ts +2 -0
  171. package/src/lib/download-project-file.ts +17 -0
  172. package/src/lib/editor-level-display.ts +9 -2
  173. package/src/lib/editor-views.ts +35 -1
  174. package/src/lib/export-texture-utils.ts +2 -4
  175. package/src/lib/floor-foundation-support.test.ts +1 -0
  176. package/src/lib/floorplan/floorplan-pdfkit-document.ts +31 -16
  177. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +39 -0
  178. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +4 -0
  179. package/src/lib/floorplan/pdfkit-standard-fonts.d.ts +5 -0
  180. package/src/lib/floorplan/sheet-export.test.ts +24 -0
  181. package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +34 -45
  182. package/src/lib/floorplan/site-plan/coverage.ts +2 -4
  183. package/src/lib/floorplan/site-plan/front-edge.ts +2 -1
  184. package/src/lib/floorplan/site-plan/geometry.test.ts +9 -15
  185. package/src/lib/floorplan/site-plan/geometry.ts +1 -416
  186. package/src/lib/floorplan/site-plan/index.ts +0 -16
  187. package/src/lib/floorplan/site-plan/site-annotations.test.ts +117 -0
  188. package/src/lib/floorplan/site-plan/site-annotations.ts +3 -2
  189. package/src/lib/floorplan/site-plan/site-parts.ts +14 -106
  190. package/src/lib/floorplan/site-plan/yards.test.ts +2 -1
  191. package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
  192. package/src/lib/follow-pascal-exterior.test.ts +171 -0
  193. package/src/lib/follow-pascal-exterior.ts +39 -0
  194. package/src/lib/follow-pascal-presentation.test.ts +203 -0
  195. package/src/lib/follow-pascal-presentation.ts +129 -0
  196. package/src/lib/follow-pascal-sight.ts +84 -0
  197. package/src/lib/follow-pascal-targets.test.ts +406 -0
  198. package/src/lib/follow-pascal-targets.ts +198 -0
  199. package/src/lib/follow-pascal.test.ts +350 -0
  200. package/src/lib/follow-pascal.ts +284 -0
  201. package/src/lib/fresh-planar-placement.test.ts +112 -0
  202. package/src/lib/fresh-planar-placement.ts +57 -3
  203. package/src/lib/geometry-script/author.ts +6 -1
  204. package/src/lib/geometry-script/client.ts +8 -1
  205. package/src/lib/geometry-script/finish-scripts.ts +127 -0
  206. package/src/lib/geometry-script/protocol.ts +1 -0
  207. package/src/lib/glb-export.test.ts +187 -162
  208. package/src/lib/glb-export.ts +4 -1
  209. package/src/lib/history.test.ts +12 -7
  210. package/src/lib/interaction/scope.ts +4 -0
  211. package/src/lib/level-selection.test.ts +57 -0
  212. package/src/lib/level-selection.ts +16 -1
  213. package/src/lib/lot/clean-ring.test.ts +2 -1
  214. package/src/lib/lot/drop-in.ts +9 -24
  215. package/src/lib/lot/lot-patch.test.ts +1 -1
  216. package/src/lib/lot/lot-patch.ts +2 -2
  217. package/src/lib/measurement-kind.test.ts +6 -8
  218. package/src/lib/measurements.test.ts +23 -0
  219. package/src/lib/orbit-floor-clearance.test.ts +36 -0
  220. package/src/lib/orbit-floor-clearance.ts +48 -0
  221. package/src/lib/paint-pass-through.test.ts +11 -0
  222. package/src/lib/paint-pass-through.ts +4 -2
  223. package/src/lib/paint-scope.ts +1 -0
  224. package/src/lib/parametric-node-update.ts +16 -7
  225. package/src/lib/plugin-chat.ts +15 -0
  226. package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
  227. package/src/lib/point-ask/__fixtures__/house.ts +81 -0
  228. package/src/lib/point-ask/ask-created.test.ts +195 -0
  229. package/src/lib/point-ask/ask-flow.test.ts +380 -0
  230. package/src/lib/point-ask/build-context.test.ts +173 -0
  231. package/src/lib/point-ask/build-context.ts +94 -0
  232. package/src/lib/point-ask/capture-crop.ts +129 -0
  233. package/src/lib/point-ask/choreography.ts +69 -0
  234. package/src/lib/point-ask/compare.test.ts +145 -0
  235. package/src/lib/point-ask/compare.ts +73 -0
  236. package/src/lib/point-ask/describe-target.test.ts +165 -0
  237. package/src/lib/point-ask/describe-target.ts +234 -0
  238. package/src/lib/point-ask/dictation.ts +79 -0
  239. package/src/lib/point-ask/edge-marker.test.ts +61 -0
  240. package/src/lib/point-ask/edge-marker.ts +27 -0
  241. package/src/lib/point-ask/frame-loop.ts +25 -0
  242. package/src/lib/point-ask/history-marks.test.ts +94 -0
  243. package/src/lib/point-ask/history-marks.ts +39 -0
  244. package/src/lib/point-ask/host-api.ts +32 -0
  245. package/src/lib/point-ask/outline-box.test.ts +30 -0
  246. package/src/lib/point-ask/outline-box.ts +11 -0
  247. package/src/lib/point-ask/overlay-metrics.ts +6 -0
  248. package/src/lib/point-ask/pick.test.ts +220 -0
  249. package/src/lib/point-ask/pick.ts +121 -0
  250. package/src/lib/point-ask/pin-machine.test.ts +494 -0
  251. package/src/lib/point-ask/pin-machine.ts +217 -0
  252. package/src/lib/point-ask/placement.test.ts +203 -0
  253. package/src/lib/point-ask/placement.ts +141 -0
  254. package/src/lib/point-ask/pointer-tracker.ts +34 -0
  255. package/src/lib/point-ask/projector.ts +83 -0
  256. package/src/lib/point-ask/region.test.ts +157 -0
  257. package/src/lib/point-ask/region.ts +136 -0
  258. package/src/lib/point-ask/retry.test.ts +217 -0
  259. package/src/lib/point-ask/retry.ts +37 -0
  260. package/src/lib/point-ask/scene-commit.test.ts +312 -0
  261. package/src/lib/point-ask/scene-commit.ts +32 -0
  262. package/src/lib/point-ask/session.ts +1391 -0
  263. package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
  264. package/src/lib/point-ask/spring-keyframes.ts +38 -0
  265. package/src/lib/point-ask/spring.test.ts +63 -0
  266. package/src/lib/point-ask/spring.ts +107 -0
  267. package/src/lib/point-ask/suggestions.test.ts +80 -0
  268. package/src/lib/point-ask/suggestions.ts +125 -0
  269. package/src/lib/point-ask/target-scope.test.ts +622 -0
  270. package/src/lib/point-ask/target-scope.ts +338 -0
  271. package/src/lib/point-ask/types.ts +48 -0
  272. package/src/lib/portable-export.test.ts +34 -1
  273. package/src/lib/portable-export.ts +119 -53
  274. package/src/lib/print-feature-thickness.ts +10 -1
  275. package/src/lib/print-roof-solids.test.ts +39 -1
  276. package/src/lib/print-roof-solids.ts +43 -10
  277. package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
  278. package/src/lib/room-assembly-overlay.ts +2 -3
  279. package/src/lib/room-selection.test.ts +45 -1
  280. package/src/lib/scene-clipboard.test.ts +246 -1
  281. package/src/lib/scene-clipboard.ts +44 -14
  282. package/src/lib/selection-routing.ts +1 -1
  283. package/src/lib/sfx-bus.ts +13 -0
  284. package/src/lib/sfx-player.test.ts +70 -9
  285. package/src/lib/sfx-player.ts +6 -0
  286. package/src/lib/stair-levels.test.ts +25 -0
  287. package/src/lib/stair-levels.ts +22 -3
  288. package/src/lib/terrace-draft.ts +2 -1
  289. package/src/lib/viewer-selection.ts +22 -11
  290. package/src/lib/wall-placement-pointer.test.ts +42 -0
  291. package/src/lib/wall-placement-pointer.ts +57 -0
  292. package/src/lib/window-changes.ts +35 -0
  293. package/src/store/use-audio.test.ts +146 -0
  294. package/src/store/use-audio.tsx +127 -3
  295. package/src/store/use-construction-reveal.test.ts +173 -0
  296. package/src/store/use-construction-reveal.ts +145 -0
  297. package/src/store/use-corner-snap-hint.test.ts +24 -0
  298. package/src/store/use-corner-snap-hint.ts +47 -0
  299. package/src/store/use-follow-pascal.test.ts +62 -0
  300. package/src/store/use-follow-pascal.ts +53 -0
  301. package/src/store/use-point-ask.ts +247 -0
  302. package/src/store/use-stair-build-preview.ts +25 -5
  303. package/src/store/view-layout.test.ts +27 -1
  304. package/src/store/view-layout.ts +4 -1
  305. package/src/test-utils/render-current-markup.tsx +16 -0
  306. package/src/components/tools/stair/stair-click-guard.test.ts +0 -58
  307. package/src/components/tools/stair/stair-click-guard.ts +0 -72
  308. package/src/components/tools/stair/stair-defaults.ts +0 -15
  309. package/src/components/tools/stair/stair-tool.tsx +0 -682
@@ -274,7 +274,9 @@ export function useFloorplanBackgroundPlacement({
274
274
  )
275
275
  const fenceGridBase = worldGridSnap(planPoint, fenceStep)
276
276
  const fenceLocked =
277
- fenceSnapped[0] !== fenceGridBase[0] || fenceSnapped[1] !== fenceGridBase[1]
277
+ event.altKey ||
278
+ fenceSnapped[0] !== fenceGridBase[0] ||
279
+ fenceSnapped[1] !== fenceGridBase[1]
278
280
  const snappedPoint = fenceLocked
279
281
  ? fenceSnapped
280
282
  : alignFloorplanDraftPoint(fenceSnapped, {
@@ -30,6 +30,7 @@ import { openSidebarPanel } from '../../../lib/sidebar-panel'
30
30
  import { cn } from '../../../lib/utils'
31
31
  import useEditor from '../../../store/use-editor'
32
32
  import { IconRefGlyph } from '../../ui/icon-ref'
33
+ import { Badge } from '../../ui/primitives/badge'
33
34
  import { Button } from '../../ui/primitives/button'
34
35
  import { PLUGINS_VIEW_ID } from './use-editor-views'
35
36
 
@@ -101,14 +102,6 @@ function PluginIcon({ panel, size }: { panel: EditorHostPanel; size: 'md' | 'lg'
101
102
  )
102
103
  }
103
104
 
104
- function Badge({ label }: { label: string }) {
105
- return (
106
- <span className="rounded-[5px] border border-border px-1.5 py-px font-mono font-medium text-[10px] text-muted-foreground uppercase tracking-wide">
107
- {label}
108
- </span>
109
- )
110
- }
111
-
112
105
  function StatusLine({ entry, projectName }: { entry: PluginEntry; projectName?: string }) {
113
106
  return (
114
107
  <span className="flex min-w-0 items-center gap-1.5 text-muted-foreground text-xs">
@@ -0,0 +1,54 @@
1
+ import { afterEach, expect, test } from 'bun:test'
2
+ import { useViewer } from '@pascal-app/viewer'
3
+ import { renderCurrentMarkup } from '../../../test-utils/render-current-markup'
4
+ import { GroundModeToggle } from './view-bar'
5
+
6
+ const saved = useViewer.getState()
7
+
8
+ afterEach(() => {
9
+ useViewer.setState(saved, true)
10
+ })
11
+
12
+ test('over a level at grade the bar says nothing about the ground', () => {
13
+ useViewer.setState({ belowGrade: false })
14
+ expect(renderCurrentMarkup(<GroundModeToggle />)).toBe('')
15
+ })
16
+
17
+ test('over a level below grade the bar says the ground is veiled, the ground on Auto', () => {
18
+ useViewer.setState({ belowGrade: true, gradeStanding: 'below-grade', groundMode: 'auto' })
19
+ const bar = renderCurrentMarkup(<GroundModeToggle />)
20
+ expect(bar).toContain('Below grade · ground veiled')
21
+ expect(bar).toMatch(/aria-pressed="true"[^>]*>Auto</)
22
+ })
23
+
24
+ test('the person who keeps the ground solid is told so', () => {
25
+ useViewer.setState({ belowGrade: true, gradeStanding: 'below-grade', groundMode: 'solid' })
26
+ const bar = renderCurrentMarkup(<GroundModeToggle />)
27
+ expect(bar).toContain('Below grade · ground solid')
28
+ expect(bar).toMatch(/aria-pressed="true"[^>]*>Solid</)
29
+ })
30
+
31
+ test('a level only partly below grade is named as such, its ground veiled where the hill stands', () => {
32
+ useViewer.setState({
33
+ belowGrade: true,
34
+ gradeStanding: 'partly-below-grade',
35
+ groundWindowVeiled: true,
36
+ groundMode: 'auto',
37
+ })
38
+ expect(renderCurrentMarkup(<GroundModeToggle />)).toContain('Partly below grade · ground veiled')
39
+ // From the walk-out side nothing is in the way: the ground reads solid, the toggle stays.
40
+ useViewer.setState({ groundWindowVeiled: false })
41
+ expect(renderCurrentMarkup(<GroundModeToggle />)).toContain('Partly below grade · ground solid')
42
+ })
43
+
44
+ test("a hillside in the eye's way over a level at grade says only that the ground is veiled", () => {
45
+ useViewer.setState({
46
+ belowGrade: false,
47
+ gradeStanding: 'at-grade',
48
+ groundWindowVeiled: true,
49
+ groundMode: 'auto',
50
+ })
51
+ expect(renderCurrentMarkup(<GroundModeToggle />)).toContain('Ground veiled')
52
+ useViewer.setState({ groundMode: 'solid' })
53
+ expect(renderCurrentMarkup(<GroundModeToggle />)).toContain('Ground solid')
54
+ })
@@ -1,7 +1,8 @@
1
1
  'use client'
2
2
 
3
- import { ArrowLeftRight, Check, Columns2, MoreHorizontal, Pin, PinOff } from 'lucide-react'
4
- import { type ReactNode, useEffect } from 'react'
3
+ import { type GroundMode, useViewer } from '@pascal-app/viewer'
4
+ import { ArrowLeftRight, Check, Columns2, MoreHorizontal, Pin, PinOff, X } from 'lucide-react'
5
+ import { type ReactNode, useEffect, useRef } from 'react'
5
6
  import { useIsMobile } from '../../../hooks/use-mobile'
6
7
  import { type EditorViewDescriptor, isSceneView, isViewPinned } from '../../../lib/editor-views'
7
8
  import { cn } from '../../../lib/utils'
@@ -24,8 +25,9 @@ const GROUP = cn(
24
25
  )
25
26
  const SEGMENT =
26
27
  'flex items-center justify-center gap-1.5 px-2.5 font-medium text-xs transition-colors'
27
- const ACTIVE = 'bg-white/10 text-foreground'
28
- const IDLE = 'text-muted-foreground/70 hover:bg-white/8 hover:text-muted-foreground'
28
+ const ACTIVE = 'bg-black/6 dark:bg-white/10 text-foreground'
29
+ const IDLE =
30
+ 'text-muted-foreground/70 hover:bg-black/5 dark:hover:bg-white/8 hover:text-muted-foreground'
29
31
 
30
32
  /** The views of one pane: its pinned views, the one it shows, and a menu with the rest. */
31
33
  export function ViewTabs({ pane, className }: { pane: ViewPaneIndex; className?: string }) {
@@ -34,23 +36,139 @@ export function ViewTabs({ pane, className }: { pane: ViewPaneIndex; className?:
34
36
  const pinnedViews = useEditor((s) => s.pinnedViews)
35
37
  const current = layout.panes[pane]
36
38
  const tabs = views.filter((view) => view.id === current || isViewPinned(view, pinnedViews))
39
+ const activeIndex = tabs.findIndex((view) => view.id === current)
40
+ const tabList = useRef<HTMLDivElement>(null)
41
+ useEffect(() => {
42
+ const list = tabList.current
43
+ const tab = list?.children.item(activeIndex)
44
+ if (!list || !(tab instanceof HTMLElement)) return
45
+ const reveal = () => {
46
+ const bounds = list.getBoundingClientRect()
47
+ const target = tab.getBoundingClientRect()
48
+ if (target.right > bounds.right) list.scrollLeft += target.right - bounds.right
49
+ else if (target.left < bounds.left) list.scrollLeft -= bounds.left - target.left
50
+ }
51
+ reveal()
52
+ const observer = new ResizeObserver(reveal)
53
+ observer.observe(list)
54
+ observer.observe(tab)
55
+ return () => observer.disconnect()
56
+ }, [activeIndex])
37
57
 
38
58
  return (
39
- <div className={cn(GROUP, className)} role="tablist">
40
- {tabs.map((view) => (
59
+ <div className={cn(GROUP, 'min-w-0 shrink', className)}>
60
+ <div
61
+ aria-label={`Pane ${pane + 1} views`}
62
+ className="flex min-w-0 overflow-x-auto [scrollbar-width:none]"
63
+ role="tablist"
64
+ ref={tabList}
65
+ >
66
+ {tabs.map((view) => (
67
+ <div
68
+ className={cn('group flex shrink-0 items-center', view.id === current ? ACTIVE : IDLE)}
69
+ key={view.id}
70
+ >
71
+ <button
72
+ aria-selected={view.id === current}
73
+ className={cn(
74
+ SEGMENT,
75
+ 'outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring',
76
+ view.onClose && 'pr-1',
77
+ )}
78
+ onClick={() => useEditor.getState().setPaneView(pane, view.id)}
79
+ onKeyDown={(event) => {
80
+ if (event.key === 'Delete' && view.onClose) {
81
+ event.preventDefault()
82
+ view.onClose()
83
+ return
84
+ }
85
+ const offset = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0
86
+ if (!offset && event.key !== 'Home' && event.key !== 'End') return
87
+ event.preventDefault()
88
+ const index = tabs.indexOf(view)
89
+ const target =
90
+ event.key === 'Home'
91
+ ? 0
92
+ : event.key === 'End'
93
+ ? tabs.length - 1
94
+ : (index + offset + tabs.length) % tabs.length
95
+ const next = tabs[target]
96
+ if (next) {
97
+ useEditor.getState().setPaneView(pane, next.id)
98
+ const buttons = event.currentTarget
99
+ .closest('[role="tablist"]')
100
+ ?.querySelectorAll<HTMLButtonElement>('[role="tab"]')
101
+ buttons?.[target]?.focus()
102
+ }
103
+ }}
104
+ tabIndex={view.id === current ? 0 : -1}
105
+ title={view.label}
106
+ role="tab"
107
+ type="button"
108
+ >
109
+ <IconRefGlyph icon={view.icon} size={14} />
110
+ <span className="max-w-36 truncate">{view.label}</span>
111
+ </button>
112
+ {view.onClose && (
113
+ <button
114
+ aria-label={`Close ${view.label}`}
115
+ className="mr-1 flex size-6 shrink-0 items-center justify-center rounded-full text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
116
+ onClick={view.onClose}
117
+ type="button"
118
+ >
119
+ <X className="size-3" />
120
+ </button>
121
+ )}
122
+ </div>
123
+ ))}
124
+ </div>
125
+ <MoreViewsMenu current={current} pane={pane} views={views} />
126
+ </div>
127
+ )
128
+ }
129
+
130
+ const GROUND_MODES = [
131
+ { mode: 'auto', label: 'Auto', title: 'Veil the ground over a level below grade' },
132
+ { mode: 'solid', label: 'Solid', title: 'Keep the ground solid' },
133
+ ] as const satisfies ReadonlyArray<{ mode: GroundMode; label: string; title: string }>
134
+
135
+ /**
136
+ * Over a level below grade the ground is veiled so its rooms show, and the bar says so: the
137
+ * person can keep the ground solid instead (Ground: Auto / Solid). The same word covers the
138
+ * ground window — a hillside in the eye's way, veiled only where it hides the house — over
139
+ * a level at grade or only partly below it.
140
+ */
141
+ export function GroundModeToggle() {
142
+ const belowGrade = useViewer((s) => s.belowGrade)
143
+ const gradeStanding = useViewer((s) => s.gradeStanding)
144
+ const groundWindowVeiled = useViewer((s) => s.groundWindowVeiled)
145
+ const groundMode = useViewer((s) => s.groundMode)
146
+ if (!(belowGrade || groundWindowVeiled)) return null
147
+ const veiled =
148
+ groundMode === 'auto' &&
149
+ ((belowGrade && gradeStanding !== 'partly-below-grade') || groundWindowVeiled)
150
+ const ground = veiled ? 'veiled' : 'solid'
151
+ const label =
152
+ gradeStanding === 'partly-below-grade'
153
+ ? `Partly below grade · ground ${ground}`
154
+ : gradeStanding === 'below-grade'
155
+ ? `Below grade · ground ${ground}`
156
+ : `Ground ${ground}`
157
+ return (
158
+ <div aria-label="Ground" className={GROUP} role="group">
159
+ <span className="flex items-center px-2.5 text-muted-foreground text-xs">{label}</span>
160
+ {GROUND_MODES.map(({ mode, label, title }) => (
41
161
  <button
42
- aria-selected={view.id === current}
43
- className={cn(SEGMENT, view.id === current ? ACTIVE : IDLE)}
44
- key={view.id}
45
- onClick={() => useEditor.getState().setPaneView(pane, view.id)}
46
- role="tab"
162
+ aria-pressed={groundMode === mode}
163
+ className={cn(SEGMENT, groundMode === mode ? ACTIVE : IDLE)}
164
+ key={mode}
165
+ onClick={() => useViewer.getState().setGroundMode(mode)}
166
+ title={title}
47
167
  type="button"
48
168
  >
49
- <IconRefGlyph icon={view.icon} size={14} />
50
- <span>{view.label}</span>
169
+ {label}
51
170
  </button>
52
171
  ))}
53
- <MoreViewsMenu current={current} pane={pane} views={views} />
54
172
  </div>
55
173
  )
56
174
  }
@@ -109,6 +227,7 @@ export function ViewBarRow({ leading }: { leading?: ReactNode }) {
109
227
  style={{ left: `${left * 100}%`, width: `${(right - left) * 100}%` }}
110
228
  >
111
229
  <ViewTabs pane={pane} />
230
+ {isSceneView(layout.panes[pane]) && <GroundModeToggle />}
112
231
  {pane === 0 && leading}
113
232
  <span className="flex-1" />
114
233
  {isLast && !isMobile && <SplitControls />}
@@ -162,7 +281,11 @@ function MoreViewsMenu({
162
281
  return (
163
282
  <DropdownMenu>
164
283
  <DropdownMenuTrigger asChild>
165
- <button aria-label="More views" className={cn(SEGMENT, IDLE, 'px-2')} type="button">
284
+ <button
285
+ aria-label="More views"
286
+ className={cn(SEGMENT, IDLE, 'shrink-0 px-2')}
287
+ type="button"
288
+ >
166
289
  <MoreHorizontal className="h-3.5 w-3.5" />
167
290
  </button>
168
291
  </DropdownMenuTrigger>
@@ -179,12 +302,12 @@ function MoreViewsMenu({
179
302
  <IconRefGlyph icon={view.icon} size={16} />
180
303
  <span className="min-w-0 flex-1 truncate">{view.label}</span>
181
304
  {view.id === current && <Check className="h-3.5 w-3.5 text-muted-foreground" />}
182
- {!isSceneView(view.id) && (
305
+ {!isSceneView(view.id) && !view.onClose && !view.alwaysPinned && (
183
306
  <button
184
307
  aria-label={pinned ? `Unpin ${view.label}` : `Pin ${view.label}`}
185
308
  aria-pressed={pinned}
186
309
  className={cn(
187
- 'flex h-6 w-6 items-center justify-center rounded-md transition-colors hover:bg-white/10',
310
+ 'flex h-6 w-6 items-center justify-center rounded-md transition-colors hover:bg-black/6 dark:hover:bg-white/10',
188
311
  pinned ? 'text-foreground' : 'text-muted-foreground/60',
189
312
  )}
190
313
  onClick={(event) => {
@@ -5,11 +5,10 @@ import {
5
5
  type AnyNodeId,
6
6
  getWallCurveFrameAt,
7
7
  getWallCurveLength,
8
- getWallPlaneTop,
9
8
  getWallThickness,
9
+ getWallTop,
10
10
  isCurvedWall,
11
11
  resolveLevelId,
12
- resolveWallTop,
13
12
  sceneRegistry,
14
13
  spatialGridManager,
15
14
  useScene,
@@ -159,8 +158,10 @@ function getWallTopY(wall: WallNode, nodes: Readonly<Record<string, AnyNode>>) {
159
158
  undefined,
160
159
  wall.justification,
161
160
  )
162
- const planeTop = getWallPlaneTop(wall, levelId, nodes as Record<string, AnyNode>)
163
- return resolveWallTop(wall, planeTop, support.elevation) + WALL_TOP_HIGHLIGHT_LIFT
161
+ return (
162
+ getWallTop(wall, levelId, nodes as Record<string, AnyNode>, support.elevation) +
163
+ WALL_TOP_HIGHLIGHT_LIFT
164
+ )
164
165
  }
165
166
 
166
167
  function buildHighlightSegment(start: [number, number], end: [number, number]) {
@@ -25,6 +25,7 @@ import { clientToPlan } from '../../lib/floorplan/plan-coords'
25
25
  import { isHistoryShortcut } from '../../lib/history'
26
26
  import { formatLinearMeasurement } from '../../lib/measurements'
27
27
  import { sfxEmitter } from '../../lib/sfx-bus'
28
+ import { isTypingTarget } from '../../lib/typing-target'
28
29
  import useAlignmentGuides from '../../store/use-alignment-guides'
29
30
  import useEditor, {
30
31
  isAlignmentGuideActive,
@@ -368,6 +369,7 @@ export function startFloorplanGroupMove(
368
369
  // Escape arm (registered earlier on window in the bubble phase), which
369
370
  // would otherwise clear the multi-selection mid-cancel.
370
371
  const onKeyDown = (e: KeyboardEvent) => {
372
+ if (isTypingTarget(e.target)) return
371
373
  const key = e.key.toLowerCase()
372
374
  if ((key === 'r' || key === 't') && !e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey) {
373
375
  // Armed but still under the drag threshold: engage first (exactly what
@@ -569,6 +571,7 @@ export function startFloorplanGroupRotate(event: {
569
571
 
570
572
  // Capture phase so Escape wins over the global `use-keyboard` arm.
571
573
  const onKeyDown = (e: KeyboardEvent) => {
574
+ if (isTypingTarget(e.target)) return
572
575
  if (e.key === 'Delete' || e.key === 'Backspace') {
573
576
  // Deleting mid-rotate: revert first, then let the global Delete arm run.
574
577
  cancel()
@@ -12,6 +12,7 @@ import {
12
12
  type FloorplanMode,
13
13
  isFloorplanToolAvailableInMode,
14
14
  } from '../../lib/floorplan/floorplan-mode'
15
+ import { selectPlacedNode } from '../../lib/level-selection'
15
16
  import useEditor from '../../store/use-editor'
16
17
  import useFloorplanMode from '../../store/use-floorplan-mode'
17
18
  import useInteractionScope, { useReshapingNode } from '../../store/use-interaction-scope'
@@ -65,8 +66,7 @@ export function FloorplanRegisteredToolLayer() {
65
66
  const metricNotation = useViewer((state) => state.metricNotation)
66
67
  const sceneApi = useMemo(() => createSceneApi(useScene), [])
67
68
  const selectNode = useCallback(
68
- (id: Parameters<FloorplanToolContext['selectNode']>[0]) =>
69
- useViewer.getState().setSelection({ selectedIds: [id] }),
69
+ (id: Parameters<FloorplanToolContext['selectNode']>[0]) => selectPlacedNode(id),
70
70
  [],
71
71
  )
72
72
  const finishTool = useCallback(() => {
@@ -39,6 +39,7 @@ import {
39
39
  import { movementSfxStepKey } from '../../lib/sfx/movement-tick'
40
40
  import { sfxEmitter } from '../../lib/sfx-bus'
41
41
  import { surfaceAttachmentId } from '../../lib/surface-attachment'
42
+ import { isTypingTarget } from '../../lib/typing-target'
42
43
  import { resolveAlignmentForFloorplanView } from '../../lib/world-grid-snap'
43
44
  import useAlignmentGuides from '../../store/use-alignment-guides'
44
45
  import useEditor, {
@@ -497,6 +498,7 @@ export function FloorplanRegistryMoveOverlay() {
497
498
  }
498
499
 
499
500
  const onKey = (event: KeyboardEvent) => {
501
+ if (isTypingTarget(event.target)) return
500
502
  // R flips a directional kind's facing mid-placement (door / window:
501
503
  // front ↔ back). The session records the flip and re-runs its last
502
504
  // apply so the 2D symbol updates immediately; kinds without a facing
@@ -516,10 +518,6 @@ export function FloorplanRegistryMoveOverlay() {
516
518
  if (event.metaKey || event.ctrlKey) return
517
519
  if (!(session.flipSide && hasMovedSinceStart && pointerOverFloorplan)) return
518
520
  if (event.repeat) return
519
- const t = event.target as HTMLElement | null
520
- if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) {
521
- return
522
- }
523
521
  event.preventDefault()
524
522
  event.stopImmediatePropagation()
525
523
  ownWrite(() => session.flipSide?.())
@@ -964,6 +962,7 @@ export function FloorplanRegistryMoveOverlay() {
964
962
  }
965
963
 
966
964
  const onKey = (event: KeyboardEvent) => {
965
+ if (isTypingTarget(event.target)) return
967
966
  if (event.key !== 'Escape' && !isHistoryShortcut(event)) return
968
967
  if (isHistoryShortcut(event)) {
969
968
  // ⌘Z mid-move cancels like Escape — keep it from reaching the
@@ -126,7 +126,7 @@ describe('commitDimensionValue', () => {
126
126
  expect(applied).toEqual([])
127
127
  expect(updated).toHaveLength(1)
128
128
  expect(updated[0]?.id).toBe('dim_1' as AnyNodeId)
129
- expect(typeof (updated[0]?.data as { textOverride?: unknown }).textOverride).toBe('string')
129
+ expect(typeof (updated[0]!.data as { textOverride?: unknown }).textOverride).toBe('string')
130
130
  })
131
131
  })
132
132
 
@@ -29,7 +29,14 @@ import {
29
29
  useLiveTransforms,
30
30
  useScene,
31
31
  } from '@pascal-app/core'
32
- import { beginPerfAction, cancelPerfAction, commitPerfAction, useViewer } from '@pascal-app/viewer'
32
+ import {
33
+ beginPerfAction,
34
+ cancelPerfAction,
35
+ commitPerfAction,
36
+ useRevealPlanActive,
37
+ useRevealWaiting,
38
+ useViewer,
39
+ } from '@pascal-app/viewer'
33
40
  import {
34
41
  type ComponentProps,
35
42
  memo,
@@ -151,6 +158,7 @@ import {
151
158
  resolveFloorplanLabelAngle,
152
159
  updateSvgFloorplanLabelOrientations,
153
160
  } from './floorplan-label-angle'
161
+ import { useFloorplanReveal } from './floorplan-reveal'
154
162
 
155
163
  /**
156
164
  * Registry-driven floor-plan layer.
@@ -2027,6 +2035,10 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
2027
2035
  wallDimensionReference,
2028
2036
  visibilityRootId,
2029
2037
  }: FloorplanRegistryEntryProps): React.ReactElement | null {
2038
+ // A node an agent just built is drawn on its construction-reveal turn, as in 3D.
2039
+ const revealWaiting = useRevealWaiting(nodeId)
2040
+ const revealActive = useRevealPlanActive(nodeId)
2041
+ const entryRef = useRef<SVGGElement>(null)
2030
2042
  const selected = useViewer((state) => state.selection.selectedIds.includes(nodeId))
2031
2043
  const highlighted = useViewer((state) => state.previewSelectedIds.includes(nodeId))
2032
2044
  const multiSelected = useViewer(
@@ -2046,7 +2058,14 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
2046
2058
  const phase = useEditor((state) => state.phase)
2047
2059
  const hoverTargetId =
2048
2060
  phase === 'site' ? (resolveCanvasBuildingId(node, nodes) ?? selectionProxyId) : selectionProxyId
2049
- const hovered = useViewer((state) => state.hoveredId === hoverTargetId)
2061
+ // What a chat chip points at (`pointedIds`) lights as a hover does, as it does in 3D: the node
2062
+ // itself, or what a hover on it would light.
2063
+ const hovered = useViewer(
2064
+ (state) =>
2065
+ state.hoveredId === hoverTargetId ||
2066
+ state.pointedIds.includes(nodeId) ||
2067
+ state.pointedIds.includes(hoverTargetId),
2068
+ )
2050
2069
  const setHoveredId = useViewer((state) => state.setHoveredId)
2051
2070
  const referencedAnnotationRole = useViewer((state) =>
2052
2071
  floorplanEntryReferencedAnnotationRole(node, nodes, new Set(state.selection.selectedIds)),
@@ -2215,7 +2234,8 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
2215
2234
  visibleGeometry && suppressHandles && pass !== 'base'
2216
2235
  ? stripHandleChrome(visibleGeometry)
2217
2236
  : visibleGeometry
2218
- if (!geometry) return null
2237
+ useFloorplanReveal(nodeId, entryRef, cacheEntry, revealWaiting, revealActive)
2238
+ if (revealWaiting || !geometry) return null
2219
2239
 
2220
2240
  const entryClick = isMarqueeSelectionActive ? undefined : onClickStop
2221
2241
  const entryPointerDown = isMarqueeSelectionActive ? undefined : handlePointerDown
@@ -2224,6 +2244,7 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
2224
2244
  <g
2225
2245
  className="floorplan-registry-entry"
2226
2246
  data-node-id={nodeId}
2247
+ ref={entryRef}
2227
2248
  onClick={entryClick}
2228
2249
  onPointerDown={entryPointerDown}
2229
2250
  onPointerEnter={handlePointerEnter}
@@ -2669,6 +2690,7 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
2669
2690
  }
2670
2691
  case 'endpoint-handle': {
2671
2692
  if (!palette) return <></>
2693
+ const endpointUnitsPerPixel = g.screenSized ? unitsPerPixel : handleUnitsPerPixel
2672
2694
  const handleId = makeHandleId(nodeId, g.payload)
2673
2695
  const doubleClickAffordance = floorplanHandleDoubleClickAffordance(g)
2674
2696
  const isHovered = hoveredHandleId === handleId
@@ -2694,10 +2716,10 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
2694
2716
  : palette.endpointHandleFill
2695
2717
  const outerRadius =
2696
2718
  (isActive ? ENDPOINT_HANDLE_ACTIVE_RADIUS_PX : ENDPOINT_HANDLE_SELECTED_RADIUS_PX) *
2697
- handleUnitsPerPixel
2719
+ endpointUnitsPerPixel
2698
2720
  const dotRadius =
2699
2721
  (isActive ? ENDPOINT_HANDLE_ACTIVE_DOT_RADIUS_PX : ENDPOINT_HANDLE_DOT_RADIUS_PX) *
2700
- handleUnitsPerPixel
2722
+ endpointUnitsPerPixel
2701
2723
  return (
2702
2724
  <g
2703
2725
  key={keyHint}
@@ -2713,7 +2735,7 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
2713
2735
  r={outerRadius}
2714
2736
  stroke={hoverStroke}
2715
2737
  strokeOpacity={isActive ? 0.24 : 0.16}
2716
- strokeWidth={ENDPOINT_HOVER_GLOW_STROKE_WIDTH_PX * handleUnitsPerPixel}
2738
+ strokeWidth={ENDPOINT_HOVER_GLOW_STROKE_WIDTH_PX * endpointUnitsPerPixel}
2717
2739
  style={{ opacity: isHovered || isActive ? 1 : 0, transition: HOVER_TRANSITION }}
2718
2740
  vectorEffect="non-scaling-stroke"
2719
2741
  />
@@ -2725,7 +2747,7 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
2725
2747
  r={outerRadius}
2726
2748
  stroke={hoverStroke}
2727
2749
  strokeOpacity={isActive ? 0.72 : 0.52}
2728
- strokeWidth={ENDPOINT_HOVER_RING_STROKE_WIDTH_PX * handleUnitsPerPixel}
2750
+ strokeWidth={ENDPOINT_HOVER_RING_STROKE_WIDTH_PX * endpointUnitsPerPixel}
2729
2751
  style={{ opacity: isHovered || isActive ? 1 : 0, transition: HOVER_TRANSITION }}
2730
2752
  vectorEffect="non-scaling-stroke"
2731
2753
  />