@weasel-js/core 1.11.0 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/CHANGELOG.md +427 -0
  2. package/README.md +1 -1
  3. package/dist/animation/index.js +3 -3
  4. package/dist/animation/rig/bindRig.js +1 -1
  5. package/dist/animation/rig/rigNode.js.map +1 -1
  6. package/dist/barrel/canvas.js +7 -3
  7. package/dist/barrel/interactions.js +5 -4
  8. package/dist/barrel/paths.js +3 -3
  9. package/dist/barrel/renderer.js +3 -2
  10. package/dist/barrel/routing.js +6 -11
  11. package/dist/barrel/scene.js +8 -5
  12. package/dist/barrel/viewport.js +4 -3
  13. package/dist/canvas/Canvas.js +3 -1
  14. package/dist/canvas/Canvas.js.map +1 -1
  15. package/dist/canvas/SceneCanvas/SceneCanvasInput.js +17 -3
  16. package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
  17. package/dist/canvas/SceneCanvas/builtinToolSets.js +30 -0
  18. package/dist/canvas/SceneCanvas/builtinToolSets.js.map +1 -0
  19. package/dist/canvas/SceneCanvas/canvasPassThrough.js +1 -0
  20. package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
  21. package/dist/canvas/SceneCanvas/coordTrace.js +5 -16
  22. package/dist/canvas/SceneCanvas/coordTrace.js.map +1 -1
  23. package/dist/canvas/SceneCanvas/defaultNodeProperties.js +5 -0
  24. package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
  25. package/dist/canvas/SceneCanvas/features.js +45 -5
  26. package/dist/canvas/SceneCanvas/features.js.map +1 -1
  27. package/dist/canvas/SceneCanvas/props.js +1 -11
  28. package/dist/canvas/SceneCanvas/props.js.map +1 -1
  29. package/dist/canvas/SceneCanvas/toolRegistry.js +8 -4
  30. package/dist/canvas/SceneCanvas/toolRegistry.js.map +1 -1
  31. package/dist/canvas/SceneCanvas/useActionsPropResolver.js +1 -11
  32. package/dist/canvas/SceneCanvas/useActionsPropResolver.js.map +1 -1
  33. package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js.map +1 -1
  34. package/dist/canvas/SceneCanvas/useBuiltinTools.js +2 -9
  35. package/dist/canvas/SceneCanvas/useBuiltinTools.js.map +1 -1
  36. package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js +93 -0
  37. package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js.map +1 -0
  38. package/dist/canvas/SceneCanvas/useCanvasSelection.js +7 -2
  39. package/dist/canvas/SceneCanvas/useCanvasSelection.js.map +1 -1
  40. package/dist/canvas/SceneCanvas/useCanvasTools.js +8 -8
  41. package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
  42. package/dist/canvas/SceneCanvas/useNodeKinds.js +1 -1
  43. package/dist/canvas/SceneCanvas/useNodeKinds.js.map +1 -1
  44. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +13 -2
  45. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
  46. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +1 -3
  47. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
  48. package/dist/canvas/SceneCanvas/useSceneChrome.js +6 -7
  49. package/dist/canvas/SceneCanvas/useSceneChrome.js.map +1 -1
  50. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js +5 -3
  51. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js.map +1 -1
  52. package/dist/canvas/SceneCanvas/useScenePicking.js +2 -3
  53. package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
  54. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +2 -1
  55. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  56. package/dist/canvas/SceneCanvas/useSceneStandardActions.js +3 -4
  57. package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
  58. package/dist/canvas/SceneCanvas.js +16 -14
  59. package/dist/canvas/SceneCanvas.js.map +1 -1
  60. package/dist/canvas/SurfaceCanvas.js +5 -22
  61. package/dist/canvas/SurfaceCanvas.js.map +1 -1
  62. package/dist/canvas/canvasRenderer.js +9 -5
  63. package/dist/canvas/canvasRenderer.js.map +1 -1
  64. package/dist/canvas/contextAttributes.js +14 -0
  65. package/dist/canvas/contextAttributes.js.map +1 -0
  66. package/dist/canvas/deps/editAnchors.js +9 -113
  67. package/dist/canvas/deps/editAnchors.js.map +1 -1
  68. package/dist/canvas/deps/insert.js +4 -2
  69. package/dist/canvas/deps/insert.js.map +1 -1
  70. package/dist/canvas/deps/pathEditCommits.js +193 -0
  71. package/dist/canvas/deps/pathEditCommits.js.map +1 -0
  72. package/dist/canvas/deps/snap.js +12 -9
  73. package/dist/canvas/deps/snap.js.map +1 -1
  74. package/dist/canvas/deps/transformFollowers.js +20 -0
  75. package/dist/canvas/deps/transformFollowers.js.map +1 -0
  76. package/dist/canvas/derivedPath.js +20 -1
  77. package/dist/canvas/derivedPath.js.map +1 -1
  78. package/dist/canvas/drawnBounds.js +29 -3
  79. package/dist/canvas/drawnBounds.js.map +1 -1
  80. package/dist/canvas/nodeShapes/derivedPainter.js +9 -1
  81. package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -1
  82. package/dist/canvas/nodeShapes/pathPainter.js +3 -2
  83. package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
  84. package/dist/canvas/nodeShapes/shapePainter.js +1 -1
  85. package/dist/canvas/overlayRoleStrokes.js +12 -0
  86. package/dist/canvas/overlayRoleStrokes.js.map +1 -1
  87. package/dist/canvas/pathOverlay.js +27 -7
  88. package/dist/canvas/pathOverlay.js.map +1 -1
  89. package/dist/canvas/renderDebugSnapshot.js +1 -0
  90. package/dist/canvas/renderDebugSnapshot.js.map +1 -1
  91. package/dist/canvas/renderSceneToPixels.js +6 -5
  92. package/dist/canvas/renderSceneToPixels.js.map +1 -1
  93. package/dist/canvas/sceneAdapter.js.map +1 -1
  94. package/dist/canvas/useCanvasPaint.js +2 -1
  95. package/dist/canvas/useCanvasPaint.js.map +1 -1
  96. package/dist/canvas/useInFlightBounds.js +52 -0
  97. package/dist/canvas/useInFlightBounds.js.map +1 -0
  98. package/dist/canvas/useViewHelpers.js +7 -9
  99. package/dist/canvas/useViewHelpers.js.map +1 -1
  100. package/dist/clipboard.d.ts +3 -2
  101. package/dist/{clone-DZDrfIlU.d.ts → clone-C3e0OTuF.d.ts} +8 -21
  102. package/dist/clone.d.ts +4 -3
  103. package/dist/core/geometry/autoPoseDescriptor.js +1 -1
  104. package/dist/core/ops/setDependsOn.js +37 -0
  105. package/dist/core/ops/setDependsOn.js.map +1 -0
  106. package/dist/core/ops/writtenBy.js +53 -0
  107. package/dist/core/ops/writtenBy.js.map +1 -0
  108. package/dist/core/pages/fitViewToPage.js +12 -1
  109. package/dist/core/pages/fitViewToPage.js.map +1 -1
  110. package/dist/core/pages/pageEdits.js +164 -0
  111. package/dist/core/pages/pageEdits.js.map +1 -0
  112. package/dist/core/pages/pages.js +30 -1
  113. package/dist/core/pages/pages.js.map +1 -1
  114. package/dist/core/scene/NodeRouting.js +8 -3
  115. package/dist/core/scene/NodeRouting.js.map +1 -1
  116. package/dist/core/scene/arrivalWindow.js.map +1 -1
  117. package/dist/core/scene/carryDescendants.js +51 -0
  118. package/dist/core/scene/carryDescendants.js.map +1 -0
  119. package/dist/core/scene/docValues.js +57 -0
  120. package/dist/core/scene/docValues.js.map +1 -0
  121. package/dist/core/scene/editPass.js +111 -0
  122. package/dist/core/scene/editPass.js.map +1 -0
  123. package/dist/core/scene/effectivePose.js +7 -1
  124. package/dist/core/scene/effectivePose.js.map +1 -1
  125. package/dist/core/scene/fieldEdits.js +4 -1
  126. package/dist/core/scene/fieldEdits.js.map +1 -1
  127. package/dist/core/scene/historyFacade.js +1 -0
  128. package/dist/core/scene/historyFacade.js.map +1 -1
  129. package/dist/core/scene/layoutPass.js +28 -3
  130. package/dist/core/scene/layoutPass.js.map +1 -1
  131. package/dist/core/scene/recorder.js +28 -19
  132. package/dist/core/scene/recorder.js.map +1 -1
  133. package/dist/core/scene/scene.js +15 -9
  134. package/dist/core/scene/scene.js.map +1 -1
  135. package/dist/core/scene/sceneChanges.js +13 -0
  136. package/dist/core/scene/sceneChanges.js.map +1 -0
  137. package/dist/core/scene/sceneEdits.js +8 -1
  138. package/dist/core/scene/sceneEdits.js.map +1 -1
  139. package/dist/core/scene/sceneMutators.js +1 -0
  140. package/dist/core/scene/sceneMutators.js.map +1 -1
  141. package/dist/core/scene/sceneRecording.js +20 -3
  142. package/dist/core/scene/sceneRecording.js.map +1 -1
  143. package/dist/core/scene/sceneSelection.js +4 -1
  144. package/dist/core/scene/sceneSelection.js.map +1 -1
  145. package/dist/core/scene/sceneState.js +11 -5
  146. package/dist/core/scene/sceneState.js.map +1 -1
  147. package/dist/core/scene/serialization.js +5 -3
  148. package/dist/core/scene/serialization.js.map +1 -1
  149. package/dist/core/scene/types.js.map +1 -1
  150. package/dist/core/scene/unitScopes.js +19 -1
  151. package/dist/core/scene/unitScopes.js.map +1 -1
  152. package/dist/core/selection/usePublishSceneSelection.js +6 -12
  153. package/dist/core/selection/usePublishSceneSelection.js.map +1 -1
  154. package/dist/core/shapeKinds.js +25 -11
  155. package/dist/core/shapeKinds.js.map +1 -1
  156. package/dist/{cornerHandles-Camw_yeu.d.ts → cornerHandles-BHuoMs1o.d.ts} +1 -1
  157. package/dist/debug/createDebugOverlayLayer.js +16 -111
  158. package/dist/debug/createDebugOverlayLayer.js.map +1 -1
  159. package/dist/debug/createDebugSink.js +2 -1
  160. package/dist/debug/createDebugSink.js.map +1 -1
  161. package/dist/debug/defaultTheme.js +4 -2
  162. package/dist/debug/defaultTheme.js.map +1 -1
  163. package/dist/debug/features.js +17 -0
  164. package/dist/debug/features.js.map +1 -0
  165. package/dist/debug/overlayPanels.js +117 -0
  166. package/dist/debug/overlayPanels.js.map +1 -0
  167. package/dist/debug/parseDebugFlags.js +4 -13
  168. package/dist/debug/parseDebugFlags.js.map +1 -1
  169. package/dist/{depSchema-Cgfu_G9s.d.ts → depSchema-BpZNOv7q.d.ts} +114 -892
  170. package/dist/devtools/handlers.js +112 -0
  171. package/dist/devtools/handlers.js.map +1 -0
  172. package/dist/devtools/history.js +71 -0
  173. package/dist/devtools/history.js.map +1 -0
  174. package/dist/devtools/hook.js +74 -0
  175. package/dist/devtools/hook.js.map +1 -0
  176. package/dist/devtools/label.js +25 -0
  177. package/dist/devtools/label.js.map +1 -0
  178. package/dist/devtools/notices.js +58 -0
  179. package/dist/devtools/notices.js.map +1 -0
  180. package/dist/devtools/protocol.js +7 -0
  181. package/dist/devtools/protocol.js.map +1 -0
  182. package/dist/devtools/registry.js +90 -0
  183. package/dist/devtools/registry.js.map +1 -0
  184. package/dist/devtools/root.js +80 -0
  185. package/dist/devtools/root.js.map +1 -0
  186. package/dist/devtools/serialize.js +86 -0
  187. package/dist/devtools/serialize.js.map +1 -0
  188. package/dist/devtools/session.js +111 -0
  189. package/dist/devtools/session.js.map +1 -0
  190. package/dist/devtools/transport.js +67 -0
  191. package/dist/devtools/transport.js.map +1 -0
  192. package/dist/devtools/useDevtoolsRoot.js +81 -0
  193. package/dist/devtools/useDevtoolsRoot.js.map +1 -0
  194. package/dist/devtools-protocol.d.ts +435 -0
  195. package/dist/devtools-protocol.js +7 -0
  196. package/dist/devtools.d.ts +8 -0
  197. package/dist/devtools.js +12 -0
  198. package/dist/devtools.js.map +1 -0
  199. package/dist/features/chrome-caps/useHoverTracking.js +16 -10
  200. package/dist/features/chrome-caps/useHoverTracking.js.map +1 -1
  201. package/dist/features/ingestion/contentHandlers.js +25 -2
  202. package/dist/features/ingestion/contentHandlers.js.map +1 -1
  203. package/dist/features/measure/measureCommands.js +7 -5
  204. package/dist/features/measure/measureCommands.js.map +1 -1
  205. package/dist/features/paths/index.js +2 -2
  206. package/dist/features/paths/pathEditExtension.js.map +1 -1
  207. package/dist/features/paths/pathSnap/pathSnapSource.js +5 -5
  208. package/dist/features/paths/pathSnap/pathSnapSource.js.map +1 -1
  209. package/dist/features/paths/pathSnap/sceneIndex.js +10 -3
  210. package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
  211. package/dist/features/selection/selectionPose.js +4 -1
  212. package/dist/features/selection/selectionPose.js.map +1 -1
  213. package/dist/features/snap/gather.js +9 -1
  214. package/dist/features/snap/gather.js.map +1 -1
  215. package/dist/features/snap/lazySession.js +17 -0
  216. package/dist/features/snap/lazySession.js.map +1 -0
  217. package/dist/features/snap/marks.js.map +1 -1
  218. package/dist/features/snap/probes2d.js +27 -19
  219. package/dist/features/snap/probes2d.js.map +1 -1
  220. package/dist/features/snap/readoutLayer.js.map +1 -1
  221. package/dist/features/snap/roles.js +2 -1
  222. package/dist/features/snap/roles.js.map +1 -1
  223. package/dist/features/snap/rotationSource.js +25 -8
  224. package/dist/features/snap/rotationSource.js.map +1 -1
  225. package/dist/features/snap/sceneSession2d.js +6 -4
  226. package/dist/features/snap/sceneSession2d.js.map +1 -1
  227. package/dist/features/snap/session2d.js +37 -23
  228. package/dist/features/snap/session2d.js.map +1 -1
  229. package/dist/features/snap/spanSnap.js +5 -2
  230. package/dist/features/snap/spanSnap.js.map +1 -1
  231. package/dist/icons.table.js +1 -1
  232. package/dist/icons.table.js.map +1 -1
  233. package/dist/icons.table.json +0 -10
  234. package/dist/index.d.ts +701 -696
  235. package/dist/index.js +35 -29
  236. package/dist/interactions/actions/align/action.js +6 -4
  237. package/dist/interactions/actions/align/action.js.map +1 -1
  238. package/dist/interactions/actions/align/align.js.map +1 -1
  239. package/dist/interactions/actions/booleans/action.js +7 -1
  240. package/dist/interactions/actions/booleans/action.js.map +1 -1
  241. package/dist/interactions/actions/clone/action.js +1 -1
  242. package/dist/interactions/actions/clone/behaviors.js +4 -4
  243. package/dist/interactions/actions/clone/behaviors.js.map +1 -1
  244. package/dist/interactions/actions/defaults/bendSegment.js +1 -1
  245. package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
  246. package/dist/interactions/actions/defaults/createPaintAction.js +1 -1
  247. package/dist/interactions/actions/defaults/delete.js +48 -39
  248. package/dist/interactions/actions/defaults/delete.js.map +1 -1
  249. package/dist/interactions/actions/defaults/duplicate.js +1 -1
  250. package/dist/interactions/actions/defaults/group.js +2 -2
  251. package/dist/interactions/actions/defaults/ingest.js +7 -5
  252. package/dist/interactions/actions/defaults/ingest.js.map +1 -1
  253. package/dist/interactions/actions/defaults/nudge.js +13 -38
  254. package/dist/interactions/actions/defaults/nudge.js.map +1 -1
  255. package/dist/interactions/actions/distribute/action.js +6 -4
  256. package/dist/interactions/actions/distribute/action.js.map +1 -1
  257. package/dist/interactions/actions/drawnBoxes.js +2 -1
  258. package/dist/interactions/actions/drawnBoxes.js.map +1 -1
  259. package/dist/interactions/actions/edit-anchors/action.js +3 -6
  260. package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
  261. package/dist/interactions/actions/edit-anchors/preview.js +41 -8
  262. package/dist/interactions/actions/edit-anchors/preview.js.map +1 -1
  263. package/dist/interactions/actions/flip/action.js +6 -4
  264. package/dist/interactions/actions/flip/action.js.map +1 -1
  265. package/dist/interactions/actions/gestureLifecycle.js +5 -2
  266. package/dist/interactions/actions/gestureLifecycle.js.map +1 -1
  267. package/dist/interactions/actions/insert/action.js +1 -2
  268. package/dist/interactions/actions/insert/action.js.map +1 -1
  269. package/dist/interactions/actions/insert/commit.js +1 -1
  270. package/dist/interactions/actions/insert/parent.js +11 -0
  271. package/dist/interactions/actions/insert/parent.js.map +1 -0
  272. package/dist/interactions/actions/insert/placeAction.js +7 -2
  273. package/dist/interactions/actions/insert/placeAction.js.map +1 -1
  274. package/dist/interactions/actions/insert/session.js +5 -1
  275. package/dist/interactions/actions/insert/session.js.map +1 -1
  276. package/dist/interactions/actions/move/action.js +8 -4
  277. package/dist/interactions/actions/move/action.js.map +1 -1
  278. package/dist/interactions/actions/move/behaviors/layoutDrop.js +0 -1
  279. package/dist/interactions/actions/move/behaviors/layoutDrop.js.map +1 -1
  280. package/dist/interactions/actions/move/behaviors/snapToContainer.js +1 -1
  281. package/dist/interactions/actions/move/capture.js +1 -1
  282. package/dist/interactions/actions/move/commit/index.js +0 -2
  283. package/dist/interactions/actions/move/commit/index.js.map +1 -1
  284. package/dist/interactions/actions/move/commit/layoutDrop.js +2 -2
  285. package/dist/interactions/actions/move/commit/layoutRelease.js +2 -2
  286. package/dist/interactions/actions/move/commit/reparent.js +11 -5
  287. package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
  288. package/dist/interactions/actions/move/frame.js +4 -10
  289. package/dist/interactions/actions/move/frame.js.map +1 -1
  290. package/dist/interactions/actions/move/landedDescendants.js +20 -17
  291. package/dist/interactions/actions/move/landedDescendants.js.map +1 -1
  292. package/dist/interactions/actions/move/layout/container.js +2 -1
  293. package/dist/interactions/actions/move/layout/container.js.map +1 -1
  294. package/dist/interactions/actions/move/layout/dragged.js +1 -1
  295. package/dist/interactions/actions/move/layout/dropPass.js +18 -1
  296. package/dist/interactions/actions/move/layout/dropPass.js.map +1 -1
  297. package/dist/interactions/actions/move/layout/exits.js +5 -5
  298. package/dist/interactions/actions/move/layout/exits.js.map +1 -1
  299. package/dist/interactions/actions/move/session.js +14 -3
  300. package/dist/interactions/actions/move/session.js.map +1 -1
  301. package/dist/interactions/actions/move/translate.js +8 -37
  302. package/dist/interactions/actions/move/translate.js.map +1 -1
  303. package/dist/interactions/actions/moveNodes.js +87 -0
  304. package/dist/interactions/actions/moveNodes.js.map +1 -0
  305. package/dist/interactions/actions/planeInput.js.map +1 -1
  306. package/dist/interactions/actions/resize/action.js +83 -72
  307. package/dist/interactions/actions/resize/action.js.map +1 -1
  308. package/dist/interactions/actions/resize/commit.js +2 -1
  309. package/dist/interactions/actions/resize/commit.js.map +1 -1
  310. package/dist/interactions/actions/resize/session.js +2 -0
  311. package/dist/interactions/actions/resize/session.js.map +1 -1
  312. package/dist/interactions/actions/resolvePreviews.js +3 -3
  313. package/dist/interactions/actions/resolvePreviews.js.map +1 -1
  314. package/dist/interactions/actions/rotate/action.js +7 -4
  315. package/dist/interactions/actions/rotate/action.js.map +1 -1
  316. package/dist/interactions/actions/rotate/commit.js +2 -1
  317. package/dist/interactions/actions/rotate/commit.js.map +1 -1
  318. package/dist/interactions/actions/rotate/session.js +2 -0
  319. package/dist/interactions/actions/rotate/session.js.map +1 -1
  320. package/dist/interactions/actions/selectionSubtree.js +13 -2
  321. package/dist/interactions/actions/selectionSubtree.js.map +1 -1
  322. package/dist/interactions/actions/testUtils.js.map +1 -1
  323. package/dist/interactions/actions/transform-again/action.js +5 -6
  324. package/dist/interactions/actions/transform-again/action.js.map +1 -1
  325. package/dist/interactions/actions/transformFinish.js +106 -0
  326. package/dist/interactions/actions/transformFinish.js.map +1 -0
  327. package/dist/kitSettingsSchema.js +0 -4
  328. package/dist/kitSettingsSchema.js.map +1 -1
  329. package/dist/move.d.ts +5 -4
  330. package/dist/{options-p9GZMXbY.d.ts → options-DBxEryYq.d.ts} +1 -1
  331. package/dist/resize.d.ts +4 -3
  332. package/dist/routing.d.ts +1 -1
  333. package/dist/routing.js +2 -2
  334. package/dist/test-hook/createTestHook.js +1 -1
  335. package/dist/test-hook/createTestHook.js.map +1 -1
  336. package/dist/test-hook/useTestHook.js +2 -1
  337. package/dist/test-hook/useTestHook.js.map +1 -1
  338. package/dist/test-seams.d.ts +9 -5
  339. package/dist/tools/builtin/pen/penHandle.js +3 -5
  340. package/dist/tools/builtin/pen/penHandle.js.map +1 -1
  341. package/dist/tools/index.js +26 -0
  342. package/dist/{types-CHVXGfYm.d.ts → types-7puAuVD2.d.ts} +3 -262
  343. package/dist/types-BN9hbdMI.d.ts +184 -0
  344. package/dist/types-CniVctmk.d.ts +949 -0
  345. package/dist/types-oFeQ5VjV.d.ts +264 -0
  346. package/dist/version.js +1 -1
  347. package/package.json +35 -25
  348. package/dist/canvas/SceneCanvas/useBuiltinRigTools.js +0 -65
  349. package/dist/canvas/SceneCanvas/useBuiltinRigTools.js.map +0 -1
  350. package/dist/tools/builtin/bone/boneActions.js +0 -156
  351. package/dist/tools/builtin/bone/boneActions.js.map +0 -1
  352. package/dist/tools/builtin/bone/boneOverlay.js +0 -71
  353. package/dist/tools/builtin/bone/boneOverlay.js.map +0 -1
  354. package/dist/tools/builtin/bone/placedRig.js +0 -28
  355. package/dist/tools/builtin/bone/placedRig.js.map +0 -1
  356. package/dist/tools/builtin/bone/rigEditState.js +0 -30
  357. package/dist/tools/builtin/bone/rigEditState.js.map +0 -1
  358. package/dist/tools/builtin/bone/useBoneTool.js +0 -125
  359. package/dist/tools/builtin/bone/useBoneTool.js.map +0 -1
  360. package/dist/tools/builtin/weight/useWeightTool.js +0 -101
  361. package/dist/tools/builtin/weight/useWeightTool.js.map +0 -1
  362. package/dist/tools/builtin/weight/weightActions.js +0 -140
  363. package/dist/tools/builtin/weight/weightActions.js.map +0 -1
  364. package/dist/tools/builtin/weight/weightOverlay.js +0 -78
  365. package/dist/tools/builtin/weight/weightOverlay.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"planeInput.js","names":[],"sources":["../../../src/interactions/actions/planeInput.ts"],"sourcesContent":["/**\n * Editing a node on a parallax plane.\n *\n * The dispatcher hands every action the camera's world. A node on a layer\n * carrying `parallax` stores its pose in its plane's world, which the camera's\n * reaches through a `PlaneMap` that changes with the camera. An editing action\n * pairs the pointer with the pose it edits, so the two have to be in one world.\n *\n * `inPlane` wraps an action so its input is carried into the plane of the\n * layer it edits before the action reads it, and the chrome it publishes back\n * comes out in the camera's world. A dep that answers in world points\n * (`nodeAtPoint`, `snap`) keeps speaking the camera's world, and the seam\n * converts at its boundary.\n *\n * A selection can span planes. The edit is measured in one of them, and a\n * node on another is carried into it as the gesture starts and back out\n * through `planeOf` as it is now.\n *\n * The map is read afresh on every call: a camera panning under a held drag, or\n * a plane whose factors are being animated, moves the node with the pointer.\n */\nimport type {\n Action, ActionDeps, AffordanceHit, BindingOpts, DragSample, InvocationCtx, OngoingHandle,\n OngoingOverlay,\n} from '@weasel-js/routing';\nimport type { Scene } from 'core/scene/types';\nimport { asNodeId } from 'core/scene/types';\nimport {\n deriveParallaxView, fromPlane, planeMap, planeToPlane, rectFromPlane, rectToPlane, toPlane,\n type ParallaxOpts, type PlaneMap,\n} from 'core/viewport/parallax';\nimport type { View } from 'core/viewport/view';\nimport type { PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport type { NodeAtPointDep, SnapDep, ViewApi } from './depSchema';\nimport type { SnapBegin } from './snapDep';\n\ntype Point = { x: number; y: number };\n\n/** Which scene layer an invocation edits, read once as it starts. `undefined`\n * means none: the action runs in the camera's world. */\nexport type EditedLayerOf = (deps: ActionDeps, affordance?: AffordanceHit) => string | undefined;\n\nconst layerOfNode = (deps: ActionDeps, id: string | null | undefined): string | undefined =>\n (id ? deps.scene?.get(asNodeId(id))?.layer : undefined);\n\n/** The layer of the node a selection edit acts on: the affordance's target\n * when the drag grabbed one, the first selected node otherwise. A selection\n * spanning planes is edited in that one node's plane. */\nexport const selectionLayer: EditedLayerOf = (deps, affordance) => {\n const selection = deps.selection;\n for (const id of [...(affordance?.targetIds ?? []), ...(selection?.get() ?? [])]) {\n const layer = layerOfNode(deps, id);\n if (layer !== undefined) return layer;\n }\n return undefined;\n};\n\n/** The layer the `insert` dep will put a new node on. */\nexport const insertLayer: EditedLayerOf = (deps) =>\n deps.insert?.layer?.();\n\n/** The layer of the path in anchor-edit mode. */\nexport const editingLayer: EditedLayerOf = (deps) =>\n layerOfNode(deps, deps.editAnchors?.editingId);\n\n/** How the world an invocation edits in maps into the world `layer`'s nodes\n * are stored in, read now. Null when they are the same world. */\nexport function planeOf(view: unknown, layer: string): PlaneMap | null {\n return (view as ViewApi | undefined)?.planeOf?.(layer) ?? null;\n}\n\n/** `m` run backwards; null stays null. */\nexport function invertPlane(m: PlaneMap | null): PlaneMap | null {\n return m && planeToPlane(m, null);\n}\n\n/** `pose` carried through `m`. The map is axis-aligned, so remapping the\n * pose's bounds carries it exactly when the map scales both axes alike. */\nexport function carryPose<P>(d: PoseDescriptor<P>, pose: P, m: PlaneMap | null): P {\n if (m === null) return pose;\n const b = d.getBounds(pose);\n return d.remapBounds(pose, b, rectToPlane(m, b));\n}\n\n/** `action`, with its input carried into the plane of the layer `layerOf`\n * names. An action in a scene with no `parallax` layer, or invoked with no\n * scene or view to read a plane from, runs exactly as it would unwrapped.\n *\n * An immediate action's world point is `params.worldX` / `params.worldY`,\n * the dispatcher's convention for a click; it is carried when present. */\nexport function inPlane<A extends Action>(action: A, layerOf: EditedLayerOf): A {\n const inner = action.invoker;\n if (!inner) return action;\n const declared = action.requires ?? [];\n const requires = [...new Set([...declared, 'scene', 'selection', 'view'])];\n if (inner.timing === 'immediate') {\n return {\n ...action,\n requires,\n invoker: {\n timing: 'immediate',\n run(deps: ActionDeps, params?: Record<string, unknown>): void {\n const plane = livePlane(deps, layerOf(deps), declared, null);\n if (plane === null) return inner.run(deps, params);\n const wx = params?.['worldX'];\n const wy = params?.['worldY'];\n const at = typeof wx === 'number' && typeof wy === 'number'\n ? toPlane(plane.map(), { x: wx, y: wy })\n : null;\n inner.run(plane.deps, at ? { ...params, worldX: at.x, worldY: at.y } : params);\n },\n },\n } as A;\n }\n return {\n ...action,\n requires,\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle {\n const plane = livePlane(\n ctx.deps, layerOf(ctx.deps, ctx.drag?.affordance), declared, ctx.drag?.start ?? null,\n );\n if (plane === null) return inner.start(ctx, opts);\n return planeHandle(inner.start(plane.carry(ctx), opts), plane);\n },\n },\n } as A;\n}\n\ninterface LivePlane {\n map(): PlaneMap;\n /** The invocation's deps, speaking the plane's world. */\n deps: ActionDeps;\n /** `ctx` in the plane's world. */\n carry(ctx: InvocationCtx): InvocationCtx;\n}\n\nfunction livePlane(\n bag: ActionDeps,\n layer: string | undefined,\n /** What the wrapped action declared. The dispatcher's dev deps bag throws\n * on any other read, so the seam converts only the deps it can see. */\n declared: readonly string[],\n /** The drag's press point in the camera's world, when there is a drag. */\n pressedAt: Point | null,\n): LivePlane | null {\n const scene = bag.scene as Scene<unknown, string, unknown> | undefined;\n const viewApi = bag.view as ViewApi | undefined;\n if (layer === undefined || !scene || !viewApi) return null;\n if (!scene.layers.some((l) => l.parallax !== undefined)) return null;\n const parallaxOf = (id: string): ParallaxOpts | undefined => scene.layers.find((l) => l.id === id)?.parallax;\n const parallax = (): ParallaxOpts | undefined => parallaxOf(layer);\n\n const IDENTITY: PlaneMap = { scale: { x: 1, y: 1 }, offset: { x: 0, y: 0 } };\n const map = (): PlaneMap => {\n const p = parallax();\n return p ? planeMap(viewApi.get(), p) : IDENTITY;\n };\n\n // A screen-pixel tolerance, and anything else an action reads off the\n // camera, has to be read at the plane's scale.\n const planeView = Object.defineProperties(Object.create(viewApi), {\n get: {\n value: (): View => {\n const p = parallax();\n const camera = viewApi.get();\n return p ? deriveParallaxView(camera, p) : camera;\n },\n },\n plane: { value: map },\n planeOf: {\n value: (id: string): PlaneMap | null => {\n const edited = parallax();\n const own = parallaxOf(id);\n const camera = viewApi.get();\n return planeToPlane(edited ? planeMap(camera, edited) : null, own ? planeMap(camera, own) : null);\n },\n },\n }) as ViewApi;\n\n // Over the dispatcher's bag rather than a copy of it, so every other read\n // still goes through whatever checks the bag makes.\n const deps = Object.create(bag) as typeof bag;\n const override = (name: string, value: unknown) =>\n Object.defineProperty(deps, name, { value, enumerable: true });\n override('view', planeView);\n const nodeAtPoint = declared.includes('nodeAtPoint')\n ? bag.nodeAtPoint as NodeAtPointDep | undefined\n : undefined;\n if (nodeAtPoint) {\n override('nodeAtPoint', ((p, exclude) => nodeAtPoint(fromPlane(map(), p), exclude)) as NodeAtPointDep);\n }\n const snap = declared.includes('snap') ? bag.snap as SnapDep | undefined : undefined;\n if (snap) {\n // A session runs in the plane, and carries the camera's targets into it.\n override('snap', { begin: (req: SnapBegin) => snap.begin({ ...req, plane: map }) } satisfies SnapDep);\n }\n\n // The drag's origin, fixed in the plane at the moment it was pressed,\n // whatever the camera does after.\n const start0 = pressedAt ? toPlane(map(), pressedAt) : null;\n const trail = trailCarrier();\n\n return {\n map,\n deps,\n carry(c) {\n const m = map();\n const out: InvocationCtx = { ...c, world: toPlane(m, c.world) };\n if (c.drag) {\n const start = start0 ?? toPlane(m, c.drag.start);\n const current = toPlane(m, c.drag.current);\n const aff = c.drag.affordance;\n out.drag = {\n ...c.drag,\n start,\n current,\n delta: { x: current.x - start.x, y: current.y - start.y },\n ...(c.drag.points ? { points: trail(c.drag.points, m) } : {}),\n ...(aff?.fixedPoint ? { affordance: { ...aff, fixedPoint: toPlane(m, aff.fixedPoint) } } : {}),\n };\n }\n // The dispatcher hands `onMove` no deps; only `start` has any to carry.\n if (c.deps) out.deps = deps;\n return out;\n },\n };\n}\n\n/** The drag trail in the plane. The dispatcher grows its trail in place, so\n * only the samples added since the last call are mapped — unless the map\n * moved, when every sample is. */\nfunction trailCarrier(): (points: DragSample[], m: PlaneMap) => DragSample[] {\n let src: DragSample[] | null = null;\n let lastMap: PlaneMap | null = null;\n let out: DragSample[] = [];\n return (points, m) => {\n const same = lastMap !== null\n && lastMap.scale.x === m.scale.x && lastMap.scale.y === m.scale.y\n && lastMap.offset.x === m.offset.x && lastMap.offset.y === m.offset.y;\n if (points !== src || !same || points.length < out.length) {\n src = points;\n out = [];\n }\n lastMap = m;\n for (let i = out.length; i < points.length; i++) {\n out.push({ ...points[i], ...toPlane(m, points[i]) });\n }\n // A copy, so a snapshot taken on `onEnd` is not the live accumulator.\n return out.slice();\n };\n}\n\nfunction planeHandle(handle: OngoingHandle, plane: LivePlane): OngoingHandle {\n const out: OngoingHandle = { ...handle };\n if (handle.onMove) out.onMove = (c) => handle.onMove!(plane.carry(c));\n if (handle.onEnd) out.onEnd = (c, reason) => handle.onEnd!(plane.carry(c), reason);\n if (handle.onWheel) out.onWheel = (c) => handle.onWheel!(plane.carry(c));\n if (handle.overlay) {\n out.overlay = () => {\n const ov = handle.overlay!();\n if (!ov) return ov;\n const m = plane.map();\n return Array.isArray(ov)\n ? (ov as readonly OngoingOverlay[]).map((o) => overlayFromPlane(o, m))\n : overlayFromPlane(ov as OngoingOverlay, m);\n };\n }\n return out;\n}\n\n/** An overlay drawn in the plane, in the camera's world: what every painter\n * reads an overlay as. */\nfunction overlayFromPlane(ov: OngoingOverlay, m: PlaneMap): OngoingOverlay {\n const pt = (p: Point) => fromPlane(m, p);\n switch (ov.kind) {\n case 'marquee':\n return { ...ov, start: pt(ov.start), current: pt(ov.current) };\n case 'lasso':\n return { ...ov, vertices: ov.vertices.map(pt), current: pt(ov.current) };\n case 'polyline':\n case 'brush':\n return { ...ov, points: ov.points.map(pt) };\n case 'insertPreview':\n return {\n ...ov,\n bounds: rectFromPlane(m, ov.bounds),\n extras: insertExtrasFromPlane(ov.extras, m),\n ...(ov.anchorPoint ? { anchorPoint: pt(ov.anchorPoint) } : {}),\n };\n }\n}\n\n/** The geometry the kit's insert kinds carry in `extras`, in the camera's\n * world. A consumer kind's extras are opaque, and pass through. */\nfunction insertExtrasFromPlane(extras: unknown, m: PlaneMap): unknown {\n if (extras === null || typeof extras !== 'object') return extras;\n const e = extras as Record<string, unknown>;\n const pt = (p: unknown) => fromPlane(m, p as Point);\n // Radial kinds inscribe in a square, so one scale carries a radius.\n const r = (v: unknown) => (typeof v === 'number' ? v / ((m.scale.x + m.scale.y) / 2) : v);\n const out: Record<string, unknown> = { ...e };\n if (e['a'] && e['b']) { out['a'] = pt(e['a']); out['b'] = pt(e['b']); }\n if (e['center']) out['center'] = pt(e['center']);\n if ('radius' in e) out['radius'] = r(e['radius']);\n if ('outerRadius' in e) out['outerRadius'] = r(e['outerRadius']);\n if (Array.isArray(e['samples'])) {\n out['samples'] = (e['samples'] as DragSample[]).map((s) => ({ ...s, ...pt(s) }));\n }\n return out;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"planeInput.js","names":[],"sources":["../../../src/interactions/actions/planeInput.ts"],"sourcesContent":["/**\n * Editing a node on a parallax plane.\n *\n * The dispatcher hands every action the camera's world. A node on a layer\n * carrying `parallax` stores its pose in its plane's world, which the camera's\n * reaches through a `PlaneMap` that changes with the camera. An editing action\n * pairs the pointer with the pose it edits, so the two have to be in one world.\n *\n * `inPlane` wraps an action so its input is carried into the plane of the\n * layer it edits before the action reads it, and the chrome it publishes back\n * comes out in the camera's world. A dep that answers in world points\n * (`nodeAtPoint`, `snap`) keeps speaking the camera's world, and the seam\n * converts at its boundary.\n *\n * A selection can span planes. The edit is measured in one of them, and a\n * node on another is carried into it as the gesture starts and back out\n * through `planeOf` as it is now.\n *\n * The map is read afresh on every call: a camera panning under a held drag, or\n * a plane whose factors are being animated, moves the node with the pointer.\n */\nimport type {\n Action, ActionDeps, AffordanceHit, BindingOpts, DragSample, InvocationCtx, OngoingHandle,\n OngoingOverlay,\n} from '@weasel-js/routing';\nimport type { Scene } from 'core/scene/types';\nimport { asNodeId } from 'core/scene/types';\nimport {\n deriveParallaxView, fromPlane, planeMap, planeToPlane, rectFromPlane, rectToPlane, toPlane,\n type ParallaxOpts, type PlaneMap,\n} from 'core/viewport/parallax';\nimport type { View } from 'core/viewport/view';\nimport type { PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport type { NodeAtPointDep, SnapDep, ViewApi } from './depSchema';\nimport type { SnapBegin } from './snapDep';\n\ntype Point = { x: number; y: number };\n\n/** Which scene layer an invocation edits, read once as it starts. `undefined`\n * means none: the action runs in the camera's world. */\nexport type EditedLayerOf = (deps: ActionDeps, affordance?: AffordanceHit) => string | undefined;\n\nconst layerOfNode = (deps: ActionDeps, id: string | null | undefined): string | undefined =>\n (id ? deps.scene?.get(asNodeId(id))?.layer : undefined);\n\n/** The layer of the node a selection edit acts on: the affordance's target\n * when the drag grabbed one, the first selected node otherwise. A selection\n * spanning planes is edited in that one node's plane. */\nexport const selectionLayer: EditedLayerOf = (deps, affordance) => {\n const selection = deps.selection;\n for (const id of [...(affordance?.targetIds ?? []), ...(selection?.get() ?? [])]) {\n const layer = layerOfNode(deps, id);\n if (layer !== undefined) return layer;\n }\n return undefined;\n};\n\n/** The layer the `insert` dep will put a new node on. */\nexport const insertLayer: EditedLayerOf = (deps) =>\n deps.insert?.layer?.();\n\n/** The layer of the path in anchor-edit mode. */\nexport const editingLayer: EditedLayerOf = (deps) =>\n layerOfNode(deps, deps.editAnchors?.editingId);\n\n/** How the world an invocation edits in maps into the world `layer`'s nodes\n * are stored in, read now. Null when they are the same world. */\nexport function planeOf(view: unknown, layer: string): PlaneMap | null {\n return (view as ViewApi | undefined)?.planeOf?.(layer) ?? null;\n}\n\n/** `m` run backwards; null stays null. */\nexport function invertPlane(m: PlaneMap | null): PlaneMap | null {\n return m && planeToPlane(m, null);\n}\n\n/** `pose` carried through `m`. The map is axis-aligned, so remapping the\n * pose's bounds carries it exactly when the map scales both axes alike. */\nexport function carryPose<P>(d: PoseDescriptor<P>, pose: P, m: PlaneMap | null): P {\n if (m === null) return pose;\n const b = d.getBounds(pose);\n return d.remapBounds(pose, b, rectToPlane(m, b));\n}\n\n/** `action`, with its input carried into the plane of the layer `layerOf`\n * names. An action in a scene with no `parallax` layer, or invoked with no\n * scene or view to read a plane from, runs exactly as it would unwrapped.\n *\n * An immediate action's world point is `params.worldX` / `params.worldY`,\n * the dispatcher's convention for a click; it is carried when present. */\nexport function inPlane<A extends Action>(action: A, layerOf: EditedLayerOf): A {\n const inner = action.invoker;\n if (!inner) return action;\n const declared = action.requires ?? [];\n const requires = [...new Set([...declared, 'scene', 'selection', 'view'])];\n if (inner.timing === 'immediate') {\n return {\n ...action,\n requires,\n invoker: {\n timing: 'immediate',\n run(deps: ActionDeps, params?: Record<string, unknown>): void {\n const plane = livePlane(deps, layerOf(deps), declared, null);\n if (plane === null) return inner.run(deps, params);\n const wx = params?.['worldX'];\n const wy = params?.['worldY'];\n const at = typeof wx === 'number' && typeof wy === 'number'\n ? toPlane(plane.map(), { x: wx, y: wy })\n : null;\n inner.run(plane.deps, at ? { ...params, worldX: at.x, worldY: at.y } : params);\n },\n },\n } as A;\n }\n return {\n ...action,\n requires,\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle {\n const plane = livePlane(\n ctx.deps, layerOf(ctx.deps, ctx.drag?.affordance), declared, ctx.drag?.start ?? null,\n );\n if (plane === null) return inner.start(ctx, opts);\n return planeHandle(inner.start(plane.carry(ctx), opts), plane);\n },\n },\n } as A;\n}\n\ninterface LivePlane {\n map(): PlaneMap;\n /** The invocation's deps, speaking the plane's world. */\n deps: ActionDeps;\n /** `ctx` in the plane's world. */\n carry(ctx: InvocationCtx): InvocationCtx;\n}\n\nfunction livePlane(\n bag: ActionDeps,\n layer: string | undefined,\n /** What the wrapped action declared. The dispatcher's dev deps bag throws\n * on any other read, so the seam converts only the deps it can see. */\n declared: readonly string[],\n /** The drag's press point in the camera's world, when there is a drag. */\n pressedAt: Point | null,\n): LivePlane | null {\n const scene = bag.scene as Scene<unknown, string, unknown> | undefined;\n const viewApi = bag.view as ViewApi | undefined;\n if (layer === undefined || !scene || !viewApi) return null;\n if (!scene.layers.some((l) => l.parallax !== undefined)) return null;\n const parallaxOf = (id: string): ParallaxOpts | undefined => scene.layers.find((l) => l.id === id)?.parallax;\n const parallax = (): ParallaxOpts | undefined => parallaxOf(layer);\n\n const IDENTITY: PlaneMap = { scale: { x: 1, y: 1 }, offset: { x: 0, y: 0 } };\n const map = (): PlaneMap => {\n const p = parallax();\n return p ? planeMap(viewApi.get(), p) : IDENTITY;\n };\n\n // A screen-pixel tolerance, and anything else an action reads off the\n // camera, has to be read at the plane's scale.\n const planeView = Object.defineProperties(Object.create(viewApi), {\n get: {\n value: (): View => {\n const p = parallax();\n const camera = viewApi.get();\n return p ? deriveParallaxView(camera, p) : camera;\n },\n },\n plane: { value: map },\n planeOf: {\n value: (id: string): PlaneMap | null => {\n const edited = parallax();\n const own = parallaxOf(id);\n const camera = viewApi.get();\n return planeToPlane(edited ? planeMap(camera, edited) : null, own ? planeMap(camera, own) : null);\n },\n },\n }) as ViewApi;\n\n // Over the dispatcher's bag rather than a copy of it, so every other read\n // still goes through whatever checks the bag makes.\n const deps = Object.create(bag) as typeof bag;\n const override = (name: string, value: unknown) =>\n Object.defineProperty(deps, name, { value, enumerable: true });\n override('view', planeView);\n const nodeAtPoint = declared.includes('nodeAtPoint')\n ? bag.nodeAtPoint as NodeAtPointDep | undefined\n : undefined;\n if (nodeAtPoint) {\n override('nodeAtPoint', ((p, exclude) => nodeAtPoint(fromPlane(map(), p), exclude)) as NodeAtPointDep);\n }\n const snap = declared.includes('snap') ? bag.snap as SnapDep | undefined : undefined;\n if (snap) {\n // A session runs in the plane; a target the camera draws, a grid or a\n // ruler guide, is carried into it.\n override('snap', { begin: (req: SnapBegin) => snap.begin({ ...req, plane: map }) } satisfies SnapDep);\n }\n\n // The drag's origin, fixed in the plane at the moment it was pressed,\n // whatever the camera does after.\n const start0 = pressedAt ? toPlane(map(), pressedAt) : null;\n const trail = trailCarrier();\n\n return {\n map,\n deps,\n carry(c) {\n const m = map();\n const out: InvocationCtx = { ...c, world: toPlane(m, c.world) };\n if (c.drag) {\n const start = start0 ?? toPlane(m, c.drag.start);\n const current = toPlane(m, c.drag.current);\n const aff = c.drag.affordance;\n out.drag = {\n ...c.drag,\n start,\n current,\n delta: { x: current.x - start.x, y: current.y - start.y },\n ...(c.drag.points ? { points: trail(c.drag.points, m) } : {}),\n ...(aff?.fixedPoint ? { affordance: { ...aff, fixedPoint: toPlane(m, aff.fixedPoint) } } : {}),\n };\n }\n // The dispatcher hands `onMove` no deps; only `start` has any to carry.\n if (c.deps) out.deps = deps;\n return out;\n },\n };\n}\n\n/** The drag trail in the plane. The dispatcher grows its trail in place, so\n * only the samples added since the last call are mapped — unless the map\n * moved, when every sample is. */\nfunction trailCarrier(): (points: DragSample[], m: PlaneMap) => DragSample[] {\n let src: DragSample[] | null = null;\n let lastMap: PlaneMap | null = null;\n let out: DragSample[] = [];\n return (points, m) => {\n const same = lastMap !== null\n && lastMap.scale.x === m.scale.x && lastMap.scale.y === m.scale.y\n && lastMap.offset.x === m.offset.x && lastMap.offset.y === m.offset.y;\n if (points !== src || !same || points.length < out.length) {\n src = points;\n out = [];\n }\n lastMap = m;\n for (let i = out.length; i < points.length; i++) {\n out.push({ ...points[i], ...toPlane(m, points[i]) });\n }\n // A copy, so a snapshot taken on `onEnd` is not the live accumulator.\n return out.slice();\n };\n}\n\nfunction planeHandle(handle: OngoingHandle, plane: LivePlane): OngoingHandle {\n const out: OngoingHandle = { ...handle };\n if (handle.onMove) out.onMove = (c) => handle.onMove!(plane.carry(c));\n if (handle.onEnd) out.onEnd = (c, reason) => handle.onEnd!(plane.carry(c), reason);\n if (handle.onWheel) out.onWheel = (c) => handle.onWheel!(plane.carry(c));\n if (handle.overlay) {\n out.overlay = () => {\n const ov = handle.overlay!();\n if (!ov) return ov;\n const m = plane.map();\n return Array.isArray(ov)\n ? (ov as readonly OngoingOverlay[]).map((o) => overlayFromPlane(o, m))\n : overlayFromPlane(ov as OngoingOverlay, m);\n };\n }\n return out;\n}\n\n/** An overlay drawn in the plane, in the camera's world: what every painter\n * reads an overlay as. */\nfunction overlayFromPlane(ov: OngoingOverlay, m: PlaneMap): OngoingOverlay {\n const pt = (p: Point) => fromPlane(m, p);\n switch (ov.kind) {\n case 'marquee':\n return { ...ov, start: pt(ov.start), current: pt(ov.current) };\n case 'lasso':\n return { ...ov, vertices: ov.vertices.map(pt), current: pt(ov.current) };\n case 'polyline':\n case 'brush':\n return { ...ov, points: ov.points.map(pt) };\n case 'insertPreview':\n return {\n ...ov,\n bounds: rectFromPlane(m, ov.bounds),\n extras: insertExtrasFromPlane(ov.extras, m),\n ...(ov.anchorPoint ? { anchorPoint: pt(ov.anchorPoint) } : {}),\n };\n }\n}\n\n/** The geometry the kit's insert kinds carry in `extras`, in the camera's\n * world. A consumer kind's extras are opaque, and pass through. */\nfunction insertExtrasFromPlane(extras: unknown, m: PlaneMap): unknown {\n if (extras === null || typeof extras !== 'object') return extras;\n const e = extras as Record<string, unknown>;\n const pt = (p: unknown) => fromPlane(m, p as Point);\n // Radial kinds inscribe in a square, so one scale carries a radius.\n const r = (v: unknown) => (typeof v === 'number' ? v / ((m.scale.x + m.scale.y) / 2) : v);\n const out: Record<string, unknown> = { ...e };\n if (e['a'] && e['b']) { out['a'] = pt(e['a']); out['b'] = pt(e['b']); }\n if (e['center']) out['center'] = pt(e['center']);\n if ('radius' in e) out['radius'] = r(e['radius']);\n if ('outerRadius' in e) out['outerRadius'] = r(e['outerRadius']);\n if (Array.isArray(e['samples'])) {\n out['samples'] = (e['samples'] as DragSample[]).map((s) => ({ ...s, ...pt(s) }));\n }\n return out;\n}\n"],"mappings":"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"}
@@ -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"}
@@ -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
  },