@pascal-app/editor 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (440) hide show
  1. package/package.json +11 -9
  2. package/src/components/editor/action-menu-button.tsx +83 -0
  3. package/src/components/editor/bake-thumbnail.tsx +315 -4
  4. package/src/components/editor/capture-level-picker.tsx +72 -0
  5. package/src/components/editor/custom-camera-controls.tsx +202 -57
  6. package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
  7. package/src/components/editor/duplicate-node.ts +244 -0
  8. package/src/components/editor/editor-layout-mobile.tsx +36 -25
  9. package/src/components/editor/editor-layout-v2.tsx +131 -58
  10. package/src/components/editor/export-manager.tsx +34 -8
  11. package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
  12. package/src/components/editor/first-person/build-collider-world.ts +6 -2
  13. package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
  14. package/src/components/editor/first-person/mechanism-targets.ts +36 -0
  15. package/src/components/editor/first-person-controls.tsx +205 -2
  16. package/src/components/editor/floating-action-menu.tsx +63 -207
  17. package/src/components/editor/floor-aware-camera-controls.test.ts +442 -0
  18. package/src/components/editor/floor-aware-camera-controls.ts +324 -0
  19. package/src/components/editor/floor-openings-overlay.tsx +243 -0
  20. package/src/components/editor/floor-region-controls.tsx +846 -0
  21. package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
  22. package/src/components/editor/floor-surface.test.ts +165 -0
  23. package/src/components/editor/floor-surface.ts +182 -0
  24. package/src/components/editor/floorplan-background-selection.test.ts +11 -2
  25. package/src/components/editor/floorplan-background-selection.ts +1 -3
  26. package/src/components/editor/floorplan-panel.tsx +344 -342
  27. package/src/components/editor/footprint-height-handle.tsx +204 -0
  28. package/src/components/editor/group-actions.ts +54 -2
  29. package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
  30. package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
  31. package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
  32. package/src/components/editor/handles/use-handle-drag.ts +269 -197
  33. package/src/components/editor/index.tsx +294 -169
  34. package/src/components/editor/join-walls-pill.tsx +44 -0
  35. package/src/components/editor/node-action-menu.tsx +68 -70
  36. package/src/components/editor/node-arrow-handles.tsx +25 -6
  37. package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
  38. package/src/components/editor/open-wall-ends-hint.tsx +89 -0
  39. package/src/components/editor/quick-measurement-hud.tsx +3 -2
  40. package/src/components/editor/registry-action-contributions.test.tsx +63 -0
  41. package/src/components/editor/registry-action-contributions.tsx +80 -1
  42. package/src/components/editor/room-controls.tsx +847 -0
  43. package/src/components/editor/room-floor-highlight.tsx +128 -0
  44. package/src/components/editor/room-handles-2d.tsx +354 -0
  45. package/src/components/editor/room-handles.tsx +675 -0
  46. package/src/components/editor/room-highlight.tsx +225 -0
  47. package/src/components/editor/room-labels.test.ts +357 -0
  48. package/src/components/editor/room-labels.tsx +990 -0
  49. package/src/components/editor/room-transform-ghost.tsx +256 -0
  50. package/src/components/editor/room-webgpu-materials.test.ts +27 -0
  51. package/src/components/editor/selection-manager.tsx +451 -317
  52. package/src/components/editor/slab-hole-highlights.tsx +3 -0
  53. package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
  54. package/src/components/editor/snapshot-capture.test.ts +52 -4
  55. package/src/components/editor/snapshot-capture.ts +30 -5
  56. package/src/components/editor/thumbnail-generator.test.ts +308 -0
  57. package/src/components/editor/thumbnail-generator.tsx +1035 -229
  58. package/src/components/editor/use-floorplan-background-placement.ts +31 -11
  59. package/src/components/editor/vector-edge-extractor.tsx +121 -0
  60. package/src/components/editor/views/host-view.tsx +61 -0
  61. package/src/components/editor/views/plugins-view.tsx +670 -0
  62. package/src/components/editor/views/scene-region.tsx +59 -0
  63. package/src/components/editor/views/use-editor-views.ts +57 -0
  64. package/src/components/editor/views/view-bar.tsx +220 -0
  65. package/src/components/editor/wall-justification.test.ts +140 -0
  66. package/src/components/editor/wall-move-side-handles.tsx +43 -142
  67. package/src/components/editor/wall-push-arrow.tsx +92 -0
  68. package/src/components/editor/wall-region-handles.tsx +509 -0
  69. package/src/components/editor/wall-snap-beacon-layer.tsx +3 -0
  70. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
  71. package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
  72. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
  73. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
  74. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +113 -102
  75. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
  76. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
  77. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
  78. package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
  79. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
  80. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
  81. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +291 -227
  82. package/src/components/editor-2d/svg-paths.ts +12 -15
  83. package/src/components/procedural-items/inspector.tsx +40 -15
  84. package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
  85. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
  86. package/src/components/systems/zone/zone-system.test.ts +47 -0
  87. package/src/components/systems/zone/zone-system.tsx +29 -68
  88. package/src/components/tools/item/placement-history.test.tsx +474 -0
  89. package/src/components/tools/item/placement-strategies.ts +8 -4
  90. package/src/components/tools/item/use-draft-node.test.tsx +432 -1
  91. package/src/components/tools/item/use-draft-node.ts +166 -95
  92. package/src/components/tools/item/use-placement-coordinator.tsx +93 -46
  93. package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
  94. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
  95. package/src/components/tools/registry/move-registry-node-tool.tsx +3 -1
  96. package/src/components/tools/select/box-select-tool.tsx +2 -12
  97. package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
  98. package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
  99. package/src/components/tools/select/select-candidates.test.ts +37 -4
  100. package/src/components/tools/select/select-candidates.ts +15 -11
  101. package/src/components/tools/shared/cursor-sphere.tsx +36 -12
  102. package/src/components/tools/site/site-boundary-editor.tsx +1 -2
  103. package/src/components/tools/stair/stair-defaults.ts +0 -3
  104. package/src/components/tools/stair/stair-tool.tsx +90 -42
  105. package/src/components/tools/tool-manager.tsx +46 -20
  106. package/src/components/tools/wall/wall-drafting.test.ts +19 -4
  107. package/src/components/tools/zone/zone-tool.tsx +4 -12
  108. package/src/components/ui/action-menu/action-button.tsx +3 -0
  109. package/src/components/ui/action-menu/camera-actions.tsx +2 -1
  110. package/src/components/ui/action-menu/control-modes.tsx +2 -35
  111. package/src/components/ui/action-menu/measurement-control.tsx +3 -10
  112. package/src/components/ui/build-panel/build-panel.tsx +235 -0
  113. package/src/components/ui/command-palette/editor-commands.tsx +4 -30
  114. package/src/components/ui/command-palette/index.tsx +5 -24
  115. package/src/components/ui/controls/finish-swatch.tsx +110 -0
  116. package/src/components/ui/controls/material-paint-panel.tsx +97 -26
  117. package/src/components/ui/controls/material-picker.tsx +4 -1
  118. package/src/components/ui/controls/scene-material-list.tsx +4 -32
  119. package/src/components/ui/controls/select-control.tsx +65 -0
  120. package/src/components/ui/floating-level-selector.tsx +9 -14
  121. package/src/components/ui/helpers/building-helper.tsx +4 -1
  122. package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
  123. package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
  124. package/src/components/ui/helpers/helper-manager.tsx +325 -14
  125. package/src/components/ui/helpers/item-helper.tsx +8 -0
  126. package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
  127. package/src/components/ui/icon-ref.tsx +45 -1
  128. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  129. package/src/components/ui/node-icon-image.tsx +16 -0
  130. package/src/components/ui/panels/collections/collections-popover.tsx +20 -14
  131. package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
  132. package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
  133. package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
  134. package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
  135. package/src/components/ui/panels/multi-field-value.test.ts +26 -3
  136. package/src/components/ui/panels/multi-field-value.ts +6 -3
  137. package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
  138. package/src/components/ui/panels/multi-selection-panel.tsx +43 -4
  139. package/src/components/ui/panels/node-display.ts +8 -3
  140. package/src/components/ui/panels/panel-manager.tsx +103 -68
  141. package/src/components/ui/panels/panel-wrapper.tsx +78 -36
  142. package/src/components/ui/panels/parametric-field-control.tsx +1 -1
  143. package/src/components/ui/panels/parametric-inspector.tsx +4 -4
  144. package/src/components/ui/panels/reference-panel.tsx +2 -3
  145. package/src/components/ui/panels/room-construction-rows.tsx +774 -0
  146. package/src/components/ui/panels/room-name-options.test.ts +55 -0
  147. package/src/components/ui/panels/room-name-options.ts +47 -0
  148. package/src/components/ui/panels/room-openings-field.tsx +110 -0
  149. package/src/components/ui/panels/room-panel.tsx +725 -0
  150. package/src/components/ui/panels/shape-choice.tsx +58 -0
  151. package/src/components/ui/panels/wall-region-list.tsx +109 -0
  152. package/src/components/ui/primitives/color-dot.tsx +36 -24
  153. package/src/components/ui/primitives/sidebar.tsx +2 -0
  154. package/src/components/ui/right-stack.test.ts +29 -0
  155. package/src/components/ui/right-stack.tsx +145 -0
  156. package/src/components/ui/sidebar/icon-rail.tsx +5 -0
  157. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +31 -4
  158. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
  159. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
  160. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
  161. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
  162. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
  163. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
  164. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
  165. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
  166. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
  167. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
  168. package/src/components/ui/sidebar/panels/site-panel/index.tsx +176 -211
  169. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
  170. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
  171. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
  172. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
  173. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
  174. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
  175. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
  176. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
  177. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
  178. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
  179. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +0 -27
  180. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
  181. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
  182. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
  183. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
  184. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +0 -2
  185. package/src/components/ui/sidebar/tab-bar.tsx +10 -0
  186. package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
  187. package/src/components/viewer/floorplan-compass-button.tsx +9 -1
  188. package/src/components/viewer/floorplan-preview.tsx +5 -6
  189. package/src/components/viewer/viewer-hover-label.tsx +34 -0
  190. package/src/components/viewer/viewer-rooms.tsx +175 -0
  191. package/src/components/viewer/viewer-scene-header.tsx +181 -145
  192. package/src/components/viewer/viewer-selection-manager.tsx +124 -0
  193. package/src/components/viewer/viewer-stage.tsx +2 -2
  194. package/src/components/viewer-zone-system.tsx +3 -8
  195. package/src/hooks/use-auto-save.integration.test.tsx +123 -9
  196. package/src/hooks/use-auto-save.ts +34 -8
  197. package/src/hooks/use-keyboard.test.ts +178 -3
  198. package/src/hooks/use-keyboard.ts +89 -71
  199. package/src/hooks/use-open-wall-ends.test.ts +99 -0
  200. package/src/hooks/use-open-wall-ends.ts +238 -0
  201. package/src/hooks/use-placement-notice.test.ts +48 -0
  202. package/src/hooks/use-placement-notice.ts +42 -0
  203. package/src/hooks/use-selected-room.ts +143 -0
  204. package/src/index.tsx +246 -39
  205. package/src/lib/ceiling-edit-session.test.ts +321 -0
  206. package/src/lib/ceiling-edit-session.ts +206 -0
  207. package/src/lib/ceiling-region-session.test.ts +179 -0
  208. package/src/lib/continuation.test.ts +19 -1
  209. package/src/lib/continuation.ts +25 -4
  210. package/src/lib/door-interaction.ts +14 -0
  211. package/src/lib/editor-level-display.test.ts +96 -0
  212. package/src/lib/editor-level-display.ts +48 -0
  213. package/src/lib/editor-views.ts +144 -0
  214. package/src/lib/export-geometry-wait.test.ts +90 -0
  215. package/src/lib/floor-edit-session.ts +153 -0
  216. package/src/lib/floor-footprints.test.ts +563 -0
  217. package/src/lib/floor-footprints.ts +395 -0
  218. package/src/lib/floor-foundation-support.test.ts +283 -0
  219. package/src/lib/floor-opening-draft.ts +218 -0
  220. package/src/lib/floor-openings.test.ts +216 -0
  221. package/src/lib/floor-region-geometry.ts +191 -0
  222. package/src/lib/floor-region-session.test.ts +485 -0
  223. package/src/lib/floor-region-session.ts +589 -0
  224. package/src/lib/floor-region-snap.ts +101 -0
  225. package/src/lib/floor-step-hover.test.ts +32 -0
  226. package/src/lib/floorplan/annotation-visibility.ts +17 -0
  227. package/src/lib/floorplan/dimension-drive.test.ts +360 -0
  228. package/src/lib/floorplan/dimension-drive.ts +479 -0
  229. package/src/lib/floorplan/floorplan-export.test.ts +12 -8
  230. package/src/lib/floorplan/floorplan-export.tsx +32 -8
  231. package/src/lib/floorplan/floorplan-extension.ts +32 -0
  232. package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
  233. package/src/lib/floorplan/floorplan-mode.ts +2 -0
  234. package/src/lib/floorplan/floorplan-pdfkit-document.ts +339 -18
  235. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
  236. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -25
  237. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
  238. package/src/lib/floorplan/floorplan-readonly.ts +6 -0
  239. package/src/lib/floorplan/geometry.ts +13 -6
  240. package/src/lib/floorplan/index.ts +0 -9
  241. package/src/lib/floorplan/items.ts +5 -2
  242. package/src/lib/floorplan/marks.test.ts +213 -0
  243. package/src/lib/floorplan/marks.ts +238 -0
  244. package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
  245. package/src/lib/floorplan/open-wall-ends.ts +162 -0
  246. package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
  247. package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
  248. package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
  249. package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
  250. package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
  251. package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
  252. package/src/lib/floorplan/schedules.test.ts +189 -0
  253. package/src/lib/floorplan/schedules.ts +339 -0
  254. package/src/lib/floorplan/sheet-export.test.ts +100 -0
  255. package/src/lib/floorplan/sheet-export.ts +318 -0
  256. package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -0
  257. package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
  258. package/src/lib/floorplan/site-plan/contributors.ts +97 -0
  259. package/src/lib/floorplan/site-plan/coverage.ts +232 -0
  260. package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
  261. package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
  262. package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
  263. package/src/lib/floorplan/site-plan/front-edge.ts +156 -0
  264. package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
  265. package/src/lib/floorplan/site-plan/geometry.ts +557 -0
  266. package/src/lib/floorplan/site-plan/index.ts +71 -0
  267. package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
  268. package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
  269. package/src/lib/floorplan/site-plan/site-parts.ts +379 -0
  270. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
  271. package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
  272. package/src/lib/floorplan/site-plan/street-name.ts +302 -0
  273. package/src/lib/floorplan/site-plan/yards.test.ts +69 -0
  274. package/src/lib/floorplan/types.ts +1 -27
  275. package/src/lib/fresh-planar-placement.test.ts +36 -0
  276. package/src/lib/fresh-planar-placement.ts +59 -36
  277. package/src/lib/geometry-script/author.ts +152 -0
  278. package/src/lib/geometry-script/client.ts +152 -0
  279. package/src/lib/geometry-script/protocol.ts +12 -0
  280. package/src/lib/gesture-lifecycle.test.ts +595 -0
  281. package/src/lib/gesture-lifecycle.ts +290 -0
  282. package/src/lib/glb-export-zone-holes.test.ts +41 -0
  283. package/src/lib/glb-export.test.ts +485 -0
  284. package/src/lib/glb-export.ts +237 -119
  285. package/src/lib/history.test.ts +6 -0
  286. package/src/lib/history.ts +7 -0
  287. package/src/lib/hud-title.ts +109 -0
  288. package/src/lib/hydration-gated-storage.ts +49 -0
  289. package/src/lib/ifc-export.test.ts +174 -0
  290. package/src/lib/ifc-export.ts +201 -0
  291. package/src/lib/ifc-parts.ts +15 -0
  292. package/src/lib/inspector-expanded.test.ts +44 -0
  293. package/src/lib/inspector-expanded.ts +36 -0
  294. package/src/lib/interaction/scope.ts +18 -0
  295. package/src/lib/keyboard-pan.test.ts +129 -7
  296. package/src/lib/keyboard-pan.ts +161 -12
  297. package/src/lib/live-gesture-round-trip.test.ts +360 -0
  298. package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
  299. package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
  300. package/src/lib/lot/clean-ring.test.ts +186 -0
  301. package/src/lib/lot/clean-ring.ts +217 -0
  302. package/src/lib/lot/dossier.test.ts +202 -0
  303. package/src/lib/lot/dossier.ts +498 -0
  304. package/src/lib/lot/drop-in.test.ts +326 -0
  305. package/src/lib/lot/drop-in.ts +407 -0
  306. package/src/lib/lot/index.ts +43 -0
  307. package/src/lib/lot/lot-patch.test.ts +340 -0
  308. package/src/lib/lot/lot-patch.ts +394 -0
  309. package/src/lib/lot/parcel-provider.ts +43 -0
  310. package/src/lib/lot/terrain.test.ts +161 -0
  311. package/src/lib/lot/terrain.ts +266 -0
  312. package/src/lib/material-catalog-model.ts +3 -1
  313. package/src/lib/material-paint-panel-model.ts +9 -5
  314. package/src/lib/material-paint.ts +4 -0
  315. package/src/lib/mezzanine-actions.test.ts +342 -0
  316. package/src/lib/mezzanine-draft.test.ts +501 -0
  317. package/src/lib/mezzanine-draft.ts +278 -0
  318. package/src/lib/mezzanine-messages.ts +26 -0
  319. package/src/lib/mezzanine-selection.test.ts +207 -0
  320. package/src/lib/mezzanine-stairs.ts +46 -0
  321. package/src/lib/model-export.ts +3 -1
  322. package/src/lib/node-action-movement.ts +17 -6
  323. package/src/lib/node-display-label.ts +37 -0
  324. package/src/lib/orbit-floor-clearance.test.ts +165 -0
  325. package/src/lib/orbit-floor-clearance.ts +106 -0
  326. package/src/lib/paint-commit-route.test.ts +37 -0
  327. package/src/lib/paint-commit-route.ts +17 -0
  328. package/src/lib/paint-eyedropper.test.tsx +165 -0
  329. package/src/lib/paint-eyedropper.ts +99 -0
  330. package/src/lib/paint-pass-through.test.ts +111 -0
  331. package/src/lib/paint-pass-through.ts +76 -0
  332. package/src/lib/paint-region-hover.ts +25 -0
  333. package/src/lib/paint-region-mode.ts +154 -0
  334. package/src/lib/paint-regions.test.ts +166 -0
  335. package/src/lib/paint-regions.ts +192 -0
  336. package/src/lib/paint-scope.test.ts +317 -35
  337. package/src/lib/paint-scope.ts +266 -25
  338. package/src/lib/panel-rows.ts +21 -0
  339. package/src/lib/parametric-node-update.ts +1 -1
  340. package/src/lib/plate-paint-affected.test.ts +300 -0
  341. package/src/lib/plate-paint-affected.ts +177 -0
  342. package/src/lib/plugin-chat.ts +50 -0
  343. package/src/lib/plugin-panels.test.ts +25 -0
  344. package/src/lib/plugin-panels.ts +94 -0
  345. package/src/lib/print-shell-compiler.ts +1 -0
  346. package/src/lib/registered-draft-snap.ts +12 -0
  347. package/src/lib/roof-duplication.ts +10 -2
  348. package/src/lib/room-assembly-overlay.test.ts +275 -0
  349. package/src/lib/room-assembly-overlay.ts +266 -0
  350. package/src/lib/room-built-on.test.ts +379 -0
  351. package/src/lib/room-built-on.ts +138 -0
  352. package/src/lib/room-construction-commands.ts +712 -0
  353. package/src/lib/room-construction.test.ts +746 -0
  354. package/src/lib/room-construction.ts +385 -0
  355. package/src/lib/room-controls-phase6.test.ts +630 -0
  356. package/src/lib/room-divide-session.ts +523 -0
  357. package/src/lib/room-handle-drag.ts +1068 -0
  358. package/src/lib/room-name-catalog.test.ts +90 -0
  359. package/src/lib/room-name-catalog.ts +168 -0
  360. package/src/lib/room-push-dimensions.test.ts +311 -0
  361. package/src/lib/room-push-dimensions.ts +251 -0
  362. package/src/lib/room-selection-commands.ts +62 -0
  363. package/src/lib/room-selection.test.ts +973 -0
  364. package/src/lib/room-selection.ts +417 -0
  365. package/src/lib/room-structure-commands.ts +94 -0
  366. package/src/lib/room-structure.test.ts +921 -0
  367. package/src/lib/room-transform-session.ts +570 -0
  368. package/src/lib/room-zone-routing.test.ts +294 -0
  369. package/src/lib/room-zone-routing.ts +197 -0
  370. package/src/lib/scene-clipboard.test.ts +364 -0
  371. package/src/lib/scene-clipboard.ts +229 -48
  372. package/src/lib/scene-material-refs.test.ts +79 -0
  373. package/src/lib/scene-material-refs.ts +79 -0
  374. package/src/lib/scene.test.ts +24 -36
  375. package/src/lib/scene.ts +33 -57
  376. package/src/lib/selection-routing.test.ts +129 -45
  377. package/src/lib/selection-routing.ts +53 -74
  378. package/src/lib/session-writes.test.ts +120 -0
  379. package/src/lib/session-writes.ts +205 -0
  380. package/src/lib/sidebar-panel.ts +62 -0
  381. package/src/lib/site-boundary.test.ts +4 -6
  382. package/src/lib/site-boundary.ts +1 -1
  383. package/src/lib/snapping-mode.test.ts +4 -0
  384. package/src/lib/snapping-mode.ts +8 -1
  385. package/src/lib/terrace-draft.test.ts +145 -0
  386. package/src/lib/terrace-draft.ts +213 -0
  387. package/src/lib/tool-cursor-icon.test.ts +16 -0
  388. package/src/lib/tool-cursor-icon.ts +11 -0
  389. package/src/lib/typing-target.ts +9 -0
  390. package/src/lib/units.ts +77 -39
  391. package/src/lib/vector-edges.test.ts +58 -0
  392. package/src/lib/vector-edges.ts +400 -0
  393. package/src/lib/viewer-selection.test.ts +577 -0
  394. package/src/lib/viewer-selection.ts +569 -0
  395. package/src/lib/wall-draw-variant.test.ts +71 -0
  396. package/src/lib/wall-draw-variant.ts +91 -0
  397. package/src/lib/wall-paint-region-session.ts +246 -0
  398. package/src/lib/wall-paint-region.test.ts +373 -0
  399. package/src/lib/wall-polygon-draft.test.ts +262 -0
  400. package/src/lib/wall-polygon-draft.ts +286 -0
  401. package/src/lib/wall-push.test.ts +453 -0
  402. package/src/lib/wall-region-face.ts +352 -0
  403. package/src/lib/wall-region-handles.test.ts +304 -0
  404. package/src/lib/wall-region-handles.ts +368 -0
  405. package/src/lib/wall-region-list.test.ts +119 -0
  406. package/src/lib/wall-region-list.ts +136 -0
  407. package/src/lib/wall-region-snap.ts +110 -0
  408. package/src/lib/window-interaction.ts +14 -0
  409. package/src/lib/zone-content.ts +4 -1
  410. package/src/lib/zone-name.test.ts +17 -0
  411. package/src/lib/zone-name.ts +17 -0
  412. package/src/store/opens-in-select-mode.test.ts +223 -0
  413. package/src/store/paint-mode.test.ts +188 -0
  414. package/src/store/terrain-sculpt-mode.test.ts +19 -21
  415. package/src/store/tool-mode.test.ts +137 -6
  416. package/src/store/use-delete-confirmation.ts +28 -1
  417. package/src/store/use-drawing-view.test.ts +24 -3
  418. package/src/store/use-drawing-view.ts +29 -1
  419. package/src/store/use-editor.tsx +432 -321
  420. package/src/store/use-floorplan-draft-preview.ts +13 -0
  421. package/src/store/use-floorplan-marquee.ts +12 -2
  422. package/src/store/use-quick-measurement-hud.ts +3 -3
  423. package/src/store/use-stair-build-preview.ts +13 -5
  424. package/src/store/use-wall-move-ghosts.ts +4 -2
  425. package/src/store/use-wall-region-handles-selection.test.ts +222 -0
  426. package/src/store/use-wall-region-handles-selection.ts +139 -0
  427. package/src/store/view-layout.test.ts +175 -0
  428. package/src/store/view-layout.ts +196 -0
  429. package/src/test-utils/immediate-animation-frames.ts +16 -0
  430. package/src/test-utils/live-gesture-round-trip.ts +224 -0
  431. package/src/test-utils/selection-harness.ts +82 -0
  432. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
  433. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
  434. package/src/components/tools/fence/fence-drafting.test.ts +0 -100
  435. package/src/components/tools/fence/fence-drafting.ts +0 -266
  436. package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
  437. package/src/components/viewer/viewer-units-panel.tsx +0 -116
  438. package/src/lib/floorplan/stairs.ts +0 -489
  439. package/src/lib/level-duplication.test.ts +0 -144
  440. package/src/lib/level-duplication.ts +0 -195
@@ -1,13 +1,18 @@
1
1
  import { afterAll, beforeAll, expect, test } from 'bun:test'
2
2
  import {
3
3
  acceptsKeyboardPan,
4
+ advanceKeyboardPanMotion,
4
5
  clearKeyboardPanKeys,
6
+ createKeyboardPanMotion,
5
7
  hasKeyboardPanInput,
6
8
  isKeyboardPanKey,
9
+ KEYBOARD_PAN_RAMP_SECONDS,
10
+ KEYBOARD_PAN_START_FRACTION,
11
+ KEYBOARD_PAN_STOP_SECONDS,
7
12
  type KeyboardPanState,
8
- keyboardPanDirection,
9
13
  keyboardPanSpeed,
10
14
  setKeyboardPanKey,
15
+ syncKeyboardPanMotion,
11
16
  } from './keyboard-pan'
12
17
 
13
18
  const idle = (): KeyboardPanState => ({
@@ -16,6 +21,51 @@ const idle = (): KeyboardPanState => ({
16
21
  left: false,
17
22
  right: false,
18
23
  })
24
+
25
+ /**
26
+ * Frames every `1000 / hz` ms from `phaseMs`; `codes` go down at `pressMs` and
27
+ * up `holdMs` later, synced at the edges like the views do. Returns the total
28
+ * travel in cruise-seconds and how long the glide lasted.
29
+ */
30
+ function simulate({
31
+ codes = ['KeyD'],
32
+ holdMs,
33
+ hz = 60,
34
+ phaseMs = 0,
35
+ pressMs = 1000,
36
+ }: {
37
+ codes?: string[]
38
+ holdMs: number
39
+ hz?: number
40
+ phaseMs?: number
41
+ pressMs?: number
42
+ }) {
43
+ const motion = createKeyboardPanMotion()
44
+ const keys = idle()
45
+ const releaseMs = pressMs + holdMs
46
+ let x = 0
47
+ let y = 0
48
+ let pressed = false
49
+ let released = false
50
+ let restMs = releaseMs
51
+ for (let now = phaseMs; now < releaseMs + 2000; now += 1000 / hz) {
52
+ if (!pressed && now >= pressMs) {
53
+ syncKeyboardPanMotion(motion, keys, pressMs)
54
+ for (const code of codes) setKeyboardPanKey(keys, code, true)
55
+ pressed = true
56
+ }
57
+ if (!released && now >= releaseMs) {
58
+ syncKeyboardPanMotion(motion, keys, releaseMs)
59
+ clearKeyboardPanKeys(keys)
60
+ released = true
61
+ }
62
+ if (advanceKeyboardPanMotion(motion, keys, now)) restMs = now
63
+ x += motion.stepX
64
+ y += motion.stepY
65
+ }
66
+ return { x, y, glideMs: restMs - releaseMs, motion }
67
+ }
68
+
19
69
  const key = (init: Partial<KeyboardEvent>) => ({ target: null, ...init }) as KeyboardEvent
20
70
 
21
71
  // No DOM in this runner: stand in for the element classes the editable check reads.
@@ -39,9 +89,15 @@ test('physical WASD keys drive a screen-space direction; letters on other layout
39
89
  expect(setKeyboardPanKey(state, 'KeyW', true)).toBe(true)
40
90
  expect(setKeyboardPanKey(state, 'KeyW', true)).toBe(false)
41
91
  setKeyboardPanKey(state, 'KeyD', true)
42
- expect(keyboardPanDirection(state)).toEqual({ horizontal: 1, vertical: 1 })
92
+ const motion = createKeyboardPanMotion()
93
+ syncKeyboardPanMotion(motion, state, 0)
94
+ advanceKeyboardPanMotion(motion, state, 100)
95
+ expect(motion.stepX).toBeGreaterThan(0)
96
+ expect(motion.stepY).toBeCloseTo(motion.stepX)
43
97
  setKeyboardPanKey(state, 'KeyA', true)
44
- expect(keyboardPanDirection(state)).toEqual({ horizontal: 0, vertical: 1 })
98
+ advanceKeyboardPanMotion(motion, state, 200)
99
+ expect(motion.stepX).toBe(0)
100
+ expect(motion.stepY).toBeGreaterThan(0)
45
101
  clearKeyboardPanKeys(state)
46
102
  expect(hasKeyboardPanInput(state)).toBe(false)
47
103
  })
@@ -55,8 +111,74 @@ test('modifier chords stay shortcuts, and typing in a field never pans', () => {
55
111
  expect(acceptsKeyboardPan(key({ target: new Input() }))).toBe(false)
56
112
  })
57
113
 
58
- test('speed scales with the visible width within fixed bounds', () => {
59
- expect(keyboardPanSpeed(0)).toBe(2)
60
- expect(keyboardPanSpeed(20)).toBeCloseTo(13)
61
- expect(keyboardPanSpeed(1000)).toBe(55)
114
+ test('cruise speed scales with the visible width within fixed bounds', () => {
115
+ expect(keyboardPanSpeed(0)).toBe(0.5)
116
+ expect(keyboardPanSpeed(20)).toBeCloseTo(6)
117
+ expect(keyboardPanSpeed(1000)).toBe(30)
118
+ })
119
+
120
+ test('a press starts just under cruise and reaches it within the ramp', () => {
121
+ const at = (holdSeconds: number) => {
122
+ const motion = createKeyboardPanMotion()
123
+ syncKeyboardPanMotion(motion, idle(), 0)
124
+ advanceKeyboardPanMotion(motion, { ...idle(), right: true }, holdSeconds * 1000)
125
+ return motion.velocityX
126
+ }
127
+ expect(at(0.001)).toBeCloseTo(KEYBOARD_PAN_START_FRACTION, 2)
128
+ expect(at(KEYBOARD_PAN_RAMP_SECONDS / 2)).toBeGreaterThan(KEYBOARD_PAN_START_FRACTION)
129
+ expect(at(KEYBOARD_PAN_RAMP_SECONDS / 2)).toBeLessThan(1)
130
+ expect(at(KEYBOARD_PAN_RAMP_SECONDS)).toBe(1)
131
+ })
132
+
133
+ test('a tap moves a small, predictable amount and a hold travels at cruise', () => {
134
+ // 100 ms ramping up from the start fraction, plus a short glide.
135
+ const tap = simulate({ holdMs: 100 }).x
136
+ expect(tap).toBeGreaterThan(0.1 * KEYBOARD_PAN_START_FRACTION)
137
+ expect(tap).toBeLessThan(0.1)
138
+ const hold = simulate({ holdMs: 2000 }).x
139
+ expect(hold).toBeGreaterThan(1.98)
140
+ expect(hold).toBeLessThan(2.02)
141
+ })
142
+
143
+ test('a tap counts from the key events, not from the frames around them', () => {
144
+ const taps = [0, 5, 11, 16].map((phaseMs) => simulate({ holdMs: 100, phaseMs }).x)
145
+ const slowFrames = [0, 40, 80].map((phaseMs) => simulate({ holdMs: 100, hz: 10, phaseMs }).x)
146
+ for (const tap of [...taps, ...slowFrames]) expect(tap).toBeCloseTo(taps[0] ?? 0, 2)
147
+ // Shorter than a frame still moves, by its own length.
148
+ expect(simulate({ holdMs: 8, phaseMs: 1 }).x).toBeGreaterThan(0.006)
149
+ })
150
+
151
+ test('release comes to rest within the stop time, with a barely visible glide', () => {
152
+ const hold = simulate({ codes: ['KeyW'], holdMs: 1000 })
153
+ expect(hold.glideMs).toBeLessThanOrEqual(KEYBOARD_PAN_STOP_SECONDS * 1000 + 1000 / 60)
154
+ // Glide under 3% of a cruise-second: about 1% of the view width.
155
+ const held1s = 1 - ((1 - KEYBOARD_PAN_START_FRACTION) * KEYBOARD_PAN_RAMP_SECONDS) / 3
156
+ expect(hold.y - held1s).toBeGreaterThan(0)
157
+ expect(hold.y - held1s).toBeLessThan(0.03)
158
+ expect(hold.motion.velocityY).toBe(0)
159
+ })
160
+
161
+ test('travel does not depend on the frame rate', () => {
162
+ const at60 = simulate({ holdMs: 1000 }).x
163
+ expect(simulate({ holdMs: 1000, hz: 144 }).x).toBeCloseTo(at60, 3)
164
+ expect(simulate({ holdMs: 1000, hz: 20 }).x).toBeCloseTo(at60, 2)
165
+ })
166
+
167
+ test('diagonals move no faster than a straight press', () => {
168
+ const straight = simulate({ holdMs: 1000 })
169
+ const diagonal = simulate({ codes: ['KeyD', 'KeyW'], holdMs: 1000 })
170
+ expect(Math.hypot(diagonal.x, diagonal.y)).toBeCloseTo(straight.x)
171
+ expect(diagonal.x).toBeCloseTo(diagonal.y)
172
+ })
173
+
174
+ test('a press during the glide resumes from the current speed', () => {
175
+ const motion = createKeyboardPanMotion()
176
+ const right = { ...idle(), right: true }
177
+ syncKeyboardPanMotion(motion, idle(), 0)
178
+ syncKeyboardPanMotion(motion, right, 1000)
179
+ syncKeyboardPanMotion(motion, idle(), 1002)
180
+ const glidingSpeed = motion.velocityX
181
+ expect(glidingSpeed).toBeGreaterThan(KEYBOARD_PAN_START_FRACTION)
182
+ advanceKeyboardPanMotion(motion, right, 1003)
183
+ expect(motion.velocityX).toBeGreaterThanOrEqual(glidingSpeed)
62
184
  })
@@ -9,9 +9,23 @@ export type KeyboardPanState = {
9
9
  right: boolean
10
10
  }
11
11
 
12
- const KEYBOARD_PAN_VIEW_WIDTH_PER_SECOND = 0.65
13
- const KEYBOARD_PAN_MIN_SPEED = 2
14
- const KEYBOARD_PAN_MAX_SPEED = 55
12
+ // Tuning. Speeds are in visible view widths per second, so a pan feels the
13
+ // same at any zoom; the world-unit clamps only bite on tiny or huge views.
14
+ /** Cruise speed once a key is held, in view widths per second. */
15
+ export const KEYBOARD_PAN_CRUISE_VIEW_WIDTHS_PER_SECOND = 0.3
16
+ /** Share of cruise speed a fresh press starts at. */
17
+ export const KEYBOARD_PAN_START_FRACTION = 0.55
18
+ /** Seconds of holding to reach cruise speed. */
19
+ export const KEYBOARD_PAN_RAMP_SECONDS = 0.22
20
+ /** Seconds to come to rest after the last key is released. */
21
+ export const KEYBOARD_PAN_STOP_SECONDS = 0.09
22
+ const KEYBOARD_PAN_MIN_SPEED = 0.5
23
+ const KEYBOARD_PAN_MAX_SPEED = 30
24
+ // A stalled frame (tab switch, long task) must not turn into one big jump.
25
+ const KEYBOARD_PAN_MAX_STEP_SECONDS = 0.25
26
+ // Release decays exponentially; three time constants leave 5%, then it snaps to rest.
27
+ const KEYBOARD_PAN_STOP_TIME_CONSTANT = KEYBOARD_PAN_STOP_SECONDS / 3
28
+ const KEYBOARD_PAN_REST_FRACTION = 0.05
15
29
 
16
30
  export function isEditableKeyboardTarget(target: EventTarget | null) {
17
31
  return (
@@ -72,18 +86,153 @@ export function acceptsKeyboardPan(event: KeyboardEvent) {
72
86
  )
73
87
  }
74
88
 
75
- /** Screen-space direction: `horizontal` +1 is right, `vertical` +1 is forward (up). */
76
- export function keyboardPanDirection(state: KeyboardPanState) {
89
+ /** Cruise speed, in world units per second, for a view `viewWidth` wide. */
90
+ export function keyboardPanSpeed(viewWidth: number) {
91
+ return Math.min(
92
+ Math.max(viewWidth * KEYBOARD_PAN_CRUISE_VIEW_WIDTHS_PER_SECOND, KEYBOARD_PAN_MIN_SPEED),
93
+ KEYBOARD_PAN_MAX_SPEED,
94
+ )
95
+ }
96
+
97
+ /**
98
+ * Per-view motion carried between frames. Velocity and step are screen space
99
+ * (+x right, +y forward) in shares of cruise speed; multiply the step by
100
+ * `keyboardPanSpeed` for world units.
101
+ */
102
+ export type KeyboardPanMotion = {
103
+ holding: boolean
104
+ /** Time on the ramp's clock, so a press during the glide resumes its speed. */
105
+ heldSeconds: number
106
+ velocityX: number
107
+ velocityY: number
108
+ /** This frame's travel, in cruise-seconds. */
109
+ stepX: number
110
+ stepY: number
111
+ // Travel integrated at key edges since the last frame.
112
+ pendingX: number
113
+ pendingY: number
114
+ /** `performance.now()` the motion was last advanced to; null before the first press. */
115
+ time: number | null
116
+ }
117
+
118
+ export function createKeyboardPanMotion(): KeyboardPanMotion {
77
119
  return {
78
- horizontal: (state.right ? 1 : 0) - (state.left ? 1 : 0),
79
- vertical: (state.forward ? 1 : 0) - (state.backward ? 1 : 0),
120
+ holding: false,
121
+ heldSeconds: 0,
122
+ velocityX: 0,
123
+ velocityY: 0,
124
+ stepX: 0,
125
+ stepY: 0,
126
+ pendingX: 0,
127
+ pendingY: 0,
128
+ time: null,
80
129
  }
81
130
  }
82
131
 
83
- /** World units per second for a view `viewWidth` wide. */
84
- export function keyboardPanSpeed(viewWidth: number) {
85
- return Math.min(
86
- Math.max(viewWidth * KEYBOARD_PAN_VIEW_WIDTH_PER_SECOND, KEYBOARD_PAN_MIN_SPEED),
87
- KEYBOARD_PAN_MAX_SPEED,
132
+ export function resetKeyboardPanMotion(motion: KeyboardPanMotion) {
133
+ motion.holding = false
134
+ motion.heldSeconds = 0
135
+ motion.velocityX = 0
136
+ motion.velocityY = 0
137
+ motion.stepX = 0
138
+ motion.stepY = 0
139
+ motion.pendingX = 0
140
+ motion.pendingY = 0
141
+ motion.time = null
142
+ }
143
+
144
+ // Speed eases out of the start fraction into cruise: s(u) = S + (1 - S)(2u - u²), u = t / ramp.
145
+ const rampSpeed = (seconds: number) => {
146
+ const u = Math.min(seconds / KEYBOARD_PAN_RAMP_SECONDS, 1)
147
+ return KEYBOARD_PAN_START_FRACTION + (1 - KEYBOARD_PAN_START_FRACTION) * (2 * u - u * u)
148
+ }
149
+
150
+ // ∫₀ᵗ s, exact, so the distance does not depend on the frame rate.
151
+ const rampDistance = (seconds: number) => {
152
+ const u = seconds / KEYBOARD_PAN_RAMP_SECONDS
153
+ const eased = u < 1 ? u * u - (u * u * u) / 3 : u - 1 / 3
154
+ return (
155
+ KEYBOARD_PAN_START_FRACTION * seconds +
156
+ (1 - KEYBOARD_PAN_START_FRACTION) * KEYBOARD_PAN_RAMP_SECONDS * eased
157
+ )
158
+ }
159
+
160
+ const rampSecondsForSpeed = (speed: number) => {
161
+ const eased = (speed - KEYBOARD_PAN_START_FRACTION) / (1 - KEYBOARD_PAN_START_FRACTION)
162
+ if (eased <= 0) return 0
163
+ if (eased >= 1) return KEYBOARD_PAN_RAMP_SECONDS
164
+ return KEYBOARD_PAN_RAMP_SECONDS * (1 - Math.sqrt(1 - eased))
165
+ }
166
+
167
+ /**
168
+ * Integrates `motion` up to `now` (`performance.now()`) under `keys`. Call it
169
+ * with the keys as they were just before one changes, so a press or release
170
+ * counts from the moment it happened rather than from the next frame.
171
+ */
172
+ export function syncKeyboardPanMotion(
173
+ motion: KeyboardPanMotion,
174
+ keys: KeyboardPanState,
175
+ now: number,
176
+ ) {
177
+ const elapsed = motion.time === null ? 0 : (now - motion.time) / 1000
178
+ motion.time = motion.time === null ? now : Math.max(motion.time, now)
179
+ const dt = Math.min(Math.max(elapsed, 0), KEYBOARD_PAN_MAX_STEP_SECONDS)
180
+ const horizontal = (keys.right ? 1 : 0) - (keys.left ? 1 : 0)
181
+ const vertical = (keys.forward ? 1 : 0) - (keys.backward ? 1 : 0)
182
+
183
+ if (horizontal !== 0 || vertical !== 0) {
184
+ if (!motion.holding) {
185
+ motion.holding = true
186
+ motion.heldSeconds = rampSecondsForSpeed(Math.hypot(motion.velocityX, motion.velocityY))
187
+ }
188
+ const from = motion.heldSeconds
189
+ motion.heldSeconds = from + dt
190
+ // Diagonals travel at the same speed as straight moves.
191
+ const length = Math.hypot(horizontal, vertical)
192
+ const distance = rampDistance(motion.heldSeconds) - rampDistance(from)
193
+ const speed = rampSpeed(motion.heldSeconds)
194
+ motion.pendingX += (horizontal / length) * distance
195
+ motion.pendingY += (vertical / length) * distance
196
+ motion.velocityX = (horizontal / length) * speed
197
+ motion.velocityY = (vertical / length) * speed
198
+ return
199
+ }
200
+
201
+ motion.holding = false
202
+ motion.heldSeconds = 0
203
+ if (motion.velocityX === 0 && motion.velocityY === 0) return
204
+ const decay = Math.exp(-dt / KEYBOARD_PAN_STOP_TIME_CONSTANT)
205
+ const glide = KEYBOARD_PAN_STOP_TIME_CONSTANT * (1 - decay)
206
+ motion.pendingX += motion.velocityX * glide
207
+ motion.pendingY += motion.velocityY * glide
208
+ motion.velocityX *= decay
209
+ motion.velocityY *= decay
210
+ if (Math.hypot(motion.velocityX, motion.velocityY) < KEYBOARD_PAN_REST_FRACTION) {
211
+ motion.velocityX = 0
212
+ motion.velocityY = 0
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Once per frame: syncs `motion` to `now` and leaves the travel since the
218
+ * last frame in `stepX`/`stepY`. Returns false once the
219
+ * view is at rest with nothing left to apply.
220
+ */
221
+ export function advanceKeyboardPanMotion(
222
+ motion: KeyboardPanMotion,
223
+ keys: KeyboardPanState,
224
+ now: number,
225
+ ): boolean {
226
+ syncKeyboardPanMotion(motion, keys, now)
227
+ motion.stepX = motion.pendingX
228
+ motion.stepY = motion.pendingY
229
+ motion.pendingX = 0
230
+ motion.pendingY = 0
231
+ return (
232
+ motion.holding ||
233
+ motion.velocityX !== 0 ||
234
+ motion.velocityY !== 0 ||
235
+ motion.stepX !== 0 ||
236
+ motion.stepY !== 0
88
237
  )
89
238
  }