@weasel-js/core 1.11.0 → 1.12.0

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 (586) hide show
  1. package/CHANGELOG.md +611 -0
  2. package/README.md +1 -1
  3. package/dist/WeaselProvider.js +1 -1
  4. package/dist/affordances/pathAnchors.js +10 -6
  5. package/dist/affordances/pathAnchors.js.map +1 -1
  6. package/dist/animation/index.js +3 -3
  7. package/dist/animation/rig/bindRig.js +1 -1
  8. package/dist/animation/rig/rigNode.js.map +1 -1
  9. package/dist/barrel/canvas.js +9 -3
  10. package/dist/barrel/interactions.js +5 -4
  11. package/dist/barrel/paint.js +4 -4
  12. package/dist/barrel/paths.js +10 -6
  13. package/dist/barrel/renderer.js +5 -3
  14. package/dist/barrel/routing.js +7 -12
  15. package/dist/barrel/scene.js +9 -5
  16. package/dist/barrel/text.js +2 -2
  17. package/dist/barrel/viewport.js +9 -4
  18. package/dist/canvas/Canvas.js +5 -2
  19. package/dist/canvas/Canvas.js.map +1 -1
  20. package/dist/canvas/CanvasView.js +23 -34
  21. package/dist/canvas/CanvasView.js.map +1 -1
  22. package/dist/canvas/ModalityHud.js +1 -1
  23. package/dist/canvas/NodeShape.js +4 -1
  24. package/dist/canvas/NodeShape.js.map +1 -1
  25. package/dist/canvas/SceneCanvas/ActionsProviderIfRoot.js +1 -1
  26. package/dist/canvas/SceneCanvas/DepRegistryProviderIfRoot.js +1 -1
  27. package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +1 -1
  28. package/dist/canvas/SceneCanvas/SceneCanvasInput.js +17 -3
  29. package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
  30. package/dist/canvas/SceneCanvas/SurfaceDeclarations.js +23 -0
  31. package/dist/canvas/SceneCanvas/SurfaceDeclarations.js.map +1 -0
  32. package/dist/canvas/SceneCanvas/builtinToolSets.js +30 -0
  33. package/dist/canvas/SceneCanvas/builtinToolSets.js.map +1 -0
  34. package/dist/canvas/SceneCanvas/canvasPassThrough.js +2 -0
  35. package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
  36. package/dist/canvas/SceneCanvas/coordTrace.js +5 -16
  37. package/dist/canvas/SceneCanvas/coordTrace.js.map +1 -1
  38. package/dist/canvas/SceneCanvas/defaultNodeProperties.js +5 -0
  39. package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
  40. package/dist/canvas/SceneCanvas/features.js +45 -5
  41. package/dist/canvas/SceneCanvas/features.js.map +1 -1
  42. package/dist/canvas/SceneCanvas/props.js +1 -11
  43. package/dist/canvas/SceneCanvas/props.js.map +1 -1
  44. package/dist/canvas/SceneCanvas/scenePointQuery.js +7 -3
  45. package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -1
  46. package/dist/canvas/SceneCanvas/toolRegistry.js +8 -4
  47. package/dist/canvas/SceneCanvas/toolRegistry.js.map +1 -1
  48. package/dist/canvas/SceneCanvas/useActionTunings.js +6 -1
  49. package/dist/canvas/SceneCanvas/useActionTunings.js.map +1 -1
  50. package/dist/canvas/SceneCanvas/useActionsPropResolver.js +1 -11
  51. package/dist/canvas/SceneCanvas/useActionsPropResolver.js.map +1 -1
  52. package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js.map +1 -1
  53. package/dist/canvas/SceneCanvas/useBuiltinTools.js +2 -9
  54. package/dist/canvas/SceneCanvas/useBuiltinTools.js.map +1 -1
  55. package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js +94 -0
  56. package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js.map +1 -0
  57. package/dist/canvas/SceneCanvas/useCanvasSelection.js +7 -2
  58. package/dist/canvas/SceneCanvas/useCanvasSelection.js.map +1 -1
  59. package/dist/canvas/SceneCanvas/useCanvasTools.js +15 -10
  60. package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
  61. package/dist/canvas/SceneCanvas/useNodeKinds.js +1 -1
  62. package/dist/canvas/SceneCanvas/useNodeKinds.js.map +1 -1
  63. package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js +9 -3
  64. package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js.map +1 -1
  65. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +23 -4
  66. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
  67. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +2 -3
  68. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
  69. package/dist/canvas/SceneCanvas/useSceneChrome.js +6 -7
  70. package/dist/canvas/SceneCanvas/useSceneChrome.js.map +1 -1
  71. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js +24 -7
  72. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js.map +1 -1
  73. package/dist/canvas/SceneCanvas/useScenePicking.js +2 -3
  74. package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
  75. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +2 -1
  76. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  77. package/dist/canvas/SceneCanvas/useSceneStandardActions.js +5 -5
  78. package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
  79. package/dist/canvas/SceneCanvas.js +27 -24
  80. package/dist/canvas/SceneCanvas.js.map +1 -1
  81. package/dist/canvas/SurfaceCanvas.js +5 -22
  82. package/dist/canvas/SurfaceCanvas.js.map +1 -1
  83. package/dist/canvas/ViewMarker.js +35 -0
  84. package/dist/canvas/ViewMarker.js.map +1 -0
  85. package/dist/canvas/canvasRenderer.js +9 -5
  86. package/dist/canvas/canvasRenderer.js.map +1 -1
  87. package/dist/canvas/contextAttributes.js +14 -0
  88. package/dist/canvas/contextAttributes.js.map +1 -0
  89. package/dist/canvas/defaultDrawOne.js +2 -0
  90. package/dist/canvas/defaultDrawOne.js.map +1 -1
  91. package/dist/canvas/deps/booleans.js +2 -1
  92. package/dist/canvas/deps/booleans.js.map +1 -1
  93. package/dist/canvas/deps/editAnchors.js +9 -113
  94. package/dist/canvas/deps/editAnchors.js.map +1 -1
  95. package/dist/canvas/deps/hitTestArea.js +2 -2
  96. package/dist/canvas/deps/hitTestArea.js.map +1 -1
  97. package/dist/canvas/deps/insert.js +10 -4
  98. package/dist/canvas/deps/insert.js.map +1 -1
  99. package/dist/canvas/deps/pathEditCommits.js +189 -0
  100. package/dist/canvas/deps/pathEditCommits.js.map +1 -0
  101. package/dist/canvas/deps/snap.js +12 -9
  102. package/dist/canvas/deps/snap.js.map +1 -1
  103. package/dist/canvas/deps/transformFollowers.js +20 -0
  104. package/dist/canvas/deps/transformFollowers.js.map +1 -0
  105. package/dist/canvas/derivedPath.js +20 -1
  106. package/dist/canvas/derivedPath.js.map +1 -1
  107. package/dist/canvas/drawnBounds.js +38 -4
  108. package/dist/canvas/drawnBounds.js.map +1 -1
  109. package/dist/canvas/inheritedPaints.js +44 -0
  110. package/dist/canvas/inheritedPaints.js.map +1 -0
  111. package/dist/canvas/nodeShapes/derivedPainter.js +23 -16
  112. package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -1
  113. package/dist/canvas/nodeShapes/nodePaint.js +13 -34
  114. package/dist/canvas/nodeShapes/nodePaint.js.map +1 -1
  115. package/dist/canvas/nodeShapes/pathPainter.js +12 -10
  116. package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
  117. package/dist/canvas/nodeShapes/rectFallbackPainter.js +4 -5
  118. package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -1
  119. package/dist/canvas/nodeShapes/shapePainter.js +12 -11
  120. package/dist/canvas/nodeShapes/shapePainter.js.map +1 -1
  121. package/dist/canvas/nodeShapes/textPainter.js +13 -12
  122. package/dist/canvas/nodeShapes/textPainter.js.map +1 -1
  123. package/dist/canvas/overlayRoleStrokes.js +12 -0
  124. package/dist/canvas/overlayRoleStrokes.js.map +1 -1
  125. package/dist/canvas/pathOverlay.js +27 -7
  126. package/dist/canvas/pathOverlay.js.map +1 -1
  127. package/dist/canvas/pickReach.js +3 -3
  128. package/dist/canvas/pickReach.js.map +1 -1
  129. package/dist/canvas/pickWalk.js +3 -1
  130. package/dist/canvas/pickWalk.js.map +1 -1
  131. package/dist/canvas/renderDebugSnapshot.js +1 -0
  132. package/dist/canvas/renderDebugSnapshot.js.map +1 -1
  133. package/dist/canvas/renderSceneToPixels.js +7 -6
  134. package/dist/canvas/renderSceneToPixels.js.map +1 -1
  135. package/dist/canvas/sceneAdapter.js.map +1 -1
  136. package/dist/canvas/sceneSlotWiring.js +2 -1
  137. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  138. package/dist/canvas/sceneViewRender.js +2 -1
  139. package/dist/canvas/sceneViewRender.js.map +1 -1
  140. package/dist/canvas/surfaceContribution.js +12 -8
  141. package/dist/canvas/surfaceContribution.js.map +1 -1
  142. package/dist/canvas/useCanvasPaint.js +2 -1
  143. package/dist/canvas/useCanvasPaint.js.map +1 -1
  144. package/dist/canvas/useCanvasView.js +4 -2
  145. package/dist/canvas/useCanvasView.js.map +1 -1
  146. package/dist/canvas/useInFlightBounds.js +52 -0
  147. package/dist/canvas/useInFlightBounds.js.map +1 -0
  148. package/dist/canvas/useLayerStack.js +6 -2
  149. package/dist/canvas/useLayerStack.js.map +1 -1
  150. package/dist/canvas/useViewAim.js +121 -0
  151. package/dist/canvas/useViewAim.js.map +1 -0
  152. package/dist/canvas/useViewHelpers.js +7 -9
  153. package/dist/canvas/useViewHelpers.js.map +1 -1
  154. package/dist/canvas/useViewNesting.js +3 -2
  155. package/dist/canvas/useViewNesting.js.map +1 -1
  156. package/dist/canvas/useViewRegistrySurface.js +4 -2
  157. package/dist/canvas/useViewRegistrySurface.js.map +1 -1
  158. package/dist/canvas/viewMarkers.js +63 -0
  159. package/dist/canvas/viewMarkers.js.map +1 -0
  160. package/dist/canvas/viewRegistry.js +27 -1
  161. package/dist/canvas/viewRegistry.js.map +1 -1
  162. package/dist/clipboard.d.ts +3 -2
  163. package/dist/{clone-DZDrfIlU.d.ts → clone-C3e0OTuF.d.ts} +8 -21
  164. package/dist/clone.d.ts +4 -3
  165. package/dist/core/geometry/autoPoseDescriptor.js +1 -1
  166. package/dist/core/ops/setDependsOn.js +37 -0
  167. package/dist/core/ops/setDependsOn.js.map +1 -0
  168. package/dist/core/ops/writtenBy.js +53 -0
  169. package/dist/core/ops/writtenBy.js.map +1 -0
  170. package/dist/core/pages/fitViewToPage.js +12 -1
  171. package/dist/core/pages/fitViewToPage.js.map +1 -1
  172. package/dist/core/pages/pageEdits.js +163 -0
  173. package/dist/core/pages/pageEdits.js.map +1 -0
  174. package/dist/core/pages/pages.js +30 -1
  175. package/dist/core/pages/pages.js.map +1 -1
  176. package/dist/core/paint/meshKindLazy.js +59 -2
  177. package/dist/core/paint/meshKindLazy.js.map +1 -1
  178. package/dist/core/scene/NodeRouting.js +8 -3
  179. package/dist/core/scene/NodeRouting.js.map +1 -1
  180. package/dist/core/scene/arrivalWindow.js.map +1 -1
  181. package/dist/core/scene/carryDescendants.js +51 -0
  182. package/dist/core/scene/carryDescendants.js.map +1 -0
  183. package/dist/core/scene/clipNothing.js +13 -0
  184. package/dist/core/scene/clipNothing.js.map +1 -0
  185. package/dist/core/scene/depMemo.js +11 -3
  186. package/dist/core/scene/depMemo.js.map +1 -1
  187. package/dist/core/scene/dependents.js +7 -1
  188. package/dist/core/scene/dependents.js.map +1 -1
  189. package/dist/core/scene/derivedParts.js +10 -11
  190. package/dist/core/scene/derivedParts.js.map +1 -1
  191. package/dist/core/scene/docValues.js +57 -0
  192. package/dist/core/scene/docValues.js.map +1 -0
  193. package/dist/core/scene/editPass.js +111 -0
  194. package/dist/core/scene/editPass.js.map +1 -0
  195. package/dist/core/scene/effectivePose.js +13 -2
  196. package/dist/core/scene/effectivePose.js.map +1 -1
  197. package/dist/core/scene/fieldEdits.js +4 -1
  198. package/dist/core/scene/fieldEdits.js.map +1 -1
  199. package/dist/core/scene/fontReads.js +37 -0
  200. package/dist/core/scene/fontReads.js.map +1 -0
  201. package/dist/core/scene/historyFacade.js +1 -0
  202. package/dist/core/scene/historyFacade.js.map +1 -1
  203. package/dist/core/scene/inheritedPaint.js +75 -0
  204. package/dist/core/scene/inheritedPaint.js.map +1 -0
  205. package/dist/core/scene/kitRegistry.js +5 -0
  206. package/dist/core/scene/kitRegistry.js.map +1 -1
  207. package/dist/core/scene/layoutPass.js +28 -3
  208. package/dist/core/scene/layoutPass.js.map +1 -1
  209. package/dist/core/scene/nodeMemo.js +15 -7
  210. package/dist/core/scene/nodeMemo.js.map +1 -1
  211. package/dist/core/scene/pathEffects/blend.js.map +1 -1
  212. package/dist/core/scene/pathEffects/derive.js +7 -2
  213. package/dist/core/scene/pathEffects/derive.js.map +1 -1
  214. package/dist/core/scene/pathEffects/effects.js +3 -1
  215. package/dist/core/scene/pathEffects/effects.js.map +1 -1
  216. package/dist/core/scene/pathEffects/textAlong.js +95 -0
  217. package/dist/core/scene/pathEffects/textAlong.js.map +1 -0
  218. package/dist/core/scene/recorder.js +28 -19
  219. package/dist/core/scene/recorder.js.map +1 -1
  220. package/dist/core/scene/scene.js +15 -9
  221. package/dist/core/scene/scene.js.map +1 -1
  222. package/dist/core/scene/sceneChanges.js +13 -0
  223. package/dist/core/scene/sceneChanges.js.map +1 -0
  224. package/dist/core/scene/sceneEdits.js +8 -1
  225. package/dist/core/scene/sceneEdits.js.map +1 -1
  226. package/dist/core/scene/sceneMutators.js +1 -0
  227. package/dist/core/scene/sceneMutators.js.map +1 -1
  228. package/dist/core/scene/sceneRecording.js +20 -3
  229. package/dist/core/scene/sceneRecording.js.map +1 -1
  230. package/dist/core/scene/sceneSelection.js +4 -1
  231. package/dist/core/scene/sceneSelection.js.map +1 -1
  232. package/dist/core/scene/sceneState.js +11 -5
  233. package/dist/core/scene/sceneState.js.map +1 -1
  234. package/dist/core/scene/serialization.js +5 -3
  235. package/dist/core/scene/serialization.js.map +1 -1
  236. package/dist/core/scene/types.js.map +1 -1
  237. package/dist/core/scene/unitScopes.js +19 -1
  238. package/dist/core/scene/unitScopes.js.map +1 -1
  239. package/dist/core/selection/usePublishSceneSelection.js +6 -12
  240. package/dist/core/selection/usePublishSceneSelection.js.map +1 -1
  241. package/dist/core/shapeKinds.js +25 -11
  242. package/dist/core/shapeKinds.js.map +1 -1
  243. package/dist/{cornerHandles-Camw_yeu.d.ts → cornerHandles-p8fDIAKt.d.ts} +3 -2
  244. package/dist/debug/createDebugOverlayLayer.js +16 -111
  245. package/dist/debug/createDebugOverlayLayer.js.map +1 -1
  246. package/dist/debug/createDebugSink.js +2 -1
  247. package/dist/debug/createDebugSink.js.map +1 -1
  248. package/dist/debug/defaultTheme.js +4 -2
  249. package/dist/debug/defaultTheme.js.map +1 -1
  250. package/dist/debug/features.js +17 -0
  251. package/dist/debug/features.js.map +1 -0
  252. package/dist/debug/overlayPanels.js +117 -0
  253. package/dist/debug/overlayPanels.js.map +1 -0
  254. package/dist/debug/parseDebugFlags.js +4 -13
  255. package/dist/debug/parseDebugFlags.js.map +1 -1
  256. package/dist/{depSchema-Cgfu_G9s.d.ts → depSchema-mG19aEdD.d.ts} +174 -904
  257. package/dist/devtools/handlers.js +112 -0
  258. package/dist/devtools/handlers.js.map +1 -0
  259. package/dist/devtools/history.js +71 -0
  260. package/dist/devtools/history.js.map +1 -0
  261. package/dist/devtools/hook.js +74 -0
  262. package/dist/devtools/hook.js.map +1 -0
  263. package/dist/devtools/label.js +25 -0
  264. package/dist/devtools/label.js.map +1 -0
  265. package/dist/devtools/notices.js +58 -0
  266. package/dist/devtools/notices.js.map +1 -0
  267. package/dist/devtools/protocol.js +7 -0
  268. package/dist/devtools/protocol.js.map +1 -0
  269. package/dist/devtools/registry.js +90 -0
  270. package/dist/devtools/registry.js.map +1 -0
  271. package/dist/devtools/root.js +80 -0
  272. package/dist/devtools/root.js.map +1 -0
  273. package/dist/devtools/serialize.js +86 -0
  274. package/dist/devtools/serialize.js.map +1 -0
  275. package/dist/devtools/session.js +111 -0
  276. package/dist/devtools/session.js.map +1 -0
  277. package/dist/devtools/transport.js +67 -0
  278. package/dist/devtools/transport.js.map +1 -0
  279. package/dist/devtools/useDevtoolsRoot.js +81 -0
  280. package/dist/devtools/useDevtoolsRoot.js.map +1 -0
  281. package/dist/devtools-protocol.d.ts +435 -0
  282. package/dist/devtools-protocol.js +7 -0
  283. package/dist/devtools.d.ts +8 -0
  284. package/dist/devtools.js +12 -0
  285. package/dist/devtools.js.map +1 -0
  286. package/dist/features/chrome-caps/useHoverTracking.js +16 -10
  287. package/dist/features/chrome-caps/useHoverTracking.js.map +1 -1
  288. package/dist/features/ingestion/contentHandlers.js +25 -2
  289. package/dist/features/ingestion/contentHandlers.js.map +1 -1
  290. package/dist/features/measure/measureCommands.js +7 -5
  291. package/dist/features/measure/measureCommands.js.map +1 -1
  292. package/dist/features/measure/measureContribution.js +4 -2
  293. package/dist/features/measure/measureContribution.js.map +1 -1
  294. package/dist/features/modes/modeTriggers.js +7 -3
  295. package/dist/features/modes/modeTriggers.js.map +1 -1
  296. package/dist/features/pathEffects/effectSources.js +60 -0
  297. package/dist/features/pathEffects/effectSources.js.map +1 -0
  298. package/dist/features/pathEffects/readSource.js +17 -1
  299. package/dist/features/pathEffects/readSource.js.map +1 -1
  300. package/dist/features/pathEffects/textItems.js +53 -0
  301. package/dist/features/pathEffects/textItems.js.map +1 -0
  302. package/dist/features/pathEffects/useEffectSourcesLayer.js +57 -0
  303. package/dist/features/pathEffects/useEffectSourcesLayer.js.map +1 -0
  304. package/dist/features/paths/index.js +2 -2
  305. package/dist/features/paths/pathEditExtension.js.map +1 -1
  306. package/dist/features/paths/pathEditState.js +5 -2
  307. package/dist/features/paths/pathEditState.js.map +1 -1
  308. package/dist/features/paths/pathSnap/pathSnapSource.js +5 -5
  309. package/dist/features/paths/pathSnap/pathSnapSource.js.map +1 -1
  310. package/dist/features/paths/pathSnap/sceneIndex.js +10 -3
  311. package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
  312. package/dist/features/selection/selectionPose.js +4 -1
  313. package/dist/features/selection/selectionPose.js.map +1 -1
  314. package/dist/features/snap/gather.js +9 -1
  315. package/dist/features/snap/gather.js.map +1 -1
  316. package/dist/features/snap/lazySession.js +17 -0
  317. package/dist/features/snap/lazySession.js.map +1 -0
  318. package/dist/features/snap/marks.js.map +1 -1
  319. package/dist/features/snap/probes2d.js +27 -19
  320. package/dist/features/snap/probes2d.js.map +1 -1
  321. package/dist/features/snap/readoutLayer.js.map +1 -1
  322. package/dist/features/snap/roles.js +2 -1
  323. package/dist/features/snap/roles.js.map +1 -1
  324. package/dist/features/snap/rotationSource.js +25 -8
  325. package/dist/features/snap/rotationSource.js.map +1 -1
  326. package/dist/features/snap/sceneSession2d.js +6 -4
  327. package/dist/features/snap/sceneSession2d.js.map +1 -1
  328. package/dist/features/snap/session2d.js +37 -23
  329. package/dist/features/snap/session2d.js.map +1 -1
  330. package/dist/features/snap/spanSnap.js +5 -2
  331. package/dist/features/snap/spanSnap.js.map +1 -1
  332. package/dist/features/text/sceneTextDrafts.js +65 -0
  333. package/dist/features/text/sceneTextDrafts.js.map +1 -0
  334. package/dist/features/text/textHitAt.js +31 -2
  335. package/dist/features/text/textHitAt.js.map +1 -1
  336. package/dist/features/text/useSceneTextEdit.js +22 -18
  337. package/dist/features/text/useSceneTextEdit.js.map +1 -1
  338. package/dist/features/viewports/aim.js +105 -0
  339. package/dist/features/viewports/aim.js.map +1 -0
  340. package/dist/features/viewports/viewMarker.js +82 -0
  341. package/dist/features/viewports/viewMarker.js.map +1 -0
  342. package/dist/features/viewports/viewMarkerDrag.js +55 -0
  343. package/dist/features/viewports/viewMarkerDrag.js.map +1 -0
  344. package/dist/icons.table.js +1 -1
  345. package/dist/icons.table.js.map +1 -1
  346. package/dist/icons.table.json +0 -10
  347. package/dist/index.d.ts +1198 -894
  348. package/dist/index.js +55 -38
  349. package/dist/interactions/actions/align/action.js +6 -4
  350. package/dist/interactions/actions/align/action.js.map +1 -1
  351. package/dist/interactions/actions/align/align.js.map +1 -1
  352. package/dist/interactions/actions/booleans/action.js +7 -1
  353. package/dist/interactions/actions/booleans/action.js.map +1 -1
  354. package/dist/interactions/actions/booleans/booleans.js +2 -1
  355. package/dist/interactions/actions/booleans/booleans.js.map +1 -1
  356. package/dist/interactions/actions/booleans/operands.js +6 -5
  357. package/dist/interactions/actions/booleans/operands.js.map +1 -1
  358. package/dist/interactions/actions/clipboard/action.js +27 -17
  359. package/dist/interactions/actions/clipboard/action.js.map +1 -1
  360. package/dist/interactions/actions/clone/action.js +1 -1
  361. package/dist/interactions/actions/clone/behaviors.js +4 -4
  362. package/dist/interactions/actions/clone/behaviors.js.map +1 -1
  363. package/dist/interactions/actions/defaults/anchorEditing.js +44 -23
  364. package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
  365. package/dist/interactions/actions/defaults/anchorJoin.js +12 -6
  366. package/dist/interactions/actions/defaults/anchorJoin.js.map +1 -1
  367. package/dist/interactions/actions/defaults/bendSegment.js +8 -5
  368. package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
  369. package/dist/interactions/actions/defaults/cancelGesture.js +11 -8
  370. package/dist/interactions/actions/defaults/cancelGesture.js.map +1 -1
  371. package/dist/interactions/actions/defaults/contextMenu.js +17 -8
  372. package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
  373. package/dist/interactions/actions/defaults/createPaintAction.js +2 -2
  374. package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
  375. package/dist/interactions/actions/defaults/delete.js +64 -41
  376. package/dist/interactions/actions/defaults/delete.js.map +1 -1
  377. package/dist/interactions/actions/defaults/duplicate.js +12 -9
  378. package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
  379. package/dist/interactions/actions/defaults/enterPathEdit.js +19 -9
  380. package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
  381. package/dist/interactions/actions/defaults/escape.js +11 -8
  382. package/dist/interactions/actions/defaults/escape.js.map +1 -1
  383. package/dist/interactions/actions/defaults/exitPathEdit.js +7 -4
  384. package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
  385. package/dist/interactions/actions/defaults/group.js +21 -15
  386. package/dist/interactions/actions/defaults/group.js.map +1 -1
  387. package/dist/interactions/actions/defaults/ingest.js +17 -9
  388. package/dist/interactions/actions/defaults/ingest.js.map +1 -1
  389. package/dist/interactions/actions/defaults/insertPathAnchor.js +7 -4
  390. package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
  391. package/dist/interactions/actions/defaults/nudge.js +21 -40
  392. package/dist/interactions/actions/defaults/nudge.js.map +1 -1
  393. package/dist/interactions/actions/defaults/pathEditTools.js +11 -8
  394. package/dist/interactions/actions/defaults/pathEditTools.js.map +1 -1
  395. package/dist/interactions/actions/defaults/pinchZoom.js +7 -4
  396. package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
  397. package/dist/interactions/actions/defaults/reorder.js +24 -6
  398. package/dist/interactions/actions/defaults/reorder.js.map +1 -1
  399. package/dist/interactions/actions/defaults/selectAll.js +8 -5
  400. package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
  401. package/dist/interactions/actions/defaults/setFill.js +7 -3
  402. package/dist/interactions/actions/defaults/setFill.js.map +1 -1
  403. package/dist/interactions/actions/defaults/setFillOpacity.js +1 -1
  404. package/dist/interactions/actions/defaults/setStroke.js +12 -11
  405. package/dist/interactions/actions/defaults/setStroke.js.map +1 -1
  406. package/dist/interactions/actions/defaults/setStrokeOpacity.js +1 -1
  407. package/dist/interactions/actions/defaults/toolActivate.js +7 -1
  408. package/dist/interactions/actions/defaults/toolActivate.js.map +1 -1
  409. package/dist/interactions/actions/defaults/toolResetToDefault.js +7 -4
  410. package/dist/interactions/actions/defaults/toolResetToDefault.js.map +1 -1
  411. package/dist/interactions/actions/defaults/undoRedo.js +19 -13
  412. package/dist/interactions/actions/defaults/undoRedo.js.map +1 -1
  413. package/dist/interactions/actions/defaults/viewportWheelPan.js +16 -7
  414. package/dist/interactions/actions/defaults/viewportWheelPan.js.map +1 -1
  415. package/dist/interactions/actions/defaults/viewportZoom.js +24 -6
  416. package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
  417. package/dist/interactions/actions/distribute/action.js +6 -4
  418. package/dist/interactions/actions/distribute/action.js.map +1 -1
  419. package/dist/interactions/actions/drawnBoxes.js +2 -1
  420. package/dist/interactions/actions/drawnBoxes.js.map +1 -1
  421. package/dist/interactions/actions/edit-anchors/action.js +10 -10
  422. package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
  423. package/dist/interactions/actions/edit-anchors/preview.js +41 -8
  424. package/dist/interactions/actions/edit-anchors/preview.js.map +1 -1
  425. package/dist/interactions/actions/flip/action.js +14 -6
  426. package/dist/interactions/actions/flip/action.js.map +1 -1
  427. package/dist/interactions/actions/gestureLifecycle.js +5 -2
  428. package/dist/interactions/actions/gestureLifecycle.js.map +1 -1
  429. package/dist/interactions/actions/insert/action.js +1 -2
  430. package/dist/interactions/actions/insert/action.js.map +1 -1
  431. package/dist/interactions/actions/insert/commit.js +1 -1
  432. package/dist/interactions/actions/insert/parent.js +11 -0
  433. package/dist/interactions/actions/insert/parent.js.map +1 -0
  434. package/dist/interactions/actions/insert/placeAction.js +7 -2
  435. package/dist/interactions/actions/insert/placeAction.js.map +1 -1
  436. package/dist/interactions/actions/insert/repositionAction.js +2 -1
  437. package/dist/interactions/actions/insert/repositionAction.js.map +1 -1
  438. package/dist/interactions/actions/insert/session.js +5 -1
  439. package/dist/interactions/actions/insert/session.js.map +1 -1
  440. package/dist/interactions/actions/move/action.js +15 -8
  441. package/dist/interactions/actions/move/action.js.map +1 -1
  442. package/dist/interactions/actions/move/behaviors/layoutDrop.js +0 -1
  443. package/dist/interactions/actions/move/behaviors/layoutDrop.js.map +1 -1
  444. package/dist/interactions/actions/move/behaviors/snapToContainer.js +1 -1
  445. package/dist/interactions/actions/move/capture.js +1 -1
  446. package/dist/interactions/actions/move/commit/index.js +0 -2
  447. package/dist/interactions/actions/move/commit/index.js.map +1 -1
  448. package/dist/interactions/actions/move/commit/layoutDrop.js +2 -2
  449. package/dist/interactions/actions/move/commit/layoutRelease.js +2 -2
  450. package/dist/interactions/actions/move/commit/reparent.js +11 -5
  451. package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
  452. package/dist/interactions/actions/move/frame.js +4 -10
  453. package/dist/interactions/actions/move/frame.js.map +1 -1
  454. package/dist/interactions/actions/move/landedDescendants.js +20 -17
  455. package/dist/interactions/actions/move/landedDescendants.js.map +1 -1
  456. package/dist/interactions/actions/move/layout/container.js +2 -1
  457. package/dist/interactions/actions/move/layout/container.js.map +1 -1
  458. package/dist/interactions/actions/move/layout/dragged.js +1 -1
  459. package/dist/interactions/actions/move/layout/dropPass.js +18 -1
  460. package/dist/interactions/actions/move/layout/dropPass.js.map +1 -1
  461. package/dist/interactions/actions/move/layout/exits.js +5 -5
  462. package/dist/interactions/actions/move/layout/exits.js.map +1 -1
  463. package/dist/interactions/actions/move/session.js +14 -3
  464. package/dist/interactions/actions/move/session.js.map +1 -1
  465. package/dist/interactions/actions/move/translate.js +8 -37
  466. package/dist/interactions/actions/move/translate.js.map +1 -1
  467. package/dist/interactions/actions/moveNodes.js +87 -0
  468. package/dist/interactions/actions/moveNodes.js.map +1 -0
  469. package/dist/interactions/actions/outlines/createOutlinesAction.js +8 -5
  470. package/dist/interactions/actions/outlines/createOutlinesAction.js.map +1 -1
  471. package/dist/interactions/actions/outlines/outlineStroke.js +5 -3
  472. package/dist/interactions/actions/outlines/outlineStroke.js.map +1 -1
  473. package/dist/interactions/actions/pathCommands/cutJoin.js +8 -5
  474. package/dist/interactions/actions/pathCommands/cutJoin.js.map +1 -1
  475. package/dist/interactions/actions/planeInput.js.map +1 -1
  476. package/dist/interactions/actions/resize/action.js +83 -72
  477. package/dist/interactions/actions/resize/action.js.map +1 -1
  478. package/dist/interactions/actions/resize/commit.js +2 -1
  479. package/dist/interactions/actions/resize/commit.js.map +1 -1
  480. package/dist/interactions/actions/resize/cornerHandles.js +2 -1
  481. package/dist/interactions/actions/resize/cornerHandles.js.map +1 -1
  482. package/dist/interactions/actions/resize/session.js +2 -0
  483. package/dist/interactions/actions/resize/session.js.map +1 -1
  484. package/dist/interactions/actions/resolvePreviews.js +3 -3
  485. package/dist/interactions/actions/resolvePreviews.js.map +1 -1
  486. package/dist/interactions/actions/rotate/action.js +7 -4
  487. package/dist/interactions/actions/rotate/action.js.map +1 -1
  488. package/dist/interactions/actions/rotate/commit.js +2 -1
  489. package/dist/interactions/actions/rotate/commit.js.map +1 -1
  490. package/dist/interactions/actions/rotate/session.js +2 -0
  491. package/dist/interactions/actions/rotate/session.js.map +1 -1
  492. package/dist/interactions/actions/selectionSubtree.js +25 -9
  493. package/dist/interactions/actions/selectionSubtree.js.map +1 -1
  494. package/dist/interactions/actions/testUtils.js.map +1 -1
  495. package/dist/interactions/actions/transform-again/action.js +13 -11
  496. package/dist/interactions/actions/transform-again/action.js.map +1 -1
  497. package/dist/interactions/actions/transformFinish.js +106 -0
  498. package/dist/interactions/actions/transformFinish.js.map +1 -0
  499. package/dist/json.d.ts +4 -0
  500. package/dist/json.js +3 -0
  501. package/dist/kitSettingsSchema.js +8 -5
  502. package/dist/kitSettingsSchema.js.map +1 -1
  503. package/dist/{math-CrJULaZI.d.ts → math-B56zxVs5.d.ts} +10 -1
  504. package/dist/math.d.ts +1 -1
  505. package/dist/math.js +2 -1
  506. package/dist/move.d.ts +5 -4
  507. package/dist/{options-p9GZMXbY.d.ts → options-DBxEryYq.d.ts} +1 -1
  508. package/dist/resize.d.ts +4 -3
  509. package/dist/routing.d.ts +1 -1
  510. package/dist/routing.js +2 -2
  511. package/dist/test-hook/createTestHook.js +1 -1
  512. package/dist/test-hook/createTestHook.js.map +1 -1
  513. package/dist/test-hook/useTestHook.js +2 -1
  514. package/dist/test-hook/useTestHook.js.map +1 -1
  515. package/dist/test-seams.d.ts +9 -5
  516. package/dist/tools/builtin/ellipse/useEllipseTool.js +1 -0
  517. package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
  518. package/dist/tools/builtin/eyedropper/useEyedropperTool.js +2 -1
  519. package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
  520. package/dist/tools/builtin/hand/dragPanContribution.js +4 -1
  521. package/dist/tools/builtin/hand/dragPanContribution.js.map +1 -1
  522. package/dist/tools/builtin/hand/useHandTool.js +7 -4
  523. package/dist/tools/builtin/hand/useHandTool.js.map +1 -1
  524. package/dist/tools/builtin/image/useImageTool.js +1 -0
  525. package/dist/tools/builtin/image/useImageTool.js.map +1 -1
  526. package/dist/tools/builtin/lasso/useLassoTool.js +1 -0
  527. package/dist/tools/builtin/lasso/useLassoTool.js.map +1 -1
  528. package/dist/tools/builtin/line/useLineTool.js +1 -0
  529. package/dist/tools/builtin/line/useLineTool.js.map +1 -1
  530. package/dist/tools/builtin/measure/useMeasureTool.js +12 -5
  531. package/dist/tools/builtin/measure/useMeasureTool.js.map +1 -1
  532. package/dist/tools/builtin/pen/penHandle.js +3 -5
  533. package/dist/tools/builtin/pen/penHandle.js.map +1 -1
  534. package/dist/tools/builtin/pen/usePenTool.js +14 -6
  535. package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
  536. package/dist/tools/builtin/pencil/usePencilTool.js +1 -0
  537. package/dist/tools/builtin/pencil/usePencilTool.js.map +1 -1
  538. package/dist/tools/builtin/polygon/usePolygonTool.js +13 -4
  539. package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
  540. package/dist/tools/builtin/rect/useRectTool.js +1 -0
  541. package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
  542. package/dist/tools/builtin/select/adapterPointQuery.js +3 -2
  543. package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
  544. package/dist/tools/builtin/select/pressDecision.js +32 -0
  545. package/dist/tools/builtin/select/pressDecision.js.map +1 -0
  546. package/dist/tools/builtin/select/selectionContributions.js +22 -13
  547. package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
  548. package/dist/tools/builtin/select/useSelectTool.js +13 -10
  549. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  550. package/dist/tools/builtin/slice/useSliceTool.js +22 -8
  551. package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
  552. package/dist/tools/builtin/star/useStarTool.js +13 -4
  553. package/dist/tools/builtin/star/useStarTool.js.map +1 -1
  554. package/dist/tools/builtin/sticky/stickyNode.js +4 -1
  555. package/dist/tools/builtin/sticky/stickyNode.js.map +1 -1
  556. package/dist/tools/builtin/sticky/useStickyTool.js +10 -5
  557. package/dist/tools/builtin/sticky/useStickyTool.js.map +1 -1
  558. package/dist/tools/builtin/text/useTextTool.js +3 -1
  559. package/dist/tools/builtin/text/useTextTool.js.map +1 -1
  560. package/dist/tools/index.js +26 -0
  561. package/dist/{types-CHVXGfYm.d.ts → types-7puAuVD2.d.ts} +3 -262
  562. package/dist/types-BN9hbdMI.d.ts +184 -0
  563. package/dist/types-CniVctmk.d.ts +949 -0
  564. package/dist/types-oFeQ5VjV.d.ts +264 -0
  565. package/dist/util/paint.js +1 -3
  566. package/dist/util/paint.js.map +1 -1
  567. package/dist/version.js +1 -1
  568. package/package.json +39 -25
  569. package/dist/canvas/SceneCanvas/useBuiltinRigTools.js +0 -65
  570. package/dist/canvas/SceneCanvas/useBuiltinRigTools.js.map +0 -1
  571. package/dist/tools/builtin/bone/boneActions.js +0 -156
  572. package/dist/tools/builtin/bone/boneActions.js.map +0 -1
  573. package/dist/tools/builtin/bone/boneOverlay.js +0 -71
  574. package/dist/tools/builtin/bone/boneOverlay.js.map +0 -1
  575. package/dist/tools/builtin/bone/placedRig.js +0 -28
  576. package/dist/tools/builtin/bone/placedRig.js.map +0 -1
  577. package/dist/tools/builtin/bone/rigEditState.js +0 -30
  578. package/dist/tools/builtin/bone/rigEditState.js.map +0 -1
  579. package/dist/tools/builtin/bone/useBoneTool.js +0 -125
  580. package/dist/tools/builtin/bone/useBoneTool.js.map +0 -1
  581. package/dist/tools/builtin/weight/useWeightTool.js +0 -101
  582. package/dist/tools/builtin/weight/useWeightTool.js.map +0 -1
  583. package/dist/tools/builtin/weight/weightActions.js +0 -140
  584. package/dist/tools/builtin/weight/weightActions.js.map +0 -1
  585. package/dist/tools/builtin/weight/weightOverlay.js +0 -78
  586. package/dist/tools/builtin/weight/weightOverlay.js.map +0 -1
@@ -1,3 +1,4 @@
1
+ import { transformPreview } from "../transformFinish.js";
1
2
  import { closeGesture, runBehaviorCancel } from "../gestureLifecycle.js";
2
3
  import { inPlane, selectionLayer } from "../planeInput.js";
3
4
  import { SNAP_ROLES } from "../../../features/snap/roles.js";
@@ -18,78 +19,88 @@ import { unitsOfBox } from "./replay.js";
18
19
  * carries the constraints, point snaps, and `expandIds`; absent, the invoker
19
20
  * runs no constraints and `ids => ids` expansion.
20
21
  */
21
- var resizeAction = inPlane({
22
- id: "resize",
23
- label: "Resize",
24
- eligible: { capability: "transforms-selection" },
25
- modifierRoles: SNAP_ROLES,
26
- requires: [
27
- "selection",
28
- "scene",
29
- "resizePolicy",
30
- "poseDescriptor",
31
- "applyOps",
32
- "geometryProjection",
33
- "view",
34
- "boundsIncludeStroke",
35
- "kitSettings",
36
- "snap"
37
- ],
38
- invoker: {
39
- timing: "ongoing",
40
- start(ctx, _opts) {
41
- const begun = beginResize(ctx);
42
- if (!begun) return {};
43
- const { session, readers } = begun;
44
- const subject = [...ctx.deps.selection?.get() ?? []];
45
- const hold = session.scene.overrides.hold();
46
- let frame = { previews: /* @__PURE__ */ new Map() };
47
- session.lifecycle.start(session.writeIds);
48
- return {
49
- kind: "resize",
50
- onMove(moveCtx) {
51
- if (!moveCtx.drag) return;
52
- frame = nextFrame(session, readers, moveCtx);
53
- hold.set(frame.previews);
54
- },
55
- onEnd(_endCtx, reason) {
56
- let committed = false;
57
- try {
58
- if (reason === "commit") {
59
- committed = commitResize(session, frame);
60
- if (committed && frame.proposed) {
61
- const to = unitsOfBox(session.box.bounds, frame.proposed);
62
- recordTransform(session.scene, {
63
- kind: "scale",
64
- to,
65
- anchor: session.box.anchor
66
- }, subject);
67
- }
68
- } else runBehaviorCancel(session.constraints, session.gestureCtx);
69
- } finally {
70
- session.snap.end();
71
- closeGesture({
72
- hold,
73
- previews: frame.previews,
74
- behaviors: session.constraints,
75
- ctx: session.gestureCtx,
76
- lifecycle: session.lifecycle
77
- }, committed);
78
- }
79
- },
80
- previewIds: () => frame.previews.keys(),
81
- previewPose: (id) => frame.previews.get(id) ?? null
82
- };
83
- }
84
- },
85
- /**
86
- * Always enabled: the dispatcher treats anything `!== true` as "skip and
87
- * try the next match", so a disabled reason here makes the resize binding
88
- * silently lose to lower-specificity matches (e.g. ambient `insertAction`).
89
- */
90
- enabled: () => true
91
- }, selectionLayer);
22
+ function createResizeAction(opts = {}) {
23
+ const { commit } = opts;
24
+ return inPlane({
25
+ id: "resize",
26
+ label: "Resize",
27
+ eligible: { capability: "transforms-selection" },
28
+ modifierRoles: SNAP_ROLES,
29
+ requires: [
30
+ "selection",
31
+ "scene",
32
+ "resizePolicy",
33
+ "poseDescriptor",
34
+ "applyOps",
35
+ "geometryProjection",
36
+ "view",
37
+ "boundsIncludeStroke",
38
+ "kitSettings",
39
+ "snap",
40
+ "transformFollowers"
41
+ ],
42
+ invoker: {
43
+ timing: "ongoing",
44
+ start(ctx, _opts) {
45
+ const begun = beginResize(ctx);
46
+ if (!begun) return {};
47
+ const { session, readers } = begun;
48
+ const subject = [...ctx.deps.selection?.get() ?? []];
49
+ const hold = session.scene.overrides.hold();
50
+ const preview = transformPreview(ctx.deps, session.scene, () => frame.previews);
51
+ let frame = { previews: /* @__PURE__ */ new Map() };
52
+ session.lifecycle.start(session.writeIds);
53
+ return {
54
+ kind: "resize",
55
+ onMove(moveCtx) {
56
+ if (!moveCtx.drag) return;
57
+ frame = nextFrame(session, readers, moveCtx);
58
+ hold.set(preview.poses());
59
+ },
60
+ onEnd(_endCtx, reason) {
61
+ let committed = false;
62
+ try {
63
+ if (reason === "commit" && commit !== void 0) {
64
+ committed = frame.previews.size > 0;
65
+ if (committed) commit(new Map(frame.previews), ctx.deps);
66
+ } else if (reason === "commit") {
67
+ committed = commitResize(session, frame);
68
+ if (committed && frame.proposed) {
69
+ const to = unitsOfBox(session.box.bounds, frame.proposed);
70
+ recordTransform(session.scene, {
71
+ kind: "scale",
72
+ to,
73
+ anchor: session.box.anchor
74
+ }, subject);
75
+ }
76
+ } else runBehaviorCancel(session.constraints, session.gestureCtx);
77
+ } finally {
78
+ session.snap.end();
79
+ closeGesture({
80
+ hold,
81
+ previews: frame.previews,
82
+ behaviors: session.constraints,
83
+ ctx: session.gestureCtx,
84
+ lifecycle: session.lifecycle
85
+ }, committed);
86
+ }
87
+ },
88
+ previewIds: preview.previewIds,
89
+ previewData: preview.previewData
90
+ };
91
+ }
92
+ },
93
+ /**
94
+ * Always enabled: the dispatcher treats anything `!== true` as "skip and
95
+ * try the next match", so a disabled reason here makes the resize binding
96
+ * silently lose to lower-specificity matches (e.g. ambient `insertAction`).
97
+ */
98
+ enabled: () => true
99
+ }, selectionLayer);
100
+ }
101
+ /** The resize that commits to the scene. */
102
+ var resizeAction = createResizeAction();
92
103
 
93
104
  //#endregion
94
- export { resizeAction };
105
+ export { createResizeAction, resizeAction };
95
106
  //# sourceMappingURL=action.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"action.js","names":[],"sources":["../../../../src/interactions/actions/resize/action.ts"],"sourcesContent":["import type { Action } from '@weasel-js/routing';\nimport type { InvocationCtx, OngoingHandle } from '@weasel-js/routing';\nimport type { NodeId } from 'core/scene/types';\nimport { closeGesture, runBehaviorCancel } from '../gestureLifecycle';\nimport { inPlane, selectionLayer } from '../planeInput';\nimport { commitResize } from './commit';\nimport { nextFrame } from './frame';\nimport { beginResize, type ResizeFrame } from './session';\nimport { unitsOfBox } from './replay';\nimport { recordTransform } from '../transform-again/memory';\nimport { SNAP_ROLES } from 'features/snap/roles';\n\n/**\n * Anchor-relative drag resize, committed as one batch on release. It needs\n * `InvocationCtx.drag.affordance` with a `handle:*` kind.\n *\n * The dragged corner or edge snaps through the `snap` dep's session before\n * the box is resized, held to the handle's axis, and to the box's diagonal\n * while `snap.constrain` (Shift) is held. The optional `resizePolicy` dep\n * carries the constraints, point snaps, and `expandIds`; absent, the invoker\n * runs no constraints and `ids => ids` expansion.\n */\nexport const resizeAction: Action & { requires: string[] } = inPlane({\n id: 'resize',\n label: 'Resize',\n // No default binding: a bare `{ kind: 'drag' }` claims every drag at ambient\n // scope. The resize handles bind it (`selectionResizeBindings`).\n eligible: { capability: 'transforms-selection' },\n modifierRoles: SNAP_ROLES,\n requires: [\n 'selection', 'scene', 'resizePolicy', 'poseDescriptor', 'applyOps', 'geometryProjection', 'view',\n 'boundsIncludeStroke', 'kitSettings', 'snap',\n ],\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx, _opts): OngoingHandle {\n const begun = beginResize(ctx);\n if (!begun) return {};\n const { session, readers } = begun;\n const subject = [...(ctx.deps.selection?.get() ?? [])] as string[];\n const hold = session.scene.overrides.hold();\n let frame: ResizeFrame = { previews: new Map() };\n session.lifecycle.start(session.writeIds as readonly string[]);\n return {\n kind: 'resize',\n onMove(moveCtx: InvocationCtx): void {\n if (!moveCtx.drag) return;\n frame = nextFrame(session, readers, moveCtx);\n hold.set(frame.previews);\n },\n onEnd(_endCtx: InvocationCtx, reason: 'commit' | 'cancel'): void {\n let committed = false;\n try {\n if (reason === 'commit') {\n committed = commitResize(session, frame);\n if (committed && frame.proposed) {\n const to = unitsOfBox(session.box.bounds, frame.proposed);\n recordTransform(session.scene, { kind: 'scale', to, anchor: session.box.anchor }, subject);\n }\n } else runBehaviorCancel(session.constraints, session.gestureCtx);\n } finally {\n session.snap.end();\n closeGesture({ hold, previews: frame.previews, behaviors: session.constraints, ctx: session.gestureCtx, lifecycle: session.lifecycle }, committed);\n }\n },\n previewIds: () => frame.previews.keys(),\n previewPose: (id: string) => frame.previews.get(id as NodeId) ?? null,\n };\n },\n },\n /**\n * Always enabled: the dispatcher treats anything `!== true` as \"skip and\n * try the next match\", so a disabled reason here makes the resize binding\n * silently lose to lower-specificity matches (e.g. ambient `insertAction`).\n */\n enabled: () => true,\n}, selectionLayer);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAsBA,IAAa,eAAgD,QAAQ;CACnE,IAAI;CACJ,OAAO;CAGP,UAAU,EAAE,YAAY,uBAAuB;CAC/C,eAAe;CACf,UAAU;EACR;EAAa;EAAS;EAAgB;EAAkB;EAAY;EAAsB;EAC1F;EAAuB;EAAe;CACxC;CACA,SAAS;EACP,QAAQ;EACR,MAAM,KAAoB,OAAsB;GAC9C,MAAM,QAAQ,YAAY,GAAG;GAC7B,IAAI,CAAC,OAAO,OAAO,CAAC;GACpB,MAAM,EAAE,SAAS,YAAY;GAC7B,MAAM,UAAU,CAAC,GAAI,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,CAAE;GACrD,MAAM,OAAO,QAAQ,MAAM,UAAU,KAAK;GAC1C,IAAI,QAAqB,EAAE,0BAAU,IAAI,IAAI,EAAE;GAC/C,QAAQ,UAAU,MAAM,QAAQ,QAA6B;GAC7D,OAAO;IACL,MAAM;IACN,OAAO,SAA8B;KACnC,IAAI,CAAC,QAAQ,MAAM;KACnB,QAAQ,UAAU,SAAS,SAAS,OAAO;KAC3C,KAAK,IAAI,MAAM,QAAQ;IACzB;IACA,MAAM,SAAwB,QAAmC;KAC/D,IAAI,YAAY;KAChB,IAAI;MACF,IAAI,WAAW,UAAU;OACvB,YAAY,aAAa,SAAS,KAAK;OACvC,IAAI,aAAa,MAAM,UAAU;QAC/B,MAAM,KAAK,WAAW,QAAQ,IAAI,QAAQ,MAAM,QAAQ;QACxD,gBAAgB,QAAQ,OAAO;SAAE,MAAM;SAAS;SAAI,QAAQ,QAAQ,IAAI;QAAO,GAAG,OAAO;OAC3F;MACF,OAAO,kBAAkB,QAAQ,aAAa,QAAQ,UAAU;KAClE,UAAU;MACR,QAAQ,KAAK,IAAI;MACjB,aAAa;OAAE;OAAM,UAAU,MAAM;OAAU,WAAW,QAAQ;OAAa,KAAK,QAAQ;OAAY,WAAW,QAAQ;MAAU,GAAG,SAAS;KACnJ;IACF;IACA,kBAAkB,MAAM,SAAS,KAAK;IACtC,cAAc,OAAe,MAAM,SAAS,IAAI,EAAY,KAAK;GACnE;EACF;CACF;;;;;;CAMA,eAAe;AACjB,GAAG,cAAc"}
1
+ {"version":3,"file":"action.js","names":[],"sources":["../../../../src/interactions/actions/resize/action.ts"],"sourcesContent":["import type { Action, ActionDeps } from '@weasel-js/routing';\nimport type { InvocationCtx, OngoingHandle } from '@weasel-js/routing';\nimport type { NodeId } from 'core/scene/types';\nimport { transformPreview } from '../transformFinish';\nimport { closeGesture, runBehaviorCancel } from '../gestureLifecycle';\nimport { inPlane, selectionLayer } from '../planeInput';\nimport { commitResize } from './commit';\nimport { nextFrame } from './frame';\nimport { beginResize, type ResizeFrame } from './session';\nimport { unitsOfBox } from './replay';\nimport { recordTransform } from '../transform-again/memory';\nimport { SNAP_ROLES } from 'features/snap/roles';\n\n/** Options for {@link createResizeAction}. */\nexport interface ResizeActionOptions {\n /** Runs in place of the scene commit, handed each resized node's pose at\n * release, so a host keeping the geometry as data can store it. The scene\n * is left as it was, and no undo entry is made; the constraints' `onEnd`\n * answers are not asked for. */\n commit?: (poses: ReadonlyMap<NodeId, unknown>, deps: ActionDeps) => void;\n}\n\n/**\n * Anchor-relative drag resize, committed as one batch on release. It needs\n * `InvocationCtx.drag.affordance` with a `handle:*` kind.\n *\n * The dragged corner or edge snaps through the `snap` dep's session before\n * the box is resized, held to the handle's axis, and to the box's diagonal\n * while `snap.constrain` (Shift) is held. The optional `resizePolicy` dep\n * carries the constraints, point snaps, and `expandIds`; absent, the invoker\n * runs no constraints and `ids => ids` expansion.\n */\nexport function createResizeAction(opts: ResizeActionOptions = {}): Action & { requires: string[] } {\n const { commit } = opts;\n return inPlane({\n id: 'resize',\n label: 'Resize',\n // No default binding: a bare `{ kind: 'drag' }` claims every drag at ambient\n // scope. The resize handles bind it (`selectionResizeBindings`).\n eligible: { capability: 'transforms-selection' },\n modifierRoles: SNAP_ROLES,\n requires: [\n 'selection', 'scene', 'resizePolicy', 'poseDescriptor', 'applyOps', 'geometryProjection', 'view',\n 'boundsIncludeStroke', 'kitSettings', 'snap', 'transformFollowers',\n ],\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx, _opts): OngoingHandle {\n const begun = beginResize(ctx);\n if (!begun) return {};\n const { session, readers } = begun;\n const subject = [...(ctx.deps.selection?.get() ?? [])] as string[];\n const hold = session.scene.overrides.hold();\n const preview = transformPreview(ctx.deps, session.scene, () => frame.previews);\n let frame: ResizeFrame = { previews: new Map() };\n session.lifecycle.start(session.writeIds as readonly string[]);\n return {\n kind: 'resize',\n onMove(moveCtx: InvocationCtx): void {\n if (!moveCtx.drag) return;\n frame = nextFrame(session, readers, moveCtx);\n hold.set(preview.poses());\n },\n onEnd(_endCtx: InvocationCtx, reason: 'commit' | 'cancel'): void {\n let committed = false;\n try {\n if (reason === 'commit' && commit !== undefined) {\n committed = frame.previews.size > 0;\n if (committed) commit(new Map(frame.previews), ctx.deps);\n } else if (reason === 'commit') {\n committed = commitResize(session, frame);\n if (committed && frame.proposed) {\n const to = unitsOfBox(session.box.bounds, frame.proposed);\n recordTransform(session.scene, { kind: 'scale', to, anchor: session.box.anchor }, subject);\n }\n } else runBehaviorCancel(session.constraints, session.gestureCtx);\n } finally {\n session.snap.end();\n closeGesture({ hold, previews: frame.previews, behaviors: session.constraints, ctx: session.gestureCtx, lifecycle: session.lifecycle }, committed);\n }\n },\n previewIds: preview.previewIds,\n previewData: preview.previewData,\n };\n },\n },\n /**\n * Always enabled: the dispatcher treats anything `!== true` as \"skip and\n * try the next match\", so a disabled reason here makes the resize binding\n * silently lose to lower-specificity matches (e.g. ambient `insertAction`).\n */\n enabled: () => true,\n }, selectionLayer);\n}\n\n/** The resize that commits to the scene. */\nexport const resizeAction = createResizeAction();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,mBAAmB,OAA4B,CAAC,GAAoC;CAClG,MAAM,EAAE,WAAW;CACnB,OAAO,QAAQ;EACb,IAAI;EACJ,OAAO;EAGP,UAAU,EAAE,YAAY,uBAAuB;EAC/C,eAAe;EACf,UAAU;GACR;GAAa;GAAS;GAAgB;GAAkB;GAAY;GAAsB;GAC1F;GAAuB;GAAe;GAAQ;EAChD;EACA,SAAS;GACP,QAAQ;GACR,MAAM,KAAoB,OAAsB;IAC9C,MAAM,QAAQ,YAAY,GAAG;IAC7B,IAAI,CAAC,OAAO,OAAO,CAAC;IACpB,MAAM,EAAE,SAAS,YAAY;IAC7B,MAAM,UAAU,CAAC,GAAI,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,CAAE;IACrD,MAAM,OAAO,QAAQ,MAAM,UAAU,KAAK;IAC1C,MAAM,UAAU,iBAAiB,IAAI,MAAM,QAAQ,aAAa,MAAM,QAAQ;IAC9E,IAAI,QAAqB,EAAE,0BAAU,IAAI,IAAI,EAAE;IAC/C,QAAQ,UAAU,MAAM,QAAQ,QAA6B;IAC7D,OAAO;KACL,MAAM;KACN,OAAO,SAA8B;MACnC,IAAI,CAAC,QAAQ,MAAM;MACnB,QAAQ,UAAU,SAAS,SAAS,OAAO;MAC3C,KAAK,IAAI,QAAQ,MAAM,CAAC;KAC1B;KACA,MAAM,SAAwB,QAAmC;MAC/D,IAAI,YAAY;MAChB,IAAI;OACF,IAAI,WAAW,YAAY,WAAW,QAAW;QAC/C,YAAY,MAAM,SAAS,OAAO;QAClC,IAAI,WAAW,OAAO,IAAI,IAAI,MAAM,QAAQ,GAAG,IAAI,IAAI;OACzD,OAAO,IAAI,WAAW,UAAU;QAC9B,YAAY,aAAa,SAAS,KAAK;QACvC,IAAI,aAAa,MAAM,UAAU;SAC/B,MAAM,KAAK,WAAW,QAAQ,IAAI,QAAQ,MAAM,QAAQ;SACxD,gBAAgB,QAAQ,OAAO;UAAE,MAAM;UAAS;UAAI,QAAQ,QAAQ,IAAI;SAAO,GAAG,OAAO;QAC3F;OACF,OAAO,kBAAkB,QAAQ,aAAa,QAAQ,UAAU;MAClE,UAAU;OACR,QAAQ,KAAK,IAAI;OACjB,aAAa;QAAE;QAAM,UAAU,MAAM;QAAU,WAAW,QAAQ;QAAa,KAAK,QAAQ;QAAY,WAAW,QAAQ;OAAU,GAAG,SAAS;MACnJ;KACF;KACA,YAAY,QAAQ;KACpB,aAAa,QAAQ;IACvB;GACF;EACF;;;;;;EAMA,eAAe;CACjB,GAAG,cAAc;AACnB;;AAGA,IAAa,eAAe,mBAAmB"}
@@ -1,7 +1,7 @@
1
+ import { defaultCommitAdapter } from "../defaultCommitAdapter.js";
1
2
  import { commitGestureOps, reduceBehaviorEnd } from "../gestureLifecycle.js";
2
3
  import { createTransformOp } from "../../../core/ops/transform.js";
3
4
  import { geometryDataOp } from "../geometryProjection.js";
4
- import { defaultCommitAdapter } from "../defaultCommitAdapter.js";
5
5
  import { boxToBox } from "@weasel-js/geom";
6
6
 
7
7
  //#region src/interactions/actions/resize/commit.ts
@@ -72,6 +72,7 @@ function commitResize(session, frame) {
72
72
  const target = {
73
73
  scene: session.scene,
74
74
  applyOps: session.applyOps,
75
+ finish: session.finish,
75
76
  adapter: defaultCommitAdapter(session.scene, session.selectionMethods)
76
77
  };
77
78
  const r = reduceBehaviorEnd(session.constraints, session.gestureCtx);
@@ -1 +1 @@
1
- {"version":3,"file":"commit.js","names":[],"sources":["../../../../src/interactions/actions/resize/commit.ts"],"sourcesContent":["import { boxToBox, type Affine } from '@weasel-js/geom';\nimport type { Op } from 'core/ops/types';\nimport { createTransformOp } from 'core/ops/transform';\nimport type { NodeId } from 'core/scene/types';\nimport { defaultCommitAdapter } from '../defaultCommitAdapter';\nimport { geometryDataOp, type GeometryProjection } from '../geometryProjection';\nimport { commitGestureOps, reduceBehaviorEnd } from '../gestureLifecycle';\nimport type { PoseDescriptor } from './geometry';\nimport type { ResizeFrame, ResizeSession } from './session';\n\n/** The op mapping a leaf's data-held geometry by the box→box affine its pose\n * underwent, and the pose to commit beside it. Null when the projection\n * leaves this node's data alone. */\nfunction projectedGeometry(\n projection: GeometryProjection,\n g: PoseDescriptor<unknown>,\n node: { id: string; data: unknown },\n from: unknown,\n next: unknown,\n label: string,\n): { dataOp: Op; pose: unknown } | null {\n const sb = g.getBounds(from);\n const db = g.getBounds(next);\n const m: Affine = boxToBox(\n sb.x, sb.y, sb.width, sb.height,\n db.x, db.y, db.width, db.height,\n );\n const dataOp = geometryDataOp(projection, { id: node.id, data: node.data, pose: from }, m, label);\n if (!dataOp) return null;\n // The data absorbed the affine — including any mirror from a\n // drag past the opposite edge — so the pose must commit as a\n // normalized AABB. Leaving signed extents on the pose makes\n // the node unhittable (hit-testing assumes normalized boxes).\n // Without the seam the signed pose is kept: a bare-AABB pose\n // is the only carrier of the flip there.\n if (!(db.width < 0 || db.height < 0)) return { dataOp, pose: next };\n const nb = {\n ...db,\n x: db.width < 0 ? db.x + db.width : db.x,\n y: db.height < 0 ? db.y + db.height : db.y,\n width: Math.abs(db.width),\n height: Math.abs(db.height),\n };\n return { dataOp, pose: g.remapBounds(next, db, nb) };\n}\n\n/** One transform op per write id, `from` the pose stored at press and `to`\n * its preview, each preceded by its data op when the geometry projection\n * answers for the node. */\nexport function resizeOps(\n leaves: Pick<ResizeSession, 'scene' | 'writeIds' | 'startPoses' | 'geometryOf' | 'geometryProjection'>,\n previews: ReadonlyMap<NodeId, unknown>,\n label: string,\n): Op[] {\n const ops: Op[] = [];\n for (const id of leaves.writeIds) {\n const next = previews.get(id);\n if (next === undefined) continue;\n const from = leaves.startPoses.get(id);\n if (from === undefined) continue;\n let to: unknown = next;\n const node = leaves.geometryProjection ? leaves.scene.get(id) : undefined;\n if (leaves.geometryProjection && node) {\n const projected = projectedGeometry(\n leaves.geometryProjection, leaves.geometryOf(id), { id: id as string, data: node.data }, from, next, label,\n );\n if (projected) {\n ops.push(projected.dataOp);\n to = projected.pose;\n }\n }\n ops.push(createTransformOp<unknown>({ id: id as string, from, to, label }));\n }\n return ops;\n}\n\n/** Commit the drag as one batch, so one undo entry. Returns whether anything\n * reached the document. */\nexport function commitResize(session: ResizeSession, frame: ResizeFrame): boolean {\n const { lifecycle } = session;\n const target = {\n scene: session.scene,\n applyOps: session.applyOps,\n adapter: defaultCommitAdapter(session.scene, session.selectionMethods),\n };\n const r = reduceBehaviorEnd(session.constraints, session.gestureCtx);\n if (r === null) return false;\n if (r !== undefined) return commitGestureOps(target, lifecycle, r);\n\n if (frame.previews.size === 0) return false;\n const ops = resizeOps(session, frame.previews, lifecycle.label);\n if (ops.length === 0) return false;\n return commitGestureOps(target, lifecycle, ops);\n}\n"],"mappings":";;;;;;;;;;AAaA,SAAS,kBACP,YACA,GACA,MACA,MACA,MACA,OACsC;CACtC,MAAM,KAAK,EAAE,UAAU,IAAI;CAC3B,MAAM,KAAK,EAAE,UAAU,IAAI;CAC3B,MAAM,IAAY,SAChB,GAAG,GAAG,GAAG,GAAG,GAAG,OAAO,GAAG,QACzB,GAAG,GAAG,GAAG,GAAG,GAAG,OAAO,GAAG,MAC3B;CACA,MAAM,SAAS,eAAe,YAAY;EAAE,IAAI,KAAK;EAAI,MAAM,KAAK;EAAM,MAAM;CAAK,GAAG,GAAG,KAAK;CAChG,IAAI,CAAC,QAAQ,OAAO;CAOpB,IAAI,EAAE,GAAG,QAAQ,KAAK,GAAG,SAAS,IAAI,OAAO;EAAE;EAAQ,MAAM;CAAK;CAClE,MAAM,KAAK;EACT,GAAG;EACH,GAAG,GAAG,QAAQ,IAAI,GAAG,IAAI,GAAG,QAAQ,GAAG;EACvC,GAAG,GAAG,SAAS,IAAI,GAAG,IAAI,GAAG,SAAS,GAAG;EACzC,OAAO,KAAK,IAAI,GAAG,KAAK;EACxB,QAAQ,KAAK,IAAI,GAAG,MAAM;CAC5B;CACA,OAAO;EAAE;EAAQ,MAAM,EAAE,YAAY,MAAM,IAAI,EAAE;CAAE;AACrD;;;;AAKA,SAAgB,UACd,QACA,UACA,OACM;CACN,MAAM,MAAY,CAAC;CACnB,KAAK,MAAM,MAAM,OAAO,UAAU;EAChC,MAAM,OAAO,SAAS,IAAI,EAAE;EAC5B,IAAI,SAAS,QAAW;EACxB,MAAM,OAAO,OAAO,WAAW,IAAI,EAAE;EACrC,IAAI,SAAS,QAAW;EACxB,IAAI,KAAc;EAClB,MAAM,OAAO,OAAO,qBAAqB,OAAO,MAAM,IAAI,EAAE,IAAI;EAChE,IAAI,OAAO,sBAAsB,MAAM;GACrC,MAAM,YAAY,kBAChB,OAAO,oBAAoB,OAAO,WAAW,EAAE,GAAG;IAAM;IAAc,MAAM,KAAK;GAAK,GAAG,MAAM,MAAM,KACvG;GACA,IAAI,WAAW;IACb,IAAI,KAAK,UAAU,MAAM;IACzB,KAAK,UAAU;GACjB;EACF;EACA,IAAI,KAAK,kBAA2B;GAAM;GAAc;GAAM;GAAI;EAAM,CAAC,CAAC;CAC5E;CACA,OAAO;AACT;;;AAIA,SAAgB,aAAa,SAAwB,OAA6B;CAChF,MAAM,EAAE,cAAc;CACtB,MAAM,SAAS;EACb,OAAO,QAAQ;EACf,UAAU,QAAQ;EAClB,SAAS,qBAAqB,QAAQ,OAAO,QAAQ,gBAAgB;CACvE;CACA,MAAM,IAAI,kBAAkB,QAAQ,aAAa,QAAQ,UAAU;CACnE,IAAI,MAAM,MAAM,OAAO;CACvB,IAAI,MAAM,QAAW,OAAO,iBAAiB,QAAQ,WAAW,CAAC;CAEjE,IAAI,MAAM,SAAS,SAAS,GAAG,OAAO;CACtC,MAAM,MAAM,UAAU,SAAS,MAAM,UAAU,UAAU,KAAK;CAC9D,IAAI,IAAI,WAAW,GAAG,OAAO;CAC7B,OAAO,iBAAiB,QAAQ,WAAW,GAAG;AAChD"}
1
+ {"version":3,"file":"commit.js","names":[],"sources":["../../../../src/interactions/actions/resize/commit.ts"],"sourcesContent":["import { boxToBox, type Affine } from '@weasel-js/geom';\nimport type { Op } from 'core/ops/types';\nimport { createTransformOp } from 'core/ops/transform';\nimport type { NodeId } from 'core/scene/types';\nimport { defaultCommitAdapter } from '../defaultCommitAdapter';\nimport { geometryDataOp, type GeometryProjection } from '../geometryProjection';\nimport { commitGestureOps, reduceBehaviorEnd } from '../gestureLifecycle';\nimport type { PoseDescriptor } from './geometry';\nimport type { ResizeFrame, ResizeSession } from './session';\n\n/** The op mapping a leaf's data-held geometry by the box→box affine its pose\n * underwent, and the pose to commit beside it. Null when the projection\n * leaves this node's data alone. */\nfunction projectedGeometry(\n projection: GeometryProjection,\n g: PoseDescriptor<unknown>,\n node: { id: string; data: unknown },\n from: unknown,\n next: unknown,\n label: string,\n): { dataOp: Op; pose: unknown } | null {\n const sb = g.getBounds(from);\n const db = g.getBounds(next);\n const m: Affine = boxToBox(\n sb.x, sb.y, sb.width, sb.height,\n db.x, db.y, db.width, db.height,\n );\n const dataOp = geometryDataOp(projection, { id: node.id, data: node.data, pose: from }, m, label);\n if (!dataOp) return null;\n // The data absorbed the affine — including any mirror from a\n // drag past the opposite edge — so the pose must commit as a\n // normalized AABB. Leaving signed extents on the pose makes\n // the node unhittable (hit-testing assumes normalized boxes).\n // Without the seam the signed pose is kept: a bare-AABB pose\n // is the only carrier of the flip there.\n if (!(db.width < 0 || db.height < 0)) return { dataOp, pose: next };\n const nb = {\n ...db,\n x: db.width < 0 ? db.x + db.width : db.x,\n y: db.height < 0 ? db.y + db.height : db.y,\n width: Math.abs(db.width),\n height: Math.abs(db.height),\n };\n return { dataOp, pose: g.remapBounds(next, db, nb) };\n}\n\n/** One transform op per write id, `from` the pose stored at press and `to`\n * its preview, each preceded by its data op when the geometry projection\n * answers for the node. */\nexport function resizeOps(\n leaves: Pick<ResizeSession, 'scene' | 'writeIds' | 'startPoses' | 'geometryOf' | 'geometryProjection'>,\n previews: ReadonlyMap<NodeId, unknown>,\n label: string,\n): Op[] {\n const ops: Op[] = [];\n for (const id of leaves.writeIds) {\n const next = previews.get(id);\n if (next === undefined) continue;\n const from = leaves.startPoses.get(id);\n if (from === undefined) continue;\n let to: unknown = next;\n const node = leaves.geometryProjection ? leaves.scene.get(id) : undefined;\n if (leaves.geometryProjection && node) {\n const projected = projectedGeometry(\n leaves.geometryProjection, leaves.geometryOf(id), { id: id as string, data: node.data }, from, next, label,\n );\n if (projected) {\n ops.push(projected.dataOp);\n to = projected.pose;\n }\n }\n ops.push(createTransformOp<unknown>({ id: id as string, from, to, label }));\n }\n return ops;\n}\n\n/** Commit the drag as one batch, so one undo entry. Returns whether anything\n * reached the document. */\nexport function commitResize(session: ResizeSession, frame: ResizeFrame): boolean {\n const { lifecycle } = session;\n const target = {\n scene: session.scene,\n applyOps: session.applyOps,\n finish: session.finish,\n adapter: defaultCommitAdapter(session.scene, session.selectionMethods),\n };\n const r = reduceBehaviorEnd(session.constraints, session.gestureCtx);\n if (r === null) return false;\n if (r !== undefined) return commitGestureOps(target, lifecycle, r);\n\n if (frame.previews.size === 0) return false;\n const ops = resizeOps(session, frame.previews, lifecycle.label);\n if (ops.length === 0) return false;\n return commitGestureOps(target, lifecycle, ops);\n}\n"],"mappings":";;;;;;;;;;AAaA,SAAS,kBACP,YACA,GACA,MACA,MACA,MACA,OACsC;CACtC,MAAM,KAAK,EAAE,UAAU,IAAI;CAC3B,MAAM,KAAK,EAAE,UAAU,IAAI;CAC3B,MAAM,IAAY,SAChB,GAAG,GAAG,GAAG,GAAG,GAAG,OAAO,GAAG,QACzB,GAAG,GAAG,GAAG,GAAG,GAAG,OAAO,GAAG,MAC3B;CACA,MAAM,SAAS,eAAe,YAAY;EAAE,IAAI,KAAK;EAAI,MAAM,KAAK;EAAM,MAAM;CAAK,GAAG,GAAG,KAAK;CAChG,IAAI,CAAC,QAAQ,OAAO;CAOpB,IAAI,EAAE,GAAG,QAAQ,KAAK,GAAG,SAAS,IAAI,OAAO;EAAE;EAAQ,MAAM;CAAK;CAClE,MAAM,KAAK;EACT,GAAG;EACH,GAAG,GAAG,QAAQ,IAAI,GAAG,IAAI,GAAG,QAAQ,GAAG;EACvC,GAAG,GAAG,SAAS,IAAI,GAAG,IAAI,GAAG,SAAS,GAAG;EACzC,OAAO,KAAK,IAAI,GAAG,KAAK;EACxB,QAAQ,KAAK,IAAI,GAAG,MAAM;CAC5B;CACA,OAAO;EAAE;EAAQ,MAAM,EAAE,YAAY,MAAM,IAAI,EAAE;CAAE;AACrD;;;;AAKA,SAAgB,UACd,QACA,UACA,OACM;CACN,MAAM,MAAY,CAAC;CACnB,KAAK,MAAM,MAAM,OAAO,UAAU;EAChC,MAAM,OAAO,SAAS,IAAI,EAAE;EAC5B,IAAI,SAAS,QAAW;EACxB,MAAM,OAAO,OAAO,WAAW,IAAI,EAAE;EACrC,IAAI,SAAS,QAAW;EACxB,IAAI,KAAc;EAClB,MAAM,OAAO,OAAO,qBAAqB,OAAO,MAAM,IAAI,EAAE,IAAI;EAChE,IAAI,OAAO,sBAAsB,MAAM;GACrC,MAAM,YAAY,kBAChB,OAAO,oBAAoB,OAAO,WAAW,EAAE,GAAG;IAAM;IAAc,MAAM,KAAK;GAAK,GAAG,MAAM,MAAM,KACvG;GACA,IAAI,WAAW;IACb,IAAI,KAAK,UAAU,MAAM;IACzB,KAAK,UAAU;GACjB;EACF;EACA,IAAI,KAAK,kBAA2B;GAAM;GAAc;GAAM;GAAI;EAAM,CAAC,CAAC;CAC5E;CACA,OAAO;AACT;;;AAIA,SAAgB,aAAa,SAAwB,OAA6B;CAChF,MAAM,EAAE,cAAc;CACtB,MAAM,SAAS;EACb,OAAO,QAAQ;EACf,UAAU,QAAQ;EAClB,QAAQ,QAAQ;EAChB,SAAS,qBAAqB,QAAQ,OAAO,QAAQ,gBAAgB;CACvE;CACA,MAAM,IAAI,kBAAkB,QAAQ,aAAa,QAAQ,UAAU;CACnE,IAAI,MAAM,MAAM,OAAO;CACvB,IAAI,MAAM,QAAW,OAAO,iBAAiB,QAAQ,WAAW,CAAC;CAEjE,IAAI,MAAM,SAAS,SAAS,GAAG,OAAO;CACtC,MAAM,MAAM,UAAU,SAAS,MAAM,UAAU,UAAU,KAAK;CAC9D,IAAI,IAAI,WAAW,GAAG,OAAO;CAC7B,OAAO,iBAAiB,QAAQ,WAAW,GAAG;AAChD"}
@@ -130,7 +130,8 @@ var EDGE_FRACTION = {
130
130
  mid: .5,
131
131
  max: 1
132
132
  };
133
- /** @deprecated Use {@link handlePoint}, which also places edge handles. */
133
+ /** A corner handle's position for the given bounds, in the bounds' frame.
134
+ * @deprecated Use {@link handlePoint}, which also places edge handles. */
134
135
  var cornerPoint = handlePoint;
135
136
  /** Standard 4-corner resize-handle layout: each corner pins the opposite
136
137
  * corner via an anchor of `{x: 'min'|'max', y: 'min'|'max'}` so dragging it
@@ -1 +1 @@
1
- {"version":3,"file":"cornerHandles.js","names":[],"sources":["../../../../src/interactions/actions/resize/cornerHandles.ts"],"sourcesContent":["import type { ResizeAnchor } from '../../gestures/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { pxExtentIn, type TargetTransform } from 'core/geometry/targetFrame';\n\n/** Corner resize-handle: world-space center plus the anchor that pins the opposite corner during resize. */\nexport interface CornerHandle {\n cx: number;\n cy: number;\n anchor: ResizeAnchor;\n}\n\n/** Which AABB edge a corner sits on, per axis. `'min'` = the x/y origin\n * edge; `'max'` = the origin + extent edge. */\nexport type CornerEdge = 'min' | 'max';\n\n/** Where a resize handle sits on one axis: on an edge (`CornerEdge`), or\n * `'mid'`way between them, which is what makes a handle an edge handle. */\nexport type HandleEdge = CornerEdge | 'mid';\n\n/** One entry of the canonical resize-handle layout: where the handle sits,\n * which edges it pins, and what a press on it reports. */\nexport interface ResizeHandleAnchor {\n /** Where the handle sits on x (`'min'` = left, `'mid'` = centered, `'max'` = right). */\n xEdge: HandleEdge;\n /** Where the handle sits on y (`'min'` = top, `'mid'` = centered, `'max'` = bottom). */\n yEdge: HandleEdge;\n /** Anchor that pins the opposite side: the axis-wise opposite of each\n * edge the handle sits on, and `'free'` on an axis it sits mid-way along,\n * which the drag leaves alone. */\n anchor: ResizeAnchor;\n /** Affordance/handle discriminator string for this handle. */\n kind: string;\n /** Short positional tag (`'<xEdge>-<yEdge>'`), e.g. `'min-max'`. */\n tag: string;\n}\n\n/** A corner entry of {@link RESIZE_HANDLE_ANCHORS}. */\nexport interface CornerAnchor extends ResizeHandleAnchor {\n /** Which x-edge the corner sits on (`'min'` = left, `'max'` = right). */\n xEdge: CornerEdge;\n /** Which y-edge the corner sits on (`'min'` = top, `'max'` = bottom). */\n yEdge: CornerEdge;\n}\n\n/**\n * The single ordered resize-handle table: the four corners (TL, TR, BL, BR),\n * then the four edge midpoints (top, right, bottom, left). Order is\n * **load-bearing**: the resize-handle affordance, the selection overlay and\n * `cornerResizeHandles` all iterate it in this sequence, so the i-th handle\n * means the same thing to each. A corner pins the diagonally opposite corner;\n * an edge pins the opposite edge and leaves the other axis free.\n */\nexport const RESIZE_HANDLE_ANCHORS: readonly ResizeHandleAnchor[] = [\n { xEdge: 'min', yEdge: 'min', anchor: { x: 'max', y: 'max' }, kind: 'handle:top-left', tag: 'min-min' },\n { xEdge: 'max', yEdge: 'min', anchor: { x: 'min', y: 'max' }, kind: 'handle:top-right', tag: 'max-min' },\n { xEdge: 'min', yEdge: 'max', anchor: { x: 'max', y: 'min' }, kind: 'handle:bottom-left', tag: 'min-max' },\n { xEdge: 'max', yEdge: 'max', anchor: { x: 'min', y: 'min' }, kind: 'handle:bottom-right', tag: 'max-max' },\n { xEdge: 'mid', yEdge: 'min', anchor: { x: 'free', y: 'max' }, kind: 'handle:top', tag: 'mid-min' },\n { xEdge: 'max', yEdge: 'mid', anchor: { x: 'min', y: 'free' }, kind: 'handle:right', tag: 'max-mid' },\n { xEdge: 'mid', yEdge: 'max', anchor: { x: 'free', y: 'min' }, kind: 'handle:bottom', tag: 'mid-max' },\n { xEdge: 'min', yEdge: 'mid', anchor: { x: 'max', y: 'free' }, kind: 'handle:left', tag: 'min-mid' },\n];\n\n/** True for a corner entry of {@link RESIZE_HANDLE_ANCHORS}. */\nexport function isCornerHandle(entry: ResizeHandleAnchor): entry is CornerAnchor {\n return entry.xEdge !== 'mid' && entry.yEdge !== 'mid';\n}\n\n/** The corner entries of {@link RESIZE_HANDLE_ANCHORS}, in its order. */\nexport const CORNER_ANCHORS: readonly CornerAnchor[] = RESIZE_HANDLE_ANCHORS.filter(isCornerHandle);\n\n/** An edge handle sits this many hit radii on screen from each corner of the\n * side it is on, at least: under that, it would crowd the corners and leave\n * the body too little room to be grabbed. At the default 8 px radius, a\n * side must span 48 px. */\nexport const EDGE_HANDLE_MIN_RADII = 6;\n\n/** Whether a body this size, in its own frame `xf`, has room for edge\n * handles on screen: each side spans {@link EDGE_HANDLE_MIN_RADII} times\n * `radiusPx`. Both sides, because a short side puts the two handles across\n * it over the middle of the body. */\nexport function edgeHandlesFit(\n body: { width: number; height: number },\n xf: TargetTransform,\n scale: { x: number; y: number },\n radiusPx: number,\n): boolean {\n const floor = pxExtentIn(EDGE_HANDLE_MIN_RADII * radiusPx, scale, xf);\n return Math.abs(body.width) >= floor.x && Math.abs(body.height) >= floor.y;\n}\n\n/** How a canvas lays out its resize handles. `hitRadiusPx` is the one size\n * the edge-handle decision keys on, because a handle exists to be grabbed:\n * paint and hit-test both read it, so no handle is drawn that cannot be\n * grabbed or grabbable where none is drawn. */\nexport interface ResizeHandleLayout {\n /** Whether the edge midpoints get handles at all. */\n edges: boolean;\n /** The handles' hit radius, screen px. */\n hitRadiusPx: number;\n}\n\n/** The handles a body gets in `layout`: every corner, then the edge\n * midpoints when the layout has them and {@link edgeHandlesFit} at its hit\n * radius. The answer the selection overlay paints; the hit-test reaches the\n * same one through the regions' `edgeOf`. */\nexport function resizeHandlesFor(\n layout: ResizeHandleLayout,\n body: { width: number; height: number },\n xf: TargetTransform,\n scale: { x: number; y: number },\n): readonly ResizeHandleAnchor[] {\n return layout.edges && edgeHandlesFit(body, xf, scale, layout.hitRadiusPx)\n ? RESIZE_HANDLE_ANCHORS\n : CORNER_ANCHORS;\n}\n\n/** Resolve a handle's position for the given bounds, in the bounds' frame:\n * `'min'` → the origin edge, `'max'` → origin + extent, `'mid'` → between. */\nexport function handlePoint(\n bounds: Bounds,\n entry: Pick<ResizeHandleAnchor, 'xEdge' | 'yEdge'>,\n): { x: number; y: number } {\n return {\n x: bounds.x + bounds.width * EDGE_FRACTION[entry.xEdge],\n y: bounds.y + bounds.height * EDGE_FRACTION[entry.yEdge],\n };\n}\n\nconst EDGE_FRACTION: Record<HandleEdge, number> = { min: 0, mid: 0.5, max: 1 };\n\n/** @deprecated Use {@link handlePoint}, which also places edge handles. */\nexport const cornerPoint: (\n bounds: Bounds,\n entry: Pick<CornerAnchor, 'xEdge' | 'yEdge'>,\n) => { x: number; y: number } = handlePoint;\n\n/** Standard 4-corner resize-handle layout: each corner pins the opposite\n * corner via an anchor of `{x: 'min'|'max', y: 'min'|'max'}` so dragging it\n * scales the box from the fixed opposite corner. Decodes {@link CORNER_ANCHORS}\n * against `bounds`; edge handles are the affordance's, which knows the view. */\nexport function cornerResizeHandles(bounds: Bounds): CornerHandle[] {\n return CORNER_ANCHORS.map((c) => {\n const p = handlePoint(bounds, c);\n return { cx: p.x, cy: p.y, anchor: c.anchor };\n });\n}\n\n/** Square hit-test for a handle: returns true if `(px, py)` is within\n * `radius` of the handle center on both axes. */\nexport function hitCornerHandle(\n handle: CornerHandle,\n px: number,\n py: number,\n radius: number,\n): boolean {\n return Math.abs(px - handle.cx) <= radius\n && Math.abs(py - handle.cy) <= radius;\n}\n\n/** The corner that does NOT move under a resize gesture with the given\n * anchor. Used by the rotated-resize math to pin a world-space invariant.\n *\n * Convention: `anchor.x === 'min'` means the min-x (left) edge is the\n * fixed anchor, so the fixed corner sits at `x = bounds.x` (min-x).\n * `anchor.x === 'max'` means the max-x (right) edge is fixed, so the\n * fixed corner sits at `x = bounds.x + bounds.width`. `'free'` on an\n * axis (an edge handle's other axis) pins the bounds' center on it: a drag\n * leaves that extent alone, and an aspect lock grows it about the center. */\nexport function fixedCornerOf(\n bounds: Bounds,\n anchor: ResizeAnchor,\n): { x: number; y: number } {\n return handlePoint(bounds, { xEdge: PINNED[anchor.x], yEdge: PINNED[anchor.y] });\n}\n\nconst PINNED: Record<ResizeAnchor['x'], HandleEdge> = { min: 'min', max: 'max', free: 'mid' };\n\n/** The corner or edge midpoint of `bounds` a resize pinning `anchor` moves. */\nexport function movingPoint(bounds: Bounds, anchor: ResizeAnchor): { x: number; y: number } {\n return handlePoint(bounds, { xEdge: MOVING[anchor.x], yEdge: MOVING[anchor.y] });\n}\n\nconst MOVING: Record<ResizeAnchor['x'], HandleEdge> = { min: 'max', max: 'min', free: 'mid' };\n\n/** `bounds` with the edges `anchor` leaves free moved by `(dx, dy)`, in its own axes. */\nexport function movedEdges(bounds: Bounds, anchor: ResizeAnchor, dx: number, dy: number): Bounds {\n let { x, y, width, height } = bounds;\n if (anchor.x === 'min') width += dx;\n else if (anchor.x === 'max') { x += dx; width -= dx; }\n if (anchor.y === 'min') height += dy;\n else if (anchor.y === 'max') { y += dy; height -= dy; }\n return { x, y, width, height };\n}\n"],"mappings":";;;;;;;;;;;AAoDA,IAAa,wBAAuD;CAClE;EAAE,OAAO;EAAO,OAAO;EAAO,QAAQ;GAAE,GAAG;GAAO,GAAG;EAAM;EAAG,MAAM;EAAuB,KAAK;CAAU;CAC1G;EAAE,OAAO;EAAO,OAAO;EAAO,QAAQ;GAAE,GAAG;GAAO,GAAG;EAAM;EAAG,MAAM;EAAuB,KAAK;CAAU;CAC1G;EAAE,OAAO;EAAO,OAAO;EAAO,QAAQ;GAAE,GAAG;GAAO,GAAG;EAAM;EAAG,MAAM;EAAuB,KAAK;CAAU;CAC1G;EAAE,OAAO;EAAO,OAAO;EAAO,QAAQ;GAAE,GAAG;GAAO,GAAG;EAAM;EAAG,MAAM;EAAuB,KAAK;CAAU;CAC1G;EAAE,OAAO;EAAO,OAAO;EAAO,QAAQ;GAAE,GAAG;GAAQ,GAAG;EAAM;EAAG,MAAM;EAAsB,KAAK;CAAU;CAC1G;EAAE,OAAO;EAAO,OAAO;EAAO,QAAQ;GAAE,GAAG;GAAO,GAAG;EAAO;EAAG,MAAM;EAAsB,KAAK;CAAU;CAC1G;EAAE,OAAO;EAAO,OAAO;EAAO,QAAQ;GAAE,GAAG;GAAQ,GAAG;EAAM;EAAG,MAAM;EAAsB,KAAK;CAAU;CAC1G;EAAE,OAAO;EAAO,OAAO;EAAO,QAAQ;GAAE,GAAG;GAAO,GAAG;EAAO;EAAG,MAAM;EAAsB,KAAK;CAAU;AAC5G;;AAGA,SAAgB,eAAe,OAAkD;CAC/E,OAAO,MAAM,UAAU,SAAS,MAAM,UAAU;AAClD;;AAGA,IAAa,iBAA0C,sBAAsB,OAAO,cAAc;;;;;AAMlG,IAAa,wBAAwB;;;;;AAMrC,SAAgB,eACd,MACA,IACA,OACA,UACS;CACT,MAAM,QAAQ,WAAW,wBAAwB,UAAU,OAAO,EAAE;CACpE,OAAO,KAAK,IAAI,KAAK,KAAK,KAAK,MAAM,KAAK,KAAK,IAAI,KAAK,MAAM,KAAK,MAAM;AAC3E;;;;;AAiBA,SAAgB,iBACd,QACA,MACA,IACA,OAC+B;CAC/B,OAAO,OAAO,SAAS,eAAe,MAAM,IAAI,OAAO,OAAO,WAAW,IACrE,wBACA;AACN;;;AAIA,SAAgB,YACd,QACA,OAC0B;CAC1B,OAAO;EACL,GAAG,OAAO,IAAI,OAAO,QAAQ,cAAc,MAAM;EACjD,GAAG,OAAO,IAAI,OAAO,SAAS,cAAc,MAAM;CACpD;AACF;AAEA,IAAM,gBAA4C;CAAE,KAAK;CAAG,KAAK;CAAK,KAAK;AAAE;;AAG7E,IAAa,cAGmB;;;;;AAMhC,SAAgB,oBAAoB,QAAgC;CAClE,OAAO,eAAe,KAAK,MAAM;EAC/B,MAAM,IAAI,YAAY,QAAQ,CAAC;EAC/B,OAAO;GAAE,IAAI,EAAE;GAAG,IAAI,EAAE;GAAG,QAAQ,EAAE;EAAO;CAC9C,CAAC;AACH;;;AAIA,SAAgB,gBACd,QACA,IACA,IACA,QACS;CACT,OAAO,KAAK,IAAI,KAAK,OAAO,EAAE,KAAK,UAC9B,KAAK,IAAI,KAAK,OAAO,EAAE,KAAK;AACnC;;;;;;;;;;AAWA,SAAgB,cACd,QACA,QAC0B;CAC1B,OAAO,YAAY,QAAQ;EAAE,OAAO,OAAO,OAAO;EAAI,OAAO,OAAO,OAAO;CAAG,CAAC;AACjF;AAEA,IAAM,SAAgD;CAAE,KAAK;CAAO,KAAK;CAAO,MAAM;AAAM;;AAG5F,SAAgB,YAAY,QAAgB,QAAgD;CAC1F,OAAO,YAAY,QAAQ;EAAE,OAAO,OAAO,OAAO;EAAI,OAAO,OAAO,OAAO;CAAG,CAAC;AACjF;AAEA,IAAM,SAAgD;CAAE,KAAK;CAAO,KAAK;CAAO,MAAM;AAAM;;AAG5F,SAAgB,WAAW,QAAgB,QAAsB,IAAY,IAAoB;CAC/F,IAAI,EAAE,GAAG,GAAG,OAAO,WAAW;CAC9B,IAAI,OAAO,MAAM,OAAO,SAAS;MAC5B,IAAI,OAAO,MAAM,OAAO;EAAE,KAAK;EAAI,SAAS;CAAI;CACrD,IAAI,OAAO,MAAM,OAAO,UAAU;MAC7B,IAAI,OAAO,MAAM,OAAO;EAAE,KAAK;EAAI,UAAU;CAAI;CACtD,OAAO;EAAE;EAAG;EAAG;EAAO;CAAO;AAC/B"}
1
+ {"version":3,"file":"cornerHandles.js","names":[],"sources":["../../../../src/interactions/actions/resize/cornerHandles.ts"],"sourcesContent":["import type { ResizeAnchor } from '../../gestures/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { pxExtentIn, type TargetTransform } from 'core/geometry/targetFrame';\n\n/** Corner resize-handle: world-space center plus the anchor that pins the opposite corner during resize. */\nexport interface CornerHandle {\n cx: number;\n cy: number;\n anchor: ResizeAnchor;\n}\n\n/** Which AABB edge a corner sits on, per axis. `'min'` = the x/y origin\n * edge; `'max'` = the origin + extent edge. */\nexport type CornerEdge = 'min' | 'max';\n\n/** Where a resize handle sits on one axis: on an edge (`CornerEdge`), or\n * `'mid'`way between them, which is what makes a handle an edge handle. */\nexport type HandleEdge = CornerEdge | 'mid';\n\n/** One entry of the canonical resize-handle layout: where the handle sits,\n * which edges it pins, and what a press on it reports. */\nexport interface ResizeHandleAnchor {\n /** Where the handle sits on x (`'min'` = left, `'mid'` = centered, `'max'` = right). */\n xEdge: HandleEdge;\n /** Where the handle sits on y (`'min'` = top, `'mid'` = centered, `'max'` = bottom). */\n yEdge: HandleEdge;\n /** Anchor that pins the opposite side: the axis-wise opposite of each\n * edge the handle sits on, and `'free'` on an axis it sits mid-way along,\n * which the drag leaves alone. */\n anchor: ResizeAnchor;\n /** Affordance/handle discriminator string for this handle. */\n kind: string;\n /** Short positional tag (`'<xEdge>-<yEdge>'`), e.g. `'min-max'`. */\n tag: string;\n}\n\n/** A corner entry of {@link RESIZE_HANDLE_ANCHORS}. */\nexport interface CornerAnchor extends ResizeHandleAnchor {\n /** Which x-edge the corner sits on (`'min'` = left, `'max'` = right). */\n xEdge: CornerEdge;\n /** Which y-edge the corner sits on (`'min'` = top, `'max'` = bottom). */\n yEdge: CornerEdge;\n}\n\n/**\n * The single ordered resize-handle table: the four corners (TL, TR, BL, BR),\n * then the four edge midpoints (top, right, bottom, left). Order is\n * **load-bearing**: the resize-handle affordance, the selection overlay and\n * `cornerResizeHandles` all iterate it in this sequence, so the i-th handle\n * means the same thing to each. A corner pins the diagonally opposite corner;\n * an edge pins the opposite edge and leaves the other axis free.\n */\nexport const RESIZE_HANDLE_ANCHORS: readonly ResizeHandleAnchor[] = [\n { xEdge: 'min', yEdge: 'min', anchor: { x: 'max', y: 'max' }, kind: 'handle:top-left', tag: 'min-min' },\n { xEdge: 'max', yEdge: 'min', anchor: { x: 'min', y: 'max' }, kind: 'handle:top-right', tag: 'max-min' },\n { xEdge: 'min', yEdge: 'max', anchor: { x: 'max', y: 'min' }, kind: 'handle:bottom-left', tag: 'min-max' },\n { xEdge: 'max', yEdge: 'max', anchor: { x: 'min', y: 'min' }, kind: 'handle:bottom-right', tag: 'max-max' },\n { xEdge: 'mid', yEdge: 'min', anchor: { x: 'free', y: 'max' }, kind: 'handle:top', tag: 'mid-min' },\n { xEdge: 'max', yEdge: 'mid', anchor: { x: 'min', y: 'free' }, kind: 'handle:right', tag: 'max-mid' },\n { xEdge: 'mid', yEdge: 'max', anchor: { x: 'free', y: 'min' }, kind: 'handle:bottom', tag: 'mid-max' },\n { xEdge: 'min', yEdge: 'mid', anchor: { x: 'max', y: 'free' }, kind: 'handle:left', tag: 'min-mid' },\n];\n\n/** True for a corner entry of {@link RESIZE_HANDLE_ANCHORS}. */\nexport function isCornerHandle(entry: ResizeHandleAnchor): entry is CornerAnchor {\n return entry.xEdge !== 'mid' && entry.yEdge !== 'mid';\n}\n\n/** The corner entries of {@link RESIZE_HANDLE_ANCHORS}, in its order. */\nexport const CORNER_ANCHORS: readonly CornerAnchor[] = RESIZE_HANDLE_ANCHORS.filter(isCornerHandle);\n\n/** An edge handle sits this many hit radii on screen from each corner of the\n * side it is on, at least: under that, it would crowd the corners and leave\n * the body too little room to be grabbed. At the default 8 px radius, a\n * side must span 48 px. */\nexport const EDGE_HANDLE_MIN_RADII = 6;\n\n/** Whether a body this size, in its own frame `xf`, has room for edge\n * handles on screen: each side spans {@link EDGE_HANDLE_MIN_RADII} times\n * `radiusPx`. Both sides, because a short side puts the two handles across\n * it over the middle of the body. */\nexport function edgeHandlesFit(\n body: { width: number; height: number },\n xf: TargetTransform,\n scale: { x: number; y: number },\n radiusPx: number,\n): boolean {\n const floor = pxExtentIn(EDGE_HANDLE_MIN_RADII * radiusPx, scale, xf);\n return Math.abs(body.width) >= floor.x && Math.abs(body.height) >= floor.y;\n}\n\n/** How a canvas lays out its resize handles. `hitRadiusPx` is the one size\n * the edge-handle decision keys on, because a handle exists to be grabbed:\n * paint and hit-test both read it, so no handle is drawn that cannot be\n * grabbed or grabbable where none is drawn. */\nexport interface ResizeHandleLayout {\n /** Whether the edge midpoints get handles at all. */\n edges: boolean;\n /** The handles' hit radius, screen px. */\n hitRadiusPx: number;\n}\n\n/** The handles a body gets in `layout`: every corner, then the edge\n * midpoints when the layout has them and {@link edgeHandlesFit} at its hit\n * radius. The answer the selection overlay paints; the hit-test reaches the\n * same one through the regions' `edgeOf`. */\nexport function resizeHandlesFor(\n layout: ResizeHandleLayout,\n body: { width: number; height: number },\n xf: TargetTransform,\n scale: { x: number; y: number },\n): readonly ResizeHandleAnchor[] {\n return layout.edges && edgeHandlesFit(body, xf, scale, layout.hitRadiusPx)\n ? RESIZE_HANDLE_ANCHORS\n : CORNER_ANCHORS;\n}\n\n/** Resolve a handle's position for the given bounds, in the bounds' frame:\n * `'min'` → the origin edge, `'max'` → origin + extent, `'mid'` → between. */\nexport function handlePoint(\n bounds: Bounds,\n entry: Pick<ResizeHandleAnchor, 'xEdge' | 'yEdge'>,\n): { x: number; y: number } {\n return {\n x: bounds.x + bounds.width * EDGE_FRACTION[entry.xEdge],\n y: bounds.y + bounds.height * EDGE_FRACTION[entry.yEdge],\n };\n}\n\nconst EDGE_FRACTION: Record<HandleEdge, number> = { min: 0, mid: 0.5, max: 1 };\n\n/** A corner handle's position for the given bounds, in the bounds' frame.\n * @deprecated Use {@link handlePoint}, which also places edge handles. */\nexport const cornerPoint: (\n bounds: Bounds,\n entry: Pick<CornerAnchor, 'xEdge' | 'yEdge'>,\n) => { x: number; y: number } = handlePoint;\n\n/** Standard 4-corner resize-handle layout: each corner pins the opposite\n * corner via an anchor of `{x: 'min'|'max', y: 'min'|'max'}` so dragging it\n * scales the box from the fixed opposite corner. Decodes {@link CORNER_ANCHORS}\n * against `bounds`; edge handles are the affordance's, which knows the view. */\nexport function cornerResizeHandles(bounds: Bounds): CornerHandle[] {\n return CORNER_ANCHORS.map((c) => {\n const p = handlePoint(bounds, c);\n return { cx: p.x, cy: p.y, anchor: c.anchor };\n });\n}\n\n/** Square hit-test for a handle: returns true if `(px, py)` is within\n * `radius` of the handle center on both axes. */\nexport function hitCornerHandle(\n handle: CornerHandle,\n px: number,\n py: number,\n radius: number,\n): boolean {\n return Math.abs(px - handle.cx) <= radius\n && Math.abs(py - handle.cy) <= radius;\n}\n\n/** The corner that does NOT move under a resize gesture with the given\n * anchor. Used by the rotated-resize math to pin a world-space invariant.\n *\n * Convention: `anchor.x === 'min'` means the min-x (left) edge is the\n * fixed anchor, so the fixed corner sits at `x = bounds.x` (min-x).\n * `anchor.x === 'max'` means the max-x (right) edge is fixed, so the\n * fixed corner sits at `x = bounds.x + bounds.width`. `'free'` on an\n * axis (an edge handle's other axis) pins the bounds' center on it: a drag\n * leaves that extent alone, and an aspect lock grows it about the center. */\nexport function fixedCornerOf(\n bounds: Bounds,\n anchor: ResizeAnchor,\n): { x: number; y: number } {\n return handlePoint(bounds, { xEdge: PINNED[anchor.x], yEdge: PINNED[anchor.y] });\n}\n\nconst PINNED: Record<ResizeAnchor['x'], HandleEdge> = { min: 'min', max: 'max', free: 'mid' };\n\n/** The corner or edge midpoint of `bounds` a resize pinning `anchor` moves. */\nexport function movingPoint(bounds: Bounds, anchor: ResizeAnchor): { x: number; y: number } {\n return handlePoint(bounds, { xEdge: MOVING[anchor.x], yEdge: MOVING[anchor.y] });\n}\n\nconst MOVING: Record<ResizeAnchor['x'], HandleEdge> = { min: 'max', max: 'min', free: 'mid' };\n\n/** `bounds` with the edges `anchor` leaves free moved by `(dx, dy)`, in its own axes. */\nexport function movedEdges(bounds: Bounds, anchor: ResizeAnchor, dx: number, dy: number): Bounds {\n let { x, y, width, height } = bounds;\n if (anchor.x === 'min') width += dx;\n else if (anchor.x === 'max') { x += dx; width -= dx; }\n if (anchor.y === 'min') height += dy;\n else if (anchor.y === 'max') { y += dy; height -= dy; }\n return { x, y, width, height };\n}\n"],"mappings":";;;;;;;;;;;AAoDA,IAAa,wBAAuD;CAClE;EAAE,OAAO;EAAO,OAAO;EAAO,QAAQ;GAAE,GAAG;GAAO,GAAG;EAAM;EAAG,MAAM;EAAuB,KAAK;CAAU;CAC1G;EAAE,OAAO;EAAO,OAAO;EAAO,QAAQ;GAAE,GAAG;GAAO,GAAG;EAAM;EAAG,MAAM;EAAuB,KAAK;CAAU;CAC1G;EAAE,OAAO;EAAO,OAAO;EAAO,QAAQ;GAAE,GAAG;GAAO,GAAG;EAAM;EAAG,MAAM;EAAuB,KAAK;CAAU;CAC1G;EAAE,OAAO;EAAO,OAAO;EAAO,QAAQ;GAAE,GAAG;GAAO,GAAG;EAAM;EAAG,MAAM;EAAuB,KAAK;CAAU;CAC1G;EAAE,OAAO;EAAO,OAAO;EAAO,QAAQ;GAAE,GAAG;GAAQ,GAAG;EAAM;EAAG,MAAM;EAAsB,KAAK;CAAU;CAC1G;EAAE,OAAO;EAAO,OAAO;EAAO,QAAQ;GAAE,GAAG;GAAO,GAAG;EAAO;EAAG,MAAM;EAAsB,KAAK;CAAU;CAC1G;EAAE,OAAO;EAAO,OAAO;EAAO,QAAQ;GAAE,GAAG;GAAQ,GAAG;EAAM;EAAG,MAAM;EAAsB,KAAK;CAAU;CAC1G;EAAE,OAAO;EAAO,OAAO;EAAO,QAAQ;GAAE,GAAG;GAAO,GAAG;EAAO;EAAG,MAAM;EAAsB,KAAK;CAAU;AAC5G;;AAGA,SAAgB,eAAe,OAAkD;CAC/E,OAAO,MAAM,UAAU,SAAS,MAAM,UAAU;AAClD;;AAGA,IAAa,iBAA0C,sBAAsB,OAAO,cAAc;;;;;AAMlG,IAAa,wBAAwB;;;;;AAMrC,SAAgB,eACd,MACA,IACA,OACA,UACS;CACT,MAAM,QAAQ,WAAW,wBAAwB,UAAU,OAAO,EAAE;CACpE,OAAO,KAAK,IAAI,KAAK,KAAK,KAAK,MAAM,KAAK,KAAK,IAAI,KAAK,MAAM,KAAK,MAAM;AAC3E;;;;;AAiBA,SAAgB,iBACd,QACA,MACA,IACA,OAC+B;CAC/B,OAAO,OAAO,SAAS,eAAe,MAAM,IAAI,OAAO,OAAO,WAAW,IACrE,wBACA;AACN;;;AAIA,SAAgB,YACd,QACA,OAC0B;CAC1B,OAAO;EACL,GAAG,OAAO,IAAI,OAAO,QAAQ,cAAc,MAAM;EACjD,GAAG,OAAO,IAAI,OAAO,SAAS,cAAc,MAAM;CACpD;AACF;AAEA,IAAM,gBAA4C;CAAE,KAAK;CAAG,KAAK;CAAK,KAAK;AAAE;;;AAI7E,IAAa,cAGmB;;;;;AAMhC,SAAgB,oBAAoB,QAAgC;CAClE,OAAO,eAAe,KAAK,MAAM;EAC/B,MAAM,IAAI,YAAY,QAAQ,CAAC;EAC/B,OAAO;GAAE,IAAI,EAAE;GAAG,IAAI,EAAE;GAAG,QAAQ,EAAE;EAAO;CAC9C,CAAC;AACH;;;AAIA,SAAgB,gBACd,QACA,IACA,IACA,QACS;CACT,OAAO,KAAK,IAAI,KAAK,OAAO,EAAE,KAAK,UAC9B,KAAK,IAAI,KAAK,OAAO,EAAE,KAAK;AACnC;;;;;;;;;;AAWA,SAAgB,cACd,QACA,QAC0B;CAC1B,OAAO,YAAY,QAAQ;EAAE,OAAO,OAAO,OAAO;EAAI,OAAO,OAAO,OAAO;CAAG,CAAC;AACjF;AAEA,IAAM,SAAgD;CAAE,KAAK;CAAO,KAAK;CAAO,MAAM;AAAM;;AAG5F,SAAgB,YAAY,QAAgB,QAAgD;CAC1F,OAAO,YAAY,QAAQ;EAAE,OAAO,OAAO,OAAO;EAAI,OAAO,OAAO,OAAO;CAAG,CAAC;AACjF;AAEA,IAAM,SAAgD;CAAE,KAAK;CAAO,KAAK;CAAO,MAAM;AAAM;;AAG5F,SAAgB,WAAW,QAAgB,QAAsB,IAAY,IAAoB;CAC/F,IAAI,EAAE,GAAG,GAAG,OAAO,WAAW;CAC9B,IAAI,OAAO,MAAM,OAAO,SAAS;MAC5B,IAAI,OAAO,MAAM,OAAO;EAAE,KAAK;EAAI,SAAS;CAAI;CACrD,IAAI,OAAO,MAAM,OAAO,UAAU;MAC7B,IAAI,OAAO,MAAM,OAAO;EAAE,KAAK;EAAI,UAAU;CAAI;CACtD,OAAO;EAAE;EAAG;EAAG;EAAO;CAAO;AAC/B"}
@@ -2,6 +2,7 @@ import { localToWorld } from "../../../core/geometry/targetFrame.js";
2
2
  import { outsetBox } from "../../../core/geometry/inkExtents.js";
3
3
  import { scenePoseFrame } from "../poseFrame.js";
4
4
  import { nodeDescriptors, poseDescriptorOf } from "../poseDescriptorDep.js";
5
+ import { transformFinisher } from "../transformFinish.js";
5
6
  import { readGestureLifecycle } from "../gestureLifecycle.js";
6
7
  import { gestureReaders } from "../../gestures/shared/screenTolerance.js";
7
8
  import { drawnBoxes } from "../drawnBoxes.js";
@@ -94,6 +95,7 @@ function beginResize(ctx) {
94
95
  constraints,
95
96
  gestureCtx,
96
97
  applyOps: ctx.deps.applyOps,
98
+ finish: transformFinisher(ctx.deps),
97
99
  geometryProjection: ctx.deps.geometryProjection,
98
100
  selectionMethods: selection.adapterMethods,
99
101
  lifecycle: readGestureLifecycle(policy, "Resize", constraints),
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","names":[],"sources":["../../../../src/interactions/actions/resize/session.ts"],"sourcesContent":["import type { PoseLinear } from '@weasel-js/geom';\nimport type { ActionDeps, InvocationCtx } from '@weasel-js/routing';\nimport type { Op } from 'core/ops/types';\nimport type { NodeId, Scene } from 'core/scene/types';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { gestureReaders, type GestureReaders } from '../../gestures/shared/screenTolerance';\nimport type { BoundsConstraint, GestureContext, ResizeAnchor } from '../../gestures/types';\nimport type { GeometryProjection } from '../geometryProjection';\nimport { readGestureLifecycle, type GestureLifecycle } from '../gestureLifecycle';\nimport { nodeDescriptors, poseDescriptorOf } from '../poseDescriptorDep';\nimport { scenePoseFrame, type PoseFrame } from '../poseFrame';\nimport { buildGestureContext, captureBox, captureStarts, captureTargets, resolvePolicy } from './capture';\nimport type { PoseDescriptor } from './geometry';\nimport type { InkExtents } from 'core/geometry/inkExtents';\nimport { drawnBoxes } from '../drawnBoxes';\nimport type { MinSize } from 'core/scene/minSize';\nimport { boxMinimum } from './minimum';\nimport { boxFrame } from './boxFrame';\nimport { localToWorld } from 'core/geometry/targetFrame';\nimport { outsetBox } from 'core/geometry/inkExtents';\nimport type { SnapBegin, SnapSession } from '../snapDep';\nimport { inputSession } from 'features/snap/session2d';\n\n/** The box the drag resizes, as the press found it. */\nexport interface ResizeBox {\n /** The union for a group or a multi-selection, a lone target's own bounds\n * otherwise. The `src` rect every write id is remapped from. */\n readonly bounds: Bounds;\n readonly anchor: ResizeAnchor;\n /** A lone target's rotation; 0 for a group. */\n readonly rotation: number;\n /** A lone target's `transform`; none for a group. */\n readonly transform?: PoseLinear;\n /** World position of the corner opposite the dragged one, which a resize\n * in a turned or skewed frame pins. */\n readonly fixedWorld: { x: number; y: number };\n /** How far the ink reaches past `bounds` in its own frame. The handles sit\n * on `bounds` grown by it, and the constraints see that box; the stroke\n * does not scale, so the same extents come off whatever box they return. */\n readonly extents: InkExtents;\n}\n\n/** What a resize captured at press. Nothing here changes during the drag. */\nexport interface ResizeSession {\n readonly scene: Scene<unknown, string, unknown>;\n /** The ids the gesture writes: the leaf set for a group, else the selection. */\n readonly writeIds: readonly NodeId[];\n /** Whether `box` stands for several write ids, each remapped inside it,\n * rather than being a lone target's own. */\n readonly sharedBox: boolean;\n readonly box: ResizeBox;\n /** Start poses as stored: the `from` of each op. */\n readonly startPoses: ReadonlyMap<NodeId, unknown>;\n /** Start poses in world, the frame the anchor math and the drag share. */\n readonly startWorlds: ReadonlyMap<NodeId, unknown>;\n /** Where the pointer pressed. */\n readonly startWorld: { x: number; y: number };\n readonly geometryOf: (id: NodeId) => PoseDescriptor<unknown>;\n /** World reads and local writes over the scene's composition strategy. */\n readonly poseFrame: PoseFrame<unknown>;\n readonly view: unknown;\n /** The smallest `box` may be resized to, from the written nodes' own\n * minimums: a floor under every constraint. `null` for none. */\n readonly minimum: MinSize | null;\n readonly constraints: readonly BoundsConstraint<Bounds>[];\n /** Reused across the drag; constraints mutate its `scratch`. */\n readonly gestureCtx: GestureContext<Bounds>;\n readonly applyOps?: (ops: Op[], label: string) => void;\n /** When present, the commit maps each leaf's data-held geometry by the\n * affine its pose underwent. */\n readonly geometryProjection?: GeometryProjection;\n readonly selectionMethods: SelectionApi['adapterMethods'] | undefined;\n /** Label, history, and callbacks from the `resizePolicy` dep. */\n readonly lifecycle: GestureLifecycle;\n /** The drag's snapping: the `snap` dep's session, else one that only holds\n * the handle to its axis and Shift to the aspect. */\n readonly snap: SnapSession;\n}\n\n/** What one `onMove` produced. Replaced, not mutated, by the next. */\nexport interface ResizeFrame {\n /** Stored-frame pose of every write id the move reached. */\n readonly previews: Map<NodeId, unknown>;\n /** The box the move resized `ResizeBox.bounds` to; absent before the first move. */\n readonly proposed?: Bounds;\n}\n\n/** The part of a resize that a box alone decides: what it writes, the box\n * they share, and where each starts. */\nexport type ResizePlace = Pick<\n ResizeSession,\n 'scene' | 'writeIds' | 'sharedBox' | 'box' | 'startPoses' | 'startWorlds' | 'geometryOf' | 'poseFrame' | 'view' | 'minimum'\n>;\n\n/** Capture what resizing `ids` from `anchor` acts on. Null when nothing it\n * would write exists. */\nexport function captureResize(\n scene: Scene<unknown, string, unknown>,\n ids: readonly NodeId[],\n anchor: ResizeAnchor,\n deps: Pick<ActionDeps, 'poseDescriptor' | 'resizePolicy' | 'view' | 'boundsIncludeStroke'>,\n): {\n place: ResizePlace;\n ruleOrigin: Bounds;\n policy: ActionDeps['resizePolicy'];\n constraints: BoundsConstraint<Bounds>[];\n} | null {\n const geometry = poseDescriptorOf(deps.poseDescriptor);\n const policy = deps.resizePolicy;\n const { constraints, expandIds } = resolvePolicy(policy);\n const geometryOf = nodeDescriptors(scene, geometry);\n\n const targets = captureTargets(scene, ids, expandIds);\n if (!targets) return null;\n const { writeIds, isGroupPath } = targets;\n\n // The drag delta is world, so the anchor math runs on world poses — carried\n // into the plane the edit is measured in — and each result is carried back\n // to its node's plane and stored in the node's own parent frame.\n const poseFrame = scenePoseFrame(scene);\n const view = deps.view;\n const boxes = drawnBoxes({ ...deps, scene }, poseFrame);\n const starts = captureStarts(scene, writeIds, geometryOf, poseFrame, view, boxes);\n if (starts.startPoses.size === 0) return null;\n\n const { box, ruleOrigin } = captureBox(starts, writeIds, isGroupPath, anchor, geometry, geometryOf);\n const sharedBox = isGroupPath || writeIds.length > 1;\n const written = writeIds.filter((id) => starts.startPoses.has(id));\n return {\n place: {\n scene,\n writeIds,\n sharedBox,\n minimum: boxMinimum(scene, written, starts.leafBounds, starts.startWorlds, box.bounds, sharedBox),\n box,\n startPoses: starts.startPoses,\n startWorlds: starts.startWorlds,\n geometryOf,\n poseFrame,\n view,\n },\n ruleOrigin,\n policy,\n constraints,\n };\n}\n\n/** The drag's snap session, over the box as drawn in its own frame. */\nfunction beginSnap(ctx: InvocationCtx, ids: readonly NodeId[], press: { x: number; y: number }, box: ResizeBox): SnapSession {\n const frame = boxFrame(box, box.bounds);\n const req: SnapBegin = {\n gesture: 'resize',\n ids,\n press,\n handle: box.anchor,\n constrain: 'aspect',\n box: { bounds: outsetBox(box.bounds, box.extents), toWorld: (p) => localToWorld(frame, p.x, p.y) },\n };\n return ctx.deps.snap?.begin(req) ?? inputSession(req);\n}\n\n/** Capture a resize at press. Null when nothing is selected, the press is not\n * on a resize handle, or nothing it would write exists. Runs each\n * constraint's `onStart`. */\nexport function beginResize(\n ctx: InvocationCtx,\n): { session: ResizeSession; readers: GestureReaders } | null {\n const selection = ctx.deps.selection;\n const scene = ctx.deps.scene;\n if (!selection || !scene) return null;\n\n const ids = selection.get() as NodeId[];\n if (ids.length === 0) return null;\n\n const anchor = ctx.drag?.affordance?.anchor;\n if (!anchor) return null;\n\n const captured = captureResize(scene, ids, anchor, ctx.deps);\n if (!captured) return null;\n const { place, ruleOrigin, policy, constraints } = captured;\n const startWorld = { x: ctx.drag?.start.x ?? 0, y: ctx.drag?.start.y ?? 0 };\n const readers = gestureReaders(ctx.deps);\n const gestureCtx = buildGestureContext(ctx, ids, ruleOrigin, startWorld, readers);\n for (const b of constraints) b.onStart?.(gestureCtx);\n\n const session: ResizeSession = {\n ...place,\n startWorld,\n constraints,\n gestureCtx,\n applyOps: ctx.deps.applyOps,\n geometryProjection: ctx.deps.geometryProjection,\n selectionMethods: selection.adapterMethods,\n lifecycle: readGestureLifecycle(policy, 'Resize', constraints),\n snap: beginSnap(ctx, ids, startWorld, place.box),\n };\n return { session, readers };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAiGA,SAAgB,cACd,OACA,KACA,QACA,MAMO;CACP,MAAM,WAAW,iBAAiB,KAAK,cAAc;CACrD,MAAM,SAAS,KAAK;CACpB,MAAM,EAAE,aAAa,cAAc,cAAc,MAAM;CACvD,MAAM,aAAa,gBAAgB,OAAO,QAAQ;CAElD,MAAM,UAAU,eAAe,OAAO,KAAK,SAAS;CACpD,IAAI,CAAC,SAAS,OAAO;CACrB,MAAM,EAAE,UAAU,gBAAgB;CAKlC,MAAM,YAAY,eAAe,KAAK;CACtC,MAAM,OAAO,KAAK;CAClB,MAAM,QAAQ,WAAW;EAAE,GAAG;EAAM;CAAM,GAAG,SAAS;CACtD,MAAM,SAAS,cAAc,OAAO,UAAU,YAAY,WAAW,MAAM,KAAK;CAChF,IAAI,OAAO,WAAW,SAAS,GAAG,OAAO;CAEzC,MAAM,EAAE,KAAK,eAAe,WAAW,QAAQ,UAAU,aAAa,QAAQ,UAAU,UAAU;CAClG,MAAM,YAAY,eAAe,SAAS,SAAS;CACnD,MAAM,UAAU,SAAS,QAAQ,OAAO,OAAO,WAAW,IAAI,EAAE,CAAC;CACjE,OAAO;EACL,OAAO;GACL;GACA;GACA;GACA,SAAS,WAAW,OAAO,SAAS,OAAO,YAAY,OAAO,aAAa,IAAI,QAAQ,SAAS;GAChG;GACA,YAAY,OAAO;GACnB,aAAa,OAAO;GACpB;GACA;GACA;EACF;EACA;EACA;EACA;CACF;AACF;;AAGA,SAAS,UAAU,KAAoB,KAAwB,OAAiC,KAA6B;CAC3H,MAAM,QAAQ,SAAS,KAAK,IAAI,MAAM;CACtC,MAAM,MAAiB;EACrB,SAAS;EACT;EACA;EACA,QAAQ,IAAI;EACZ,WAAW;EACX,KAAK;GAAE,QAAQ,UAAU,IAAI,QAAQ,IAAI,OAAO;GAAG,UAAU,MAAM,aAAa,OAAO,EAAE,GAAG,EAAE,CAAC;EAAE;CACnG;CACA,OAAO,IAAI,KAAK,MAAM,MAAM,GAAG,KAAK,aAAa,GAAG;AACtD;;;;AAKA,SAAgB,YACd,KAC4D;CAC5D,MAAM,YAAY,IAAI,KAAK;CAC3B,MAAM,QAAQ,IAAI,KAAK;CACvB,IAAI,CAAC,aAAa,CAAC,OAAO,OAAO;CAEjC,MAAM,MAAM,UAAU,IAAI;CAC1B,IAAI,IAAI,WAAW,GAAG,OAAO;CAE7B,MAAM,SAAS,IAAI,MAAM,YAAY;CACrC,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,WAAW,cAAc,OAAO,KAAK,QAAQ,IAAI,IAAI;CAC3D,IAAI,CAAC,UAAU,OAAO;CACtB,MAAM,EAAE,OAAO,YAAY,QAAQ,gBAAgB;CACnD,MAAM,aAAa;EAAE,GAAG,IAAI,MAAM,MAAM,KAAK;EAAG,GAAG,IAAI,MAAM,MAAM,KAAK;CAAE;CAC1E,MAAM,UAAU,eAAe,IAAI,IAAI;CACvC,MAAM,aAAa,oBAAoB,KAAK,KAAK,YAAY,YAAY,OAAO;CAChF,KAAK,MAAM,KAAK,aAAa,EAAE,UAAU,UAAU;CAanD,OAAO;EAAE;GAVP,GAAG;GACH;GACA;GACA;GACA,UAAU,IAAI,KAAK;GACnB,oBAAoB,IAAI,KAAK;GAC7B,kBAAkB,UAAU;GAC5B,WAAW,qBAAqB,QAAQ,UAAU,WAAW;GAC7D,MAAM,UAAU,KAAK,KAAK,YAAY,MAAM,GAAG;EAExC;EAAS;CAAQ;AAC5B"}
1
+ {"version":3,"file":"session.js","names":[],"sources":["../../../../src/interactions/actions/resize/session.ts"],"sourcesContent":["import type { PoseLinear } from '@weasel-js/geom';\nimport type { ActionDeps, InvocationCtx } from '@weasel-js/routing';\nimport { transformFinisher, type TransformFinisher } from '../transformFinish';\nimport type { Op } from 'core/ops/types';\nimport type { NodeId, Scene } from 'core/scene/types';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { gestureReaders, type GestureReaders } from '../../gestures/shared/screenTolerance';\nimport type { BoundsConstraint, GestureContext, ResizeAnchor } from '../../gestures/types';\nimport type { GeometryProjection } from '../geometryProjection';\nimport { readGestureLifecycle, type GestureLifecycle } from '../gestureLifecycle';\nimport { nodeDescriptors, poseDescriptorOf } from '../poseDescriptorDep';\nimport { scenePoseFrame, type PoseFrame } from '../poseFrame';\nimport { buildGestureContext, captureBox, captureStarts, captureTargets, resolvePolicy } from './capture';\nimport type { PoseDescriptor } from './geometry';\nimport type { InkExtents } from 'core/geometry/inkExtents';\nimport { drawnBoxes } from '../drawnBoxes';\nimport type { MinSize } from 'core/scene/minSize';\nimport { boxMinimum } from './minimum';\nimport { boxFrame } from './boxFrame';\nimport { localToWorld } from 'core/geometry/targetFrame';\nimport { outsetBox } from 'core/geometry/inkExtents';\nimport type { SnapBegin, SnapSession } from '../snapDep';\nimport { inputSession } from 'features/snap/session2d';\n\n/** The box the drag resizes, as the press found it. */\nexport interface ResizeBox {\n /** The union for a group or a multi-selection, a lone target's own bounds\n * otherwise. The `src` rect every write id is remapped from. */\n readonly bounds: Bounds;\n readonly anchor: ResizeAnchor;\n /** A lone target's rotation; 0 for a group. */\n readonly rotation: number;\n /** A lone target's `transform`; none for a group. */\n readonly transform?: PoseLinear;\n /** World position of the corner opposite the dragged one, which a resize\n * in a turned or skewed frame pins. */\n readonly fixedWorld: { x: number; y: number };\n /** How far the ink reaches past `bounds` in its own frame. The handles sit\n * on `bounds` grown by it, and the constraints see that box; the stroke\n * does not scale, so the same extents come off whatever box they return. */\n readonly extents: InkExtents;\n}\n\n/** What a resize captured at press. Nothing here changes during the drag. */\nexport interface ResizeSession {\n readonly scene: Scene<unknown, string, unknown>;\n /** The ids the gesture writes: the leaf set for a group, else the selection. */\n readonly writeIds: readonly NodeId[];\n /** Whether `box` stands for several write ids, each remapped inside it,\n * rather than being a lone target's own. */\n readonly sharedBox: boolean;\n readonly box: ResizeBox;\n /** Start poses as stored: the `from` of each op. */\n readonly startPoses: ReadonlyMap<NodeId, unknown>;\n /** Start poses in world, the frame the anchor math and the drag share. */\n readonly startWorlds: ReadonlyMap<NodeId, unknown>;\n /** Where the pointer pressed. */\n readonly startWorld: { x: number; y: number };\n readonly geometryOf: (id: NodeId) => PoseDescriptor<unknown>;\n /** World reads and local writes over the scene's composition strategy. */\n readonly poseFrame: PoseFrame<unknown>;\n readonly view: unknown;\n /** The smallest `box` may be resized to, from the written nodes' own\n * minimums: a floor under every constraint. `null` for none. */\n readonly minimum: MinSize | null;\n readonly constraints: readonly BoundsConstraint<Bounds>[];\n /** Reused across the drag; constraints mutate its `scratch`. */\n readonly gestureCtx: GestureContext<Bounds>;\n readonly applyOps?: (ops: Op[], label: string) => void;\n /** Finishes the committed batch with the other nodes that change with it. */\n readonly finish?: TransformFinisher;\n /** When present, the commit maps each leaf's data-held geometry by the\n * affine its pose underwent. */\n readonly geometryProjection?: GeometryProjection;\n readonly selectionMethods: SelectionApi['adapterMethods'] | undefined;\n /** Label, history, and callbacks from the `resizePolicy` dep. */\n readonly lifecycle: GestureLifecycle;\n /** The drag's snapping: the `snap` dep's session, else one that only holds\n * the handle to its axis and Shift to the aspect. */\n readonly snap: SnapSession;\n}\n\n/** What one `onMove` produced. Replaced, not mutated, by the next. */\nexport interface ResizeFrame {\n /** Stored-frame pose of every write id the move reached. */\n readonly previews: Map<NodeId, unknown>;\n /** The box the move resized `ResizeBox.bounds` to; absent before the first move. */\n readonly proposed?: Bounds;\n}\n\n/** The part of a resize that a box alone decides: what it writes, the box\n * they share, and where each starts. */\nexport type ResizePlace = Pick<\n ResizeSession,\n 'scene' | 'writeIds' | 'sharedBox' | 'box' | 'startPoses' | 'startWorlds' | 'geometryOf' | 'poseFrame' | 'view' | 'minimum'\n>;\n\n/** Capture what resizing `ids` from `anchor` acts on. Null when nothing it\n * would write exists. */\nexport function captureResize(\n scene: Scene<unknown, string, unknown>,\n ids: readonly NodeId[],\n anchor: ResizeAnchor,\n deps: Pick<ActionDeps, 'poseDescriptor' | 'resizePolicy' | 'view' | 'boundsIncludeStroke'>,\n): {\n place: ResizePlace;\n ruleOrigin: Bounds;\n policy: ActionDeps['resizePolicy'];\n constraints: BoundsConstraint<Bounds>[];\n} | null {\n const geometry = poseDescriptorOf(deps.poseDescriptor);\n const policy = deps.resizePolicy;\n const { constraints, expandIds } = resolvePolicy(policy);\n const geometryOf = nodeDescriptors(scene, geometry);\n\n const targets = captureTargets(scene, ids, expandIds);\n if (!targets) return null;\n const { writeIds, isGroupPath } = targets;\n\n // The drag delta is world, so the anchor math runs on world poses — carried\n // into the plane the edit is measured in — and each result is carried back\n // to its node's plane and stored in the node's own parent frame.\n const poseFrame = scenePoseFrame(scene);\n const view = deps.view;\n const boxes = drawnBoxes({ ...deps, scene }, poseFrame);\n const starts = captureStarts(scene, writeIds, geometryOf, poseFrame, view, boxes);\n if (starts.startPoses.size === 0) return null;\n\n const { box, ruleOrigin } = captureBox(starts, writeIds, isGroupPath, anchor, geometry, geometryOf);\n const sharedBox = isGroupPath || writeIds.length > 1;\n const written = writeIds.filter((id) => starts.startPoses.has(id));\n return {\n place: {\n scene,\n writeIds,\n sharedBox,\n minimum: boxMinimum(scene, written, starts.leafBounds, starts.startWorlds, box.bounds, sharedBox),\n box,\n startPoses: starts.startPoses,\n startWorlds: starts.startWorlds,\n geometryOf,\n poseFrame,\n view,\n },\n ruleOrigin,\n policy,\n constraints,\n };\n}\n\n/** The drag's snap session, over the box as drawn in its own frame. */\nfunction beginSnap(ctx: InvocationCtx, ids: readonly NodeId[], press: { x: number; y: number }, box: ResizeBox): SnapSession {\n const frame = boxFrame(box, box.bounds);\n const req: SnapBegin = {\n gesture: 'resize',\n ids,\n press,\n handle: box.anchor,\n constrain: 'aspect',\n box: { bounds: outsetBox(box.bounds, box.extents), toWorld: (p) => localToWorld(frame, p.x, p.y) },\n };\n return ctx.deps.snap?.begin(req) ?? inputSession(req);\n}\n\n/** Capture a resize at press. Null when nothing is selected, the press is not\n * on a resize handle, or nothing it would write exists. Runs each\n * constraint's `onStart`. */\nexport function beginResize(\n ctx: InvocationCtx,\n): { session: ResizeSession; readers: GestureReaders } | null {\n const selection = ctx.deps.selection;\n const scene = ctx.deps.scene;\n if (!selection || !scene) return null;\n\n const ids = selection.get() as NodeId[];\n if (ids.length === 0) return null;\n\n const anchor = ctx.drag?.affordance?.anchor;\n if (!anchor) return null;\n\n const captured = captureResize(scene, ids, anchor, ctx.deps);\n if (!captured) return null;\n const { place, ruleOrigin, policy, constraints } = captured;\n const startWorld = { x: ctx.drag?.start.x ?? 0, y: ctx.drag?.start.y ?? 0 };\n const readers = gestureReaders(ctx.deps);\n const gestureCtx = buildGestureContext(ctx, ids, ruleOrigin, startWorld, readers);\n for (const b of constraints) b.onStart?.(gestureCtx);\n\n const session: ResizeSession = {\n ...place,\n startWorld,\n constraints,\n gestureCtx,\n applyOps: ctx.deps.applyOps,\n finish: transformFinisher(ctx.deps),\n geometryProjection: ctx.deps.geometryProjection,\n selectionMethods: selection.adapterMethods,\n lifecycle: readGestureLifecycle(policy, 'Resize', constraints),\n snap: beginSnap(ctx, ids, startWorld, place.box),\n };\n return { session, readers };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAoGA,SAAgB,cACd,OACA,KACA,QACA,MAMO;CACP,MAAM,WAAW,iBAAiB,KAAK,cAAc;CACrD,MAAM,SAAS,KAAK;CACpB,MAAM,EAAE,aAAa,cAAc,cAAc,MAAM;CACvD,MAAM,aAAa,gBAAgB,OAAO,QAAQ;CAElD,MAAM,UAAU,eAAe,OAAO,KAAK,SAAS;CACpD,IAAI,CAAC,SAAS,OAAO;CACrB,MAAM,EAAE,UAAU,gBAAgB;CAKlC,MAAM,YAAY,eAAe,KAAK;CACtC,MAAM,OAAO,KAAK;CAClB,MAAM,QAAQ,WAAW;EAAE,GAAG;EAAM;CAAM,GAAG,SAAS;CACtD,MAAM,SAAS,cAAc,OAAO,UAAU,YAAY,WAAW,MAAM,KAAK;CAChF,IAAI,OAAO,WAAW,SAAS,GAAG,OAAO;CAEzC,MAAM,EAAE,KAAK,eAAe,WAAW,QAAQ,UAAU,aAAa,QAAQ,UAAU,UAAU;CAClG,MAAM,YAAY,eAAe,SAAS,SAAS;CACnD,MAAM,UAAU,SAAS,QAAQ,OAAO,OAAO,WAAW,IAAI,EAAE,CAAC;CACjE,OAAO;EACL,OAAO;GACL;GACA;GACA;GACA,SAAS,WAAW,OAAO,SAAS,OAAO,YAAY,OAAO,aAAa,IAAI,QAAQ,SAAS;GAChG;GACA,YAAY,OAAO;GACnB,aAAa,OAAO;GACpB;GACA;GACA;EACF;EACA;EACA;EACA;CACF;AACF;;AAGA,SAAS,UAAU,KAAoB,KAAwB,OAAiC,KAA6B;CAC3H,MAAM,QAAQ,SAAS,KAAK,IAAI,MAAM;CACtC,MAAM,MAAiB;EACrB,SAAS;EACT;EACA;EACA,QAAQ,IAAI;EACZ,WAAW;EACX,KAAK;GAAE,QAAQ,UAAU,IAAI,QAAQ,IAAI,OAAO;GAAG,UAAU,MAAM,aAAa,OAAO,EAAE,GAAG,EAAE,CAAC;EAAE;CACnG;CACA,OAAO,IAAI,KAAK,MAAM,MAAM,GAAG,KAAK,aAAa,GAAG;AACtD;;;;AAKA,SAAgB,YACd,KAC4D;CAC5D,MAAM,YAAY,IAAI,KAAK;CAC3B,MAAM,QAAQ,IAAI,KAAK;CACvB,IAAI,CAAC,aAAa,CAAC,OAAO,OAAO;CAEjC,MAAM,MAAM,UAAU,IAAI;CAC1B,IAAI,IAAI,WAAW,GAAG,OAAO;CAE7B,MAAM,SAAS,IAAI,MAAM,YAAY;CACrC,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,WAAW,cAAc,OAAO,KAAK,QAAQ,IAAI,IAAI;CAC3D,IAAI,CAAC,UAAU,OAAO;CACtB,MAAM,EAAE,OAAO,YAAY,QAAQ,gBAAgB;CACnD,MAAM,aAAa;EAAE,GAAG,IAAI,MAAM,MAAM,KAAK;EAAG,GAAG,IAAI,MAAM,MAAM,KAAK;CAAE;CAC1E,MAAM,UAAU,eAAe,IAAI,IAAI;CACvC,MAAM,aAAa,oBAAoB,KAAK,KAAK,YAAY,YAAY,OAAO;CAChF,KAAK,MAAM,KAAK,aAAa,EAAE,UAAU,UAAU;CAcnD,OAAO;EAAE;GAXP,GAAG;GACH;GACA;GACA;GACA,UAAU,IAAI,KAAK;GACnB,QAAQ,kBAAkB,IAAI,IAAI;GAClC,oBAAoB,IAAI,KAAK;GAC7B,kBAAkB,UAAU;GAC5B,WAAW,qBAAqB,QAAQ,UAAU,WAAW;GAC7D,MAAM,UAAU,KAAK,KAAK,YAAY,MAAM,GAAG;EAExC;EAAS;CAAQ;AAC5B"}
@@ -1,3 +1,4 @@
1
+ import { effectivePose } from "../../core/scene/effectivePose.js";
1
2
  import { asNodeId } from "../../core/scene/types.js";
2
3
 
3
4
  //#region src/interactions/actions/resolvePreviews.ts
@@ -15,7 +16,7 @@ function firstNonNull(sources, id, read) {
15
16
  * which is what lets a caller draw a container and its contents as one thing.
16
17
  *
17
18
  * An id with no node is skipped: an insert previews before the node it will
18
- * create exists. So is an id no source has a pose or data for.
19
+ * create exists.
19
20
  */
20
21
  function resolvePreviews(sources, scene) {
21
22
  const list = [...sources];
@@ -37,7 +38,6 @@ function resolvePreviews(sources, scene) {
37
38
  if (!node) return null;
38
39
  const pose = firstNonNull(list, id, (s, i) => s.previewPose?.(i));
39
40
  const data = firstNonNull(list, id, (s, i) => s.previewData?.(i));
40
- if (pose == null && data == null) return null;
41
41
  const children = [];
42
42
  for (const childId of scene.childrenOf(asNodeId(id))) {
43
43
  if (!ids.has(childId)) continue;
@@ -47,7 +47,7 @@ function resolvePreviews(sources, scene) {
47
47
  return {
48
48
  id: asNodeId(id),
49
49
  node,
50
- pose: pose ?? node.pose,
50
+ pose: pose ?? effectivePose(scene, node),
51
51
  data: data ?? node.data,
52
52
  opaque: opaque.has(id),
53
53
  children
@@ -1 +1 @@
1
- {"version":3,"file":"resolvePreviews.js","names":[],"sources":["../../../src/interactions/actions/resolvePreviews.ts"],"sourcesContent":["/**\n * What an in-flight gesture is proposing, resolved against the committed scene.\n *\n * An ongoing action publishes interim poses on its handle rather than writing\n * them to the scene, and `<SceneCanvas>`'s ghost layer used to be the only\n * thing that knew how to read them: which ids are in flight, which of those are\n * roots, whose preview wins when two sources name the same id, and which are\n * merely displaced rather than dragged. None of that is about drawing, and a\n * consumer with its own renderer needs all of it.\n */\n\nimport type { GesturePreviewSource } from '../../canvas/gestureBounds';\nimport type { Node, NodeId, Scene } from '../../core/scene/types';\nimport { asNodeId } from '../../core/scene/types';\n\n/** One node an in-flight gesture is previewing, as {@link resolvePreviews} returns it. */\nexport interface PreviewNode<TData, TLayer extends string, TPose> {\n id: NodeId;\n /** The node as committed — the gesture has not touched the scene. */\n node: Node<TData, TLayer, TPose>;\n /** The interim pose, or the committed one when only data is in flight. */\n pose: TPose;\n /** The interim data, or the committed one when only the pose is in flight. */\n data: TData;\n /**\n * The gesture is not carrying this one under the pointer — a layout sibling\n * reflowing into its destination slot. It reads better painted settled.\n */\n opaque: boolean;\n /** Previewed descendants, so a container's ghost brings its children. */\n children: PreviewNode<TData, TLayer, TPose>[];\n}\n\n/** First non-null answer across sources, in the order given. */\nfunction firstNonNull<T>(\n sources: readonly GesturePreviewSource[],\n id: string,\n read: (source: GesturePreviewSource, id: string) => unknown,\n): T | null {\n for (const source of sources) {\n const value = read(source, id);\n if (value != null) return value as T;\n }\n return null;\n}\n\n/**\n * The previewing subtrees, as roots. A previewed node whose parent is also\n * previewing is a child of that parent's entry rather than a root of its own,\n * which is what lets a caller draw a container and its contents as one thing.\n *\n * An id with no node is skipped: an insert previews before the node it will\n * create exists. So is an id no source has a pose or data for.\n */\nexport function resolvePreviews<TData, TLayer extends string, TPose>(\n sources: Iterable<GesturePreviewSource>,\n scene: Scene<TData, TLayer, TPose>,\n): PreviewNode<TData, TLayer, TPose>[] {\n const list = [...sources];\n const ids = new Set<string>();\n for (const source of list) {\n const previewing = source.previewIds?.();\n if (!previewing) continue;\n for (const id of previewing) ids.add(id);\n }\n if (ids.size === 0) return [];\n\n const opaque = new Set<string>();\n for (const source of list) {\n const settled = source.previewOpaqueIds?.();\n if (!settled) continue;\n for (const id of settled) opaque.add(id);\n }\n\n const build = (id: string): PreviewNode<TData, TLayer, TPose> | null => {\n const node = scene.get(asNodeId(id));\n if (!node) return null;\n const pose = firstNonNull<TPose>(list, id, (s, i) => s.previewPose?.(i));\n const data = firstNonNull<TData>(list, id, (s, i) => s.previewData?.(i));\n if (pose == null && data == null) return null;\n\n const children: PreviewNode<TData, TLayer, TPose>[] = [];\n for (const childId of scene.childrenOf(asNodeId(id))) {\n if (!ids.has(childId)) continue;\n const child = build(childId);\n if (child) children.push(child);\n }\n\n return {\n id: asNodeId(id),\n node,\n pose: pose ?? node.pose,\n data: data ?? node.data,\n opaque: opaque.has(id),\n children,\n };\n };\n\n const roots: PreviewNode<TData, TLayer, TPose>[] = [];\n for (const id of ids) {\n const parent = scene.get(asNodeId(id))?.parent;\n if (parent != null && ids.has(parent)) continue;\n const root = build(id);\n if (root) roots.push(root);\n }\n return roots;\n}\n\n/** Every entry in the forest, parents before their children. */\nexport function flattenPreviews<TData, TLayer extends string, TPose>(\n roots: readonly PreviewNode<TData, TLayer, TPose>[],\n): PreviewNode<TData, TLayer, TPose>[] {\n const out: PreviewNode<TData, TLayer, TPose>[] = [];\n const walk = (entry: PreviewNode<TData, TLayer, TPose>) => {\n out.push(entry);\n for (const child of entry.children) walk(child);\n };\n for (const root of roots) walk(root);\n return out;\n}\n"],"mappings":";;;;AAkCA,SAAS,aACP,SACA,IACA,MACU;CACV,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,QAAQ,KAAK,QAAQ,EAAE;EAC7B,IAAI,SAAS,MAAM,OAAO;CAC5B;CACA,OAAO;AACT;;;;;;;;;AAUA,SAAgB,gBACd,SACA,OACqC;CACrC,MAAM,OAAO,CAAC,GAAG,OAAO;CACxB,MAAM,sBAAM,IAAI,IAAY;CAC5B,KAAK,MAAM,UAAU,MAAM;EACzB,MAAM,aAAa,OAAO,aAAa;EACvC,IAAI,CAAC,YAAY;EACjB,KAAK,MAAM,MAAM,YAAY,IAAI,IAAI,EAAE;CACzC;CACA,IAAI,IAAI,SAAS,GAAG,OAAO,CAAC;CAE5B,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,MAAM,UAAU,MAAM;EACzB,MAAM,UAAU,OAAO,mBAAmB;EAC1C,IAAI,CAAC,SAAS;EACd,KAAK,MAAM,MAAM,SAAS,OAAO,IAAI,EAAE;CACzC;CAEA,MAAM,SAAS,OAAyD;EACtE,MAAM,OAAO,MAAM,IAAI,SAAS,EAAE,CAAC;EACnC,IAAI,CAAC,MAAM,OAAO;EAClB,MAAM,OAAO,aAAoB,MAAM,KAAK,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;EACvE,MAAM,OAAO,aAAoB,MAAM,KAAK,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;EACvE,IAAI,QAAQ,QAAQ,QAAQ,MAAM,OAAO;EAEzC,MAAM,WAAgD,CAAC;EACvD,KAAK,MAAM,WAAW,MAAM,WAAW,SAAS,EAAE,CAAC,GAAG;GACpD,IAAI,CAAC,IAAI,IAAI,OAAO,GAAG;GACvB,MAAM,QAAQ,MAAM,OAAO;GAC3B,IAAI,OAAO,SAAS,KAAK,KAAK;EAChC;EAEA,OAAO;GACL,IAAI,SAAS,EAAE;GACf;GACA,MAAM,QAAQ,KAAK;GACnB,MAAM,QAAQ,KAAK;GACnB,QAAQ,OAAO,IAAI,EAAE;GACrB;EACF;CACF;CAEA,MAAM,QAA6C,CAAC;CACpD,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,SAAS,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC,EAAE;EACxC,IAAI,UAAU,QAAQ,IAAI,IAAI,MAAM,GAAG;EACvC,MAAM,OAAO,MAAM,EAAE;EACrB,IAAI,MAAM,MAAM,KAAK,IAAI;CAC3B;CACA,OAAO;AACT;;AAGA,SAAgB,gBACd,OACqC;CACrC,MAAM,MAA2C,CAAC;CAClD,MAAM,QAAQ,UAA6C;EACzD,IAAI,KAAK,KAAK;EACd,KAAK,MAAM,SAAS,MAAM,UAAU,KAAK,KAAK;CAChD;CACA,KAAK,MAAM,QAAQ,OAAO,KAAK,IAAI;CACnC,OAAO;AACT"}
1
+ {"version":3,"file":"resolvePreviews.js","names":[],"sources":["../../../src/interactions/actions/resolvePreviews.ts"],"sourcesContent":["/**\n * What an in-flight gesture is proposing, resolved against the scene.\n *\n * An ongoing action names the ids it previews on its handle. A transform —\n * move, resize, rotate — publishes their poses to the scene's overrides, and\n * other actions answer `previewPose` on the handle. Resolving them means\n * knowing which ids are in flight, which of those are roots, whose preview\n * wins when two sources name the same id, and which are merely displaced\n * rather than dragged. None of that is about drawing, and a consumer with its\n * own renderer needs all of it.\n */\n\nimport type { GesturePreviewSource } from '../../canvas/gestureBounds';\nimport type { Node, NodeId, Scene } from '../../core/scene/types';\nimport { asNodeId } from '../../core/scene/types';\nimport { effectivePose } from '../../core/scene/effectivePose';\n\n/** One node an in-flight gesture is previewing, as {@link resolvePreviews} returns it. */\nexport interface PreviewNode<TData, TLayer extends string, TPose> {\n id: NodeId;\n /** The node as committed. */\n node: Node<TData, TLayer, TPose>;\n /** The pose a source previews, else the node's effective pose — where the\n * scene's overrides put it mid-transform. */\n pose: TPose;\n /** The interim data, or the committed one when only the pose is in flight. */\n data: TData;\n /**\n * The gesture is not carrying this one under the pointer — a layout sibling\n * reflowing into its destination slot. It reads better painted settled.\n */\n opaque: boolean;\n /** Previewed descendants, so a container's ghost brings its children. */\n children: PreviewNode<TData, TLayer, TPose>[];\n}\n\n/** First non-null answer across sources, in the order given. */\nfunction firstNonNull<T>(\n sources: readonly GesturePreviewSource[],\n id: string,\n read: (source: GesturePreviewSource, id: string) => unknown,\n): T | null {\n for (const source of sources) {\n const value = read(source, id);\n if (value != null) return value as T;\n }\n return null;\n}\n\n/**\n * The previewing subtrees, as roots. A previewed node whose parent is also\n * previewing is a child of that parent's entry rather than a root of its own,\n * which is what lets a caller draw a container and its contents as one thing.\n *\n * An id with no node is skipped: an insert previews before the node it will\n * create exists.\n */\nexport function resolvePreviews<TData, TLayer extends string, TPose>(\n sources: Iterable<GesturePreviewSource>,\n scene: Scene<TData, TLayer, TPose>,\n): PreviewNode<TData, TLayer, TPose>[] {\n const list = [...sources];\n const ids = new Set<string>();\n for (const source of list) {\n const previewing = source.previewIds?.();\n if (!previewing) continue;\n for (const id of previewing) ids.add(id);\n }\n if (ids.size === 0) return [];\n\n const opaque = new Set<string>();\n for (const source of list) {\n const settled = source.previewOpaqueIds?.();\n if (!settled) continue;\n for (const id of settled) opaque.add(id);\n }\n\n const build = (id: string): PreviewNode<TData, TLayer, TPose> | null => {\n const node = scene.get(asNodeId(id));\n if (!node) return null;\n const pose = firstNonNull<TPose>(list, id, (s, i) => s.previewPose?.(i));\n const data = firstNonNull<TData>(list, id, (s, i) => s.previewData?.(i));\n\n const children: PreviewNode<TData, TLayer, TPose>[] = [];\n for (const childId of scene.childrenOf(asNodeId(id))) {\n if (!ids.has(childId)) continue;\n const child = build(childId);\n if (child) children.push(child);\n }\n\n return {\n id: asNodeId(id),\n node,\n pose: pose ?? effectivePose(scene, node),\n data: data ?? node.data,\n opaque: opaque.has(id),\n children,\n };\n };\n\n const roots: PreviewNode<TData, TLayer, TPose>[] = [];\n for (const id of ids) {\n const parent = scene.get(asNodeId(id))?.parent;\n if (parent != null && ids.has(parent)) continue;\n const root = build(id);\n if (root) roots.push(root);\n }\n return roots;\n}\n\n/** Every entry in the forest, parents before their children. */\nexport function flattenPreviews<TData, TLayer extends string, TPose>(\n roots: readonly PreviewNode<TData, TLayer, TPose>[],\n): PreviewNode<TData, TLayer, TPose>[] {\n const out: PreviewNode<TData, TLayer, TPose>[] = [];\n const walk = (entry: PreviewNode<TData, TLayer, TPose>) => {\n out.push(entry);\n for (const child of entry.children) walk(child);\n };\n for (const root of roots) walk(root);\n return out;\n}\n"],"mappings":";;;;;AAqCA,SAAS,aACP,SACA,IACA,MACU;CACV,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,QAAQ,KAAK,QAAQ,EAAE;EAC7B,IAAI,SAAS,MAAM,OAAO;CAC5B;CACA,OAAO;AACT;;;;;;;;;AAUA,SAAgB,gBACd,SACA,OACqC;CACrC,MAAM,OAAO,CAAC,GAAG,OAAO;CACxB,MAAM,sBAAM,IAAI,IAAY;CAC5B,KAAK,MAAM,UAAU,MAAM;EACzB,MAAM,aAAa,OAAO,aAAa;EACvC,IAAI,CAAC,YAAY;EACjB,KAAK,MAAM,MAAM,YAAY,IAAI,IAAI,EAAE;CACzC;CACA,IAAI,IAAI,SAAS,GAAG,OAAO,CAAC;CAE5B,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,MAAM,UAAU,MAAM;EACzB,MAAM,UAAU,OAAO,mBAAmB;EAC1C,IAAI,CAAC,SAAS;EACd,KAAK,MAAM,MAAM,SAAS,OAAO,IAAI,EAAE;CACzC;CAEA,MAAM,SAAS,OAAyD;EACtE,MAAM,OAAO,MAAM,IAAI,SAAS,EAAE,CAAC;EACnC,IAAI,CAAC,MAAM,OAAO;EAClB,MAAM,OAAO,aAAoB,MAAM,KAAK,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;EACvE,MAAM,OAAO,aAAoB,MAAM,KAAK,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;EAEvE,MAAM,WAAgD,CAAC;EACvD,KAAK,MAAM,WAAW,MAAM,WAAW,SAAS,EAAE,CAAC,GAAG;GACpD,IAAI,CAAC,IAAI,IAAI,OAAO,GAAG;GACvB,MAAM,QAAQ,MAAM,OAAO;GAC3B,IAAI,OAAO,SAAS,KAAK,KAAK;EAChC;EAEA,OAAO;GACL,IAAI,SAAS,EAAE;GACf;GACA,MAAM,QAAQ,cAAc,OAAO,IAAI;GACvC,MAAM,QAAQ,KAAK;GACnB,QAAQ,OAAO,IAAI,EAAE;GACrB;EACF;CACF;CAEA,MAAM,QAA6C,CAAC;CACpD,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,SAAS,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC,EAAE;EACxC,IAAI,UAAU,QAAQ,IAAI,IAAI,MAAM,GAAG;EACvC,MAAM,OAAO,MAAM,EAAE;EACrB,IAAI,MAAM,MAAM,KAAK,IAAI;CAC3B;CACA,OAAO;AACT;;AAGA,SAAgB,gBACd,OACqC;CACrC,MAAM,MAA2C,CAAC;CAClD,MAAM,QAAQ,UAA6C;EACzD,IAAI,KAAK,KAAK;EACd,KAAK,MAAM,SAAS,MAAM,UAAU,KAAK,KAAK;CAChD;CACA,KAAK,MAAM,QAAQ,OAAO,KAAK,IAAI;CACnC,OAAO;AACT"}
@@ -1,4 +1,5 @@
1
1
  import { rotateCursorAt } from "../../../affordances/rotateCursor.js";
2
+ import { transformPreview } from "../transformFinish.js";
2
3
  import { closeGesture, runBehaviorCancel } from "../gestureLifecycle.js";
3
4
  import { inPlane, selectionLayer } from "../planeInput.js";
4
5
  import { SNAP_ROLES } from "../../../features/snap/roles.js";
@@ -32,7 +33,8 @@ var rotateAction = inPlane({
32
33
  "view",
33
34
  "boundsIncludeStroke",
34
35
  "kitSettings",
35
- "snap"
36
+ "snap",
37
+ "transformFollowers"
36
38
  ],
37
39
  invoker: {
38
40
  timing: "ongoing",
@@ -42,6 +44,7 @@ var rotateAction = inPlane({
42
44
  const { session, readers } = begun;
43
45
  const subject = [...ctx.deps.selection?.get() ?? []];
44
46
  const hold = session.scene.overrides.hold();
47
+ const preview = transformPreview(ctx.deps, session.scene, () => frame.previews);
45
48
  let frame = {
46
49
  delta: 0,
47
50
  previews: /* @__PURE__ */ new Map()
@@ -57,7 +60,7 @@ var rotateAction = inPlane({
57
60
  onMove(moveCtx) {
58
61
  pointer = moveCtx.world;
59
62
  frame = nextFrame(session, readers, moveCtx);
60
- hold.set(frame.previews);
63
+ hold.set(preview.poses());
61
64
  },
62
65
  onEnd(_endCtx, reason) {
63
66
  let committed = false;
@@ -84,8 +87,8 @@ var rotateAction = inPlane({
84
87
  }, committed);
85
88
  }
86
89
  },
87
- previewIds: () => frame.previews.keys(),
88
- previewPose: (id) => frame.previews.get(id) ?? null
90
+ previewIds: preview.previewIds,
91
+ previewData: preview.previewData
89
92
  };
90
93
  }
91
94
  },
@@ -1 +1 @@
1
- {"version":3,"file":"action.js","names":[],"sources":["../../../../src/interactions/actions/rotate/action.ts"],"sourcesContent":["import type { Action } from '@weasel-js/routing';\nimport type { BindingOpts, InvocationCtx, OngoingHandle } from '@weasel-js/routing';\nimport type { NodeId } from 'core/scene/types';\nimport { closeGesture, runBehaviorCancel } from '../gestureLifecycle';\nimport { inPlane, selectionLayer } from '../planeInput';\nimport { rotateCursorAt } from 'affordances/rotateCursor';\nimport { commitRotate } from './commit';\nimport { nextFrame } from './frame';\nimport { beginRotate, type RotateFrame } from './session';\nimport { recordTransform } from '../transform-again/memory';\nimport { SNAP_ROLES } from 'features/snap/roles';\n\n/**\n * Pointer-driven rotation of the selection, committed as one undo entry.\n *\n * A pose whose descriptor has no `withRotation`, or reports\n * `supportsRotation` false, is left alone. The first rotated node's angle\n * snaps through the `snap` dep's session — quarter turns and other nodes'\n * angles, 15 degree steps while `snap.constrain` (Shift) is held — and the\n * rest of the selection turns by the same amount. Behaviors\n * (`opts.behaviors`) then see that node's turned pose; a pose one returns\n * from `onMove` sets the rotation for the whole selection.\n */\nexport const rotateAction: Action & { requires: string[] } = inPlane({\n id: 'rotate',\n label: 'Rotate',\n // No default binding: a bare `{ kind: 'drag' }` makes any drag no active\n // tool claimed rotate a non-empty selection. The rotation handle binds it\n // (`selectionRotateBindings`).\n eligible: { capability: 'transforms-selection' },\n modifierRoles: SNAP_ROLES,\n requires: ['selection', 'scene', 'applyOps', 'poseDescriptor', 'view', 'boundsIncludeStroke', 'kitSettings', 'snap'],\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle {\n const begun = beginRotate(ctx, opts);\n if (!begun) return {};\n const { session, readers } = begun;\n const subject = [...(ctx.deps.selection?.get() ?? [])] as string[];\n const hold = session.scene.overrides.hold();\n let frame: RotateFrame = { delta: 0, previews: new Map() };\n let pointer = ctx.world;\n session.lifecycle.start(session.gestureCtx.draggedIds);\n return {\n kind: 'rotate',\n cursor: () => {\n const scale = ctx.deps.view?.get().scale;\n return scale ? rotateCursorAt(session.turn.pivot, pointer, scale) : undefined;\n },\n onMove(moveCtx: InvocationCtx): void {\n pointer = moveCtx.world;\n frame = nextFrame(session, readers, moveCtx);\n hold.set(frame.previews);\n },\n onEnd(_endCtx: InvocationCtx, reason: 'commit' | 'cancel'): void {\n let committed = false;\n try {\n if (reason === 'commit') {\n committed = commitRotate(session, frame);\n if (committed) {\n const pivot = session.ids.length > 1 && !session.turn.useUnionPivot ? 'each' : 'union';\n recordTransform(session.scene, { kind: 'rotate', angle: frame.delta, pivot }, subject);\n }\n } else runBehaviorCancel(session.behaviors, session.gestureCtx);\n } finally {\n session.snap.end();\n closeGesture({ hold, previews: frame.previews, behaviors: session.behaviors, ctx: session.gestureCtx, lifecycle: session.lifecycle }, committed);\n }\n },\n previewIds: () => frame.previews.keys(),\n previewPose: (id: string) => frame.previews.get(id as NodeId) ?? null,\n };\n },\n },\n enabled: () => true,\n}, selectionLayer);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAuBA,IAAa,eAAgD,QAAQ;CACnE,IAAI;CACJ,OAAO;CAIP,UAAU,EAAE,YAAY,uBAAuB;CAC/C,eAAe;CACf,UAAU;EAAC;EAAa;EAAS;EAAY;EAAkB;EAAQ;EAAuB;EAAe;CAAM;CACnH,SAAS;EACP,QAAQ;EACR,MAAM,KAAoB,MAAmC;GAC3D,MAAM,QAAQ,YAAY,KAAK,IAAI;GACnC,IAAI,CAAC,OAAO,OAAO,CAAC;GACpB,MAAM,EAAE,SAAS,YAAY;GAC7B,MAAM,UAAU,CAAC,GAAI,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,CAAE;GACrD,MAAM,OAAO,QAAQ,MAAM,UAAU,KAAK;GAC1C,IAAI,QAAqB;IAAE,OAAO;IAAG,0BAAU,IAAI,IAAI;GAAE;GACzD,IAAI,UAAU,IAAI;GAClB,QAAQ,UAAU,MAAM,QAAQ,WAAW,UAAU;GACrD,OAAO;IACL,MAAM;IACN,cAAc;KACZ,MAAM,QAAQ,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC;KACnC,OAAO,QAAQ,eAAe,QAAQ,KAAK,OAAO,SAAS,KAAK,IAAI;IACtE;IACA,OAAO,SAA8B;KACnC,UAAU,QAAQ;KAClB,QAAQ,UAAU,SAAS,SAAS,OAAO;KAC3C,KAAK,IAAI,MAAM,QAAQ;IACzB;IACA,MAAM,SAAwB,QAAmC;KAC/D,IAAI,YAAY;KAChB,IAAI;MACF,IAAI,WAAW,UAAU;OACvB,YAAY,aAAa,SAAS,KAAK;OACvC,IAAI,WAAW;QACb,MAAM,QAAQ,QAAQ,IAAI,SAAS,KAAK,CAAC,QAAQ,KAAK,gBAAgB,SAAS;QAC/E,gBAAgB,QAAQ,OAAO;SAAE,MAAM;SAAU,OAAO,MAAM;SAAO;QAAM,GAAG,OAAO;OACvF;MACF,OAAO,kBAAkB,QAAQ,WAAW,QAAQ,UAAU;KAChE,UAAU;MACR,QAAQ,KAAK,IAAI;MACjB,aAAa;OAAE;OAAM,UAAU,MAAM;OAAU,WAAW,QAAQ;OAAW,KAAK,QAAQ;OAAY,WAAW,QAAQ;MAAU,GAAG,SAAS;KACjJ;IACF;IACA,kBAAkB,MAAM,SAAS,KAAK;IACtC,cAAc,OAAe,MAAM,SAAS,IAAI,EAAY,KAAK;GACnE;EACF;CACF;CACA,eAAe;AACjB,GAAG,cAAc"}
1
+ {"version":3,"file":"action.js","names":[],"sources":["../../../../src/interactions/actions/rotate/action.ts"],"sourcesContent":["import type { Action } from '@weasel-js/routing';\nimport type { BindingOpts, InvocationCtx, OngoingHandle } from '@weasel-js/routing';\nimport { transformPreview } from '../transformFinish';\nimport { closeGesture, runBehaviorCancel } from '../gestureLifecycle';\nimport { inPlane, selectionLayer } from '../planeInput';\nimport { rotateCursorAt } from 'affordances/rotateCursor';\nimport { commitRotate } from './commit';\nimport { nextFrame } from './frame';\nimport { beginRotate, type RotateFrame } from './session';\nimport { recordTransform } from '../transform-again/memory';\nimport { SNAP_ROLES } from 'features/snap/roles';\n\n/**\n * Pointer-driven rotation of the selection, committed as one undo entry.\n *\n * A pose whose descriptor has no `withRotation`, or reports\n * `supportsRotation` false, is left alone. The first rotated node's angle\n * snaps through the `snap` dep's session — quarter turns and other nodes'\n * angles, 15 degree steps while `snap.constrain` (Shift) is held — and the\n * rest of the selection turns by the same amount. Behaviors\n * (`opts.behaviors`) then see that node's turned pose; a pose one returns\n * from `onMove` sets the rotation for the whole selection.\n */\nexport const rotateAction: Action & { requires: string[] } = inPlane({\n id: 'rotate',\n label: 'Rotate',\n // No default binding: a bare `{ kind: 'drag' }` makes any drag no active\n // tool claimed rotate a non-empty selection. The rotation handle binds it\n // (`selectionRotateBindings`).\n eligible: { capability: 'transforms-selection' },\n modifierRoles: SNAP_ROLES,\n requires: ['selection', 'scene', 'applyOps', 'poseDescriptor', 'view', 'boundsIncludeStroke', 'kitSettings', 'snap', 'transformFollowers'],\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle {\n const begun = beginRotate(ctx, opts);\n if (!begun) return {};\n const { session, readers } = begun;\n const subject = [...(ctx.deps.selection?.get() ?? [])] as string[];\n const hold = session.scene.overrides.hold();\n const preview = transformPreview(ctx.deps, session.scene, () => frame.previews);\n let frame: RotateFrame = { delta: 0, previews: new Map() };\n let pointer = ctx.world;\n session.lifecycle.start(session.gestureCtx.draggedIds);\n return {\n kind: 'rotate',\n cursor: () => {\n const scale = ctx.deps.view?.get().scale;\n return scale ? rotateCursorAt(session.turn.pivot, pointer, scale) : undefined;\n },\n onMove(moveCtx: InvocationCtx): void {\n pointer = moveCtx.world;\n frame = nextFrame(session, readers, moveCtx);\n hold.set(preview.poses());\n },\n onEnd(_endCtx: InvocationCtx, reason: 'commit' | 'cancel'): void {\n let committed = false;\n try {\n if (reason === 'commit') {\n committed = commitRotate(session, frame);\n if (committed) {\n const pivot = session.ids.length > 1 && !session.turn.useUnionPivot ? 'each' : 'union';\n recordTransform(session.scene, { kind: 'rotate', angle: frame.delta, pivot }, subject);\n }\n } else runBehaviorCancel(session.behaviors, session.gestureCtx);\n } finally {\n session.snap.end();\n closeGesture({ hold, previews: frame.previews, behaviors: session.behaviors, ctx: session.gestureCtx, lifecycle: session.lifecycle }, committed);\n }\n },\n previewIds: preview.previewIds,\n previewData: preview.previewData,\n };\n },\n },\n enabled: () => true,\n}, selectionLayer);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAa,eAAgD,QAAQ;CACnE,IAAI;CACJ,OAAO;CAIP,UAAU,EAAE,YAAY,uBAAuB;CAC/C,eAAe;CACf,UAAU;EAAC;EAAa;EAAS;EAAY;EAAkB;EAAQ;EAAuB;EAAe;EAAQ;CAAoB;CACzI,SAAS;EACP,QAAQ;EACR,MAAM,KAAoB,MAAmC;GAC3D,MAAM,QAAQ,YAAY,KAAK,IAAI;GACnC,IAAI,CAAC,OAAO,OAAO,CAAC;GACpB,MAAM,EAAE,SAAS,YAAY;GAC7B,MAAM,UAAU,CAAC,GAAI,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,CAAE;GACrD,MAAM,OAAO,QAAQ,MAAM,UAAU,KAAK;GAC1C,MAAM,UAAU,iBAAiB,IAAI,MAAM,QAAQ,aAAa,MAAM,QAAQ;GAC9E,IAAI,QAAqB;IAAE,OAAO;IAAG,0BAAU,IAAI,IAAI;GAAE;GACzD,IAAI,UAAU,IAAI;GAClB,QAAQ,UAAU,MAAM,QAAQ,WAAW,UAAU;GACrD,OAAO;IACL,MAAM;IACN,cAAc;KACZ,MAAM,QAAQ,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC;KACnC,OAAO,QAAQ,eAAe,QAAQ,KAAK,OAAO,SAAS,KAAK,IAAI;IACtE;IACA,OAAO,SAA8B;KACnC,UAAU,QAAQ;KAClB,QAAQ,UAAU,SAAS,SAAS,OAAO;KAC3C,KAAK,IAAI,QAAQ,MAAM,CAAC;IAC1B;IACA,MAAM,SAAwB,QAAmC;KAC/D,IAAI,YAAY;KAChB,IAAI;MACF,IAAI,WAAW,UAAU;OACvB,YAAY,aAAa,SAAS,KAAK;OACvC,IAAI,WAAW;QACb,MAAM,QAAQ,QAAQ,IAAI,SAAS,KAAK,CAAC,QAAQ,KAAK,gBAAgB,SAAS;QAC/E,gBAAgB,QAAQ,OAAO;SAAE,MAAM;SAAU,OAAO,MAAM;SAAO;QAAM,GAAG,OAAO;OACvF;MACF,OAAO,kBAAkB,QAAQ,WAAW,QAAQ,UAAU;KAChE,UAAU;MACR,QAAQ,KAAK,IAAI;MACjB,aAAa;OAAE;OAAM,UAAU,MAAM;OAAU,WAAW,QAAQ;OAAW,KAAK,QAAQ;OAAY,WAAW,QAAQ;MAAU,GAAG,SAAS;KACjJ;IACF;IACA,YAAY,QAAQ;IACpB,aAAa,QAAQ;GACvB;EACF;CACF;CACA,eAAe;AACjB,GAAG,cAAc"}
@@ -1,6 +1,6 @@
1
+ import { defaultCommitAdapter } from "../defaultCommitAdapter.js";
1
2
  import { commitGestureOps, reduceBehaviorEnd } from "../gestureLifecycle.js";
2
3
  import { createTransformOp } from "../../../core/ops/transform.js";
3
- import { defaultCommitAdapter } from "../defaultCommitAdapter.js";
4
4
  import { syncGestureCurrent } from "./frame.js";
5
5
 
6
6
  //#region src/interactions/actions/rotate/commit.ts
@@ -29,6 +29,7 @@ function commitRotate(session, frame) {
29
29
  const target = {
30
30
  scene: session.scene,
31
31
  applyOps: session.applyOps,
32
+ finish: session.finish,
32
33
  adapter: defaultCommitAdapter(session.scene, session.selectionMethods)
33
34
  };
34
35
  if (session.behaviors.length > 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"commit.js","names":[],"sources":["../../../../src/interactions/actions/rotate/commit.ts"],"sourcesContent":["import type { Op } from 'core/ops/types';\nimport { createTransformOp } from 'core/ops/transform';\nimport type { NodeId } from 'core/scene/types';\nimport { defaultCommitAdapter } from '../defaultCommitAdapter';\nimport { commitGestureOps, reduceBehaviorEnd } from '../gestureLifecycle';\nimport { syncGestureCurrent } from './frame';\nimport type { RotateFrame, RotateSession } from './session';\n\n/** One transform op per rotated node: `from` the pose stored at press, `to`\n * its preview. */\nexport function rotateOps(\n ids: readonly NodeId[],\n originPoses: ReadonlyMap<NodeId, unknown>,\n previews: ReadonlyMap<NodeId, unknown>,\n label: string,\n): Op[] {\n const ops: Op[] = [];\n for (const id of ids) {\n const next = previews.get(id);\n if (next === undefined) continue;\n const from = originPoses.get(id);\n if (from === undefined) continue;\n ops.push(createTransformOp<unknown>({ id: id as string, from, to: next, label }));\n }\n return ops;\n}\n\n/** Commit the drag as one batch, so one undo entry. Returns whether anything\n * reached the document. */\nexport function commitRotate(session: RotateSession, frame: RotateFrame): boolean {\n const { lifecycle } = session;\n const target = {\n scene: session.scene,\n applyOps: session.applyOps,\n adapter: defaultCommitAdapter(session.scene, session.selectionMethods),\n };\n if (session.behaviors.length > 0) {\n syncGestureCurrent(session.gestureCtx, session.turn, frame.delta);\n const r = reduceBehaviorEnd(session.behaviors, session.gestureCtx);\n if (r === null) return false;\n if (r !== undefined) return commitGestureOps(target, lifecycle, r);\n }\n if (frame.delta === 0) return false;\n const ops = rotateOps(session.ids, session.originPoses, frame.previews, lifecycle.label);\n if (ops.length === 0) return false;\n return commitGestureOps(target, lifecycle, ops);\n}\n"],"mappings":";;;;;;;;AAUA,SAAgB,UACd,KACA,aACA,UACA,OACM;CACN,MAAM,MAAY,CAAC;CACnB,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,OAAO,SAAS,IAAI,EAAE;EAC5B,IAAI,SAAS,QAAW;EACxB,MAAM,OAAO,YAAY,IAAI,EAAE;EAC/B,IAAI,SAAS,QAAW;EACxB,IAAI,KAAK,kBAA2B;GAAM;GAAc;GAAM,IAAI;GAAM;EAAM,CAAC,CAAC;CAClF;CACA,OAAO;AACT;;;AAIA,SAAgB,aAAa,SAAwB,OAA6B;CAChF,MAAM,EAAE,cAAc;CACtB,MAAM,SAAS;EACb,OAAO,QAAQ;EACf,UAAU,QAAQ;EAClB,SAAS,qBAAqB,QAAQ,OAAO,QAAQ,gBAAgB;CACvE;CACA,IAAI,QAAQ,UAAU,SAAS,GAAG;EAChC,mBAAmB,QAAQ,YAAY,QAAQ,MAAM,MAAM,KAAK;EAChE,MAAM,IAAI,kBAAkB,QAAQ,WAAW,QAAQ,UAAU;EACjE,IAAI,MAAM,MAAM,OAAO;EACvB,IAAI,MAAM,QAAW,OAAO,iBAAiB,QAAQ,WAAW,CAAC;CACnE;CACA,IAAI,MAAM,UAAU,GAAG,OAAO;CAC9B,MAAM,MAAM,UAAU,QAAQ,KAAK,QAAQ,aAAa,MAAM,UAAU,UAAU,KAAK;CACvF,IAAI,IAAI,WAAW,GAAG,OAAO;CAC7B,OAAO,iBAAiB,QAAQ,WAAW,GAAG;AAChD"}
1
+ {"version":3,"file":"commit.js","names":[],"sources":["../../../../src/interactions/actions/rotate/commit.ts"],"sourcesContent":["import type { Op } from 'core/ops/types';\nimport { createTransformOp } from 'core/ops/transform';\nimport type { NodeId } from 'core/scene/types';\nimport { defaultCommitAdapter } from '../defaultCommitAdapter';\nimport { commitGestureOps, reduceBehaviorEnd } from '../gestureLifecycle';\nimport { syncGestureCurrent } from './frame';\nimport type { RotateFrame, RotateSession } from './session';\n\n/** One transform op per rotated node: `from` the pose stored at press, `to`\n * its preview. */\nexport function rotateOps(\n ids: readonly NodeId[],\n originPoses: ReadonlyMap<NodeId, unknown>,\n previews: ReadonlyMap<NodeId, unknown>,\n label: string,\n): Op[] {\n const ops: Op[] = [];\n for (const id of ids) {\n const next = previews.get(id);\n if (next === undefined) continue;\n const from = originPoses.get(id);\n if (from === undefined) continue;\n ops.push(createTransformOp<unknown>({ id: id as string, from, to: next, label }));\n }\n return ops;\n}\n\n/** Commit the drag as one batch, so one undo entry. Returns whether anything\n * reached the document. */\nexport function commitRotate(session: RotateSession, frame: RotateFrame): boolean {\n const { lifecycle } = session;\n const target = {\n scene: session.scene,\n applyOps: session.applyOps,\n finish: session.finish,\n adapter: defaultCommitAdapter(session.scene, session.selectionMethods),\n };\n if (session.behaviors.length > 0) {\n syncGestureCurrent(session.gestureCtx, session.turn, frame.delta);\n const r = reduceBehaviorEnd(session.behaviors, session.gestureCtx);\n if (r === null) return false;\n if (r !== undefined) return commitGestureOps(target, lifecycle, r);\n }\n if (frame.delta === 0) return false;\n const ops = rotateOps(session.ids, session.originPoses, frame.previews, lifecycle.label);\n if (ops.length === 0) return false;\n return commitGestureOps(target, lifecycle, ops);\n}\n"],"mappings":";;;;;;;;AAUA,SAAgB,UACd,KACA,aACA,UACA,OACM;CACN,MAAM,MAAY,CAAC;CACnB,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,OAAO,SAAS,IAAI,EAAE;EAC5B,IAAI,SAAS,QAAW;EACxB,MAAM,OAAO,YAAY,IAAI,EAAE;EAC/B,IAAI,SAAS,QAAW;EACxB,IAAI,KAAK,kBAA2B;GAAM;GAAc;GAAM,IAAI;GAAM;EAAM,CAAC,CAAC;CAClF;CACA,OAAO;AACT;;;AAIA,SAAgB,aAAa,SAAwB,OAA6B;CAChF,MAAM,EAAE,cAAc;CACtB,MAAM,SAAS;EACb,OAAO,QAAQ;EACf,UAAU,QAAQ;EAClB,QAAQ,QAAQ;EAChB,SAAS,qBAAqB,QAAQ,OAAO,QAAQ,gBAAgB;CACvE;CACA,IAAI,QAAQ,UAAU,SAAS,GAAG;EAChC,mBAAmB,QAAQ,YAAY,QAAQ,MAAM,MAAM,KAAK;EAChE,MAAM,IAAI,kBAAkB,QAAQ,WAAW,QAAQ,UAAU;EACjE,IAAI,MAAM,MAAM,OAAO;EACvB,IAAI,MAAM,QAAW,OAAO,iBAAiB,QAAQ,WAAW,CAAC;CACnE;CACA,IAAI,MAAM,UAAU,GAAG,OAAO;CAC9B,MAAM,MAAM,UAAU,QAAQ,KAAK,QAAQ,aAAa,MAAM,UAAU,UAAU,KAAK;CACvF,IAAI,IAAI,WAAW,GAAG,OAAO;CAC7B,OAAO,iBAAiB,QAAQ,WAAW,GAAG;AAChD"}
@@ -1,6 +1,7 @@
1
1
  import { unionAABB } from "../../../core/geometry/unionBounds.js";
2
2
  import { scenePoseFrame } from "../poseFrame.js";
3
3
  import { nodeDescriptors, poseDescriptorOf } from "../poseDescriptorDep.js";
4
+ import { transformFinisher } from "../transformFinish.js";
4
5
  import { readGestureLifecycle } from "../gestureLifecycle.js";
5
6
  import { carryPose, invertPlane, planeOf } from "../planeInput.js";
6
7
  import { gestureReaders } from "../../gestures/shared/screenTolerance.js";
@@ -129,6 +130,7 @@ function beginRotate(ctx, opts) {
129
130
  behaviors,
130
131
  gestureCtx,
131
132
  applyOps: ctx.deps.applyOps,
133
+ finish: transformFinisher(ctx.deps),
132
134
  selectionMethods: selection.adapterMethods,
133
135
  lifecycle: readGestureLifecycle(params, "Rotate", behaviors)
134
136
  },