@weasel-js/core 1.10.0 → 1.10.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 (327) hide show
  1. package/CHANGELOG.md +168 -0
  2. package/README.md +33 -0
  3. package/dist/affordances/composeAffordanceLayer.js +21 -21
  4. package/dist/affordances/composeAffordanceLayer.js.map +1 -1
  5. package/dist/affordances/hitAffordanceRegions.js +48 -32
  6. package/dist/affordances/hitAffordanceRegions.js.map +1 -1
  7. package/dist/affordances/pathAnchors.js +50 -28
  8. package/dist/affordances/pathAnchors.js.map +1 -1
  9. package/dist/affordances/rotateCursor.js +12 -0
  10. package/dist/affordances/rotateCursor.js.map +1 -0
  11. package/dist/affordances/rotationHandle.js +40 -43
  12. package/dist/affordances/rotationHandle.js.map +1 -1
  13. package/dist/animation/rig/rigNode.js +9 -3
  14. package/dist/animation/rig/rigNode.js.map +1 -1
  15. package/dist/animation/rig/rigSeat.js +46 -13
  16. package/dist/animation/rig/rigSeat.js.map +1 -1
  17. package/dist/animation/rig/skinBinding.js +22 -14
  18. package/dist/animation/rig/skinBinding.js.map +1 -1
  19. package/dist/barrel/canvas.js +29 -28
  20. package/dist/barrel/interactions.js +33 -32
  21. package/dist/barrel/paths.js +11 -10
  22. package/dist/barrel/routing.js +49 -48
  23. package/dist/barrel/scene.js +10 -10
  24. package/dist/barrel/viewport.js +24 -21
  25. package/dist/canvas/Canvas.js +6 -17
  26. package/dist/canvas/Canvas.js.map +1 -1
  27. package/dist/canvas/CanvasView.js +34 -37
  28. package/dist/canvas/CanvasView.js.map +1 -1
  29. package/dist/canvas/CursorCoordsHud.js +3 -3
  30. package/dist/canvas/CursorCoordsHud.js.map +1 -1
  31. package/dist/canvas/DrawCanvas.js +3 -3
  32. package/dist/canvas/DrawCanvas.js.map +1 -1
  33. package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +24 -23
  34. package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js.map +1 -1
  35. package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js +40 -49
  36. package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js.map +1 -1
  37. package/dist/canvas/SceneCanvas/defaultNodeProperties.js +297 -259
  38. package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
  39. package/dist/canvas/SceneCanvas/features.js +3 -24
  40. package/dist/canvas/SceneCanvas/features.js.map +1 -1
  41. package/dist/canvas/SceneCanvas/insertPreviewCommands.js +97 -0
  42. package/dist/canvas/SceneCanvas/insertPreviewCommands.js.map +1 -0
  43. package/dist/canvas/SceneCanvas/poseGeometry.js.map +1 -1
  44. package/dist/canvas/SceneCanvas/props.js.map +1 -1
  45. package/dist/canvas/SceneCanvas/scenePointQuery.js +50 -0
  46. package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -0
  47. package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js +52 -0
  48. package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js.map +1 -0
  49. package/dist/canvas/SceneCanvas/useActionTunings.js +17 -0
  50. package/dist/canvas/SceneCanvas/useActionTunings.js.map +1 -0
  51. package/dist/canvas/SceneCanvas/useCanvasCamera.js +37 -29
  52. package/dist/canvas/SceneCanvas/useCanvasCamera.js.map +1 -1
  53. package/dist/canvas/SceneCanvas/useCanvasDispatcher.js +11 -11
  54. package/dist/canvas/SceneCanvas/useCanvasDispatcher.js.map +1 -1
  55. package/dist/canvas/SceneCanvas/useDispatcherOverlayLayer.js +45 -109
  56. package/dist/canvas/SceneCanvas/useDispatcherOverlayLayer.js.map +1 -1
  57. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +49 -41
  58. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
  59. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +64 -96
  60. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  61. package/dist/canvas/SceneCanvas/worldPointAt.js +3 -4
  62. package/dist/canvas/SceneCanvas/worldPointAt.js.map +1 -1
  63. package/dist/canvas/SceneCanvas.js +77 -80
  64. package/dist/canvas/SceneCanvas.js.map +1 -1
  65. package/dist/canvas/SceneViewCanvas.js +3 -3
  66. package/dist/canvas/SceneViewCanvas.js.map +1 -1
  67. package/dist/canvas/SurfaceCanvas.js +40 -0
  68. package/dist/canvas/SurfaceCanvas.js.map +1 -0
  69. package/dist/canvas/adapterDerivedPath.js +13 -13
  70. package/dist/canvas/adapterDerivedPath.js.map +1 -1
  71. package/dist/canvas/adapterPickSource.js +34 -0
  72. package/dist/canvas/adapterPickSource.js.map +1 -0
  73. package/dist/canvas/affordanceAt.js +1 -1
  74. package/dist/canvas/affordanceAt.js.map +1 -1
  75. package/dist/canvas/buildSceneLayer.js.map +1 -1
  76. package/dist/canvas/buildSceneTree.js +56 -34
  77. package/dist/canvas/buildSceneTree.js.map +1 -1
  78. package/dist/canvas/copiesAbove.js +27 -0
  79. package/dist/canvas/copiesAbove.js.map +1 -0
  80. package/dist/canvas/defaultDrawOne.js +24 -23
  81. package/dist/canvas/defaultDrawOne.js.map +1 -1
  82. package/dist/canvas/deps/editAnchors.js +45 -50
  83. package/dist/canvas/deps/editAnchors.js.map +1 -1
  84. package/dist/canvas/deps/hitTestArea.js +30 -15
  85. package/dist/canvas/deps/hitTestArea.js.map +1 -1
  86. package/dist/canvas/deps/insert.js +134 -100
  87. package/dist/canvas/deps/insert.js.map +1 -1
  88. package/dist/canvas/deps/layout.js +9 -15
  89. package/dist/canvas/deps/layout.js.map +1 -1
  90. package/dist/canvas/derivedPath.js +53 -29
  91. package/dist/canvas/derivedPath.js.map +1 -1
  92. package/dist/canvas/insertPreviewExtent.js +33 -31
  93. package/dist/canvas/insertPreviewExtent.js.map +1 -1
  94. package/dist/canvas/overChildren.js +29 -0
  95. package/dist/canvas/overChildren.js.map +1 -0
  96. package/dist/canvas/paintedCursorLayer.js.map +1 -1
  97. package/dist/canvas/pickReach.js +23 -0
  98. package/dist/canvas/pickReach.js.map +1 -0
  99. package/dist/canvas/pickWalk.js +51 -80
  100. package/dist/canvas/pickWalk.js.map +1 -1
  101. package/dist/canvas/sceneAdapter.js +14 -35
  102. package/dist/canvas/sceneAdapter.js.map +1 -1
  103. package/dist/canvas/sceneSlotWiring.js +11 -10
  104. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  105. package/dist/canvas/sceneViewRender.js +38 -44
  106. package/dist/canvas/sceneViewRender.js.map +1 -1
  107. package/dist/canvas/useCanvasView.js +42 -44
  108. package/dist/canvas/useCanvasView.js.map +1 -1
  109. package/dist/canvas/useSceneAdapter.js +22 -0
  110. package/dist/canvas/useSceneAdapter.js.map +1 -0
  111. package/dist/canvas/useSurfaceInput.js +13 -16
  112. package/dist/canvas/useSurfaceInput.js.map +1 -1
  113. package/dist/canvas/viewRegistry.js +31 -23
  114. package/dist/canvas/viewRegistry.js.map +1 -1
  115. package/dist/canvas/wrapNodeOutput.js +7 -2
  116. package/dist/canvas/wrapNodeOutput.js.map +1 -1
  117. package/dist/clipboard.d.ts +1 -1
  118. package/dist/{clone-2a5hVbBd.d.ts → clone-C0eADtS0.d.ts} +13 -13
  119. package/dist/clone.d.ts +2 -2
  120. package/dist/core/geometry/poseDescriptor.js +49 -23
  121. package/dist/core/geometry/poseDescriptor.js.map +1 -1
  122. package/dist/core/geometry/regionHit.js +2 -2
  123. package/dist/core/geometry/regionHit.js.map +1 -1
  124. package/dist/core/geometry/targetFrame.js.map +1 -1
  125. package/dist/core/layers/children.js +1 -3
  126. package/dist/core/layers/children.js.map +1 -1
  127. package/dist/core/ops/create.js +2 -2
  128. package/dist/core/ops/create.js.map +1 -1
  129. package/dist/core/ops/delete.js +19 -23
  130. package/dist/core/ops/delete.js.map +1 -1
  131. package/dist/core/ops/reorder/index.js +3 -7
  132. package/dist/core/ops/reorder/index.js.map +1 -1
  133. package/dist/core/ops/reorder/setChildOrder.js +2 -4
  134. package/dist/core/ops/reorder/setChildOrder.js.map +1 -1
  135. package/dist/core/ops/reparent.js +0 -1
  136. package/dist/core/ops/reparent.js.map +1 -1
  137. package/dist/core/ops/slot.js +3 -6
  138. package/dist/core/ops/slot.js.map +1 -1
  139. package/dist/core/pages/fitViewToPage.js +15 -0
  140. package/dist/core/pages/fitViewToPage.js.map +1 -0
  141. package/dist/core/pages/pages.js +65 -0
  142. package/dist/core/pages/pages.js.map +1 -0
  143. package/dist/core/pages/pagesLayer.js +27 -0
  144. package/dist/core/pages/pagesLayer.js.map +1 -0
  145. package/dist/core/paint/meshKindLazy.js +2 -2
  146. package/dist/core/paint/meshKindLazy.js.map +1 -1
  147. package/dist/core/scene/NodeProperties.js.map +1 -1
  148. package/dist/core/scene/composeFrame.js.map +1 -1
  149. package/dist/core/scene/composePose.js +58 -20
  150. package/dist/core/scene/composePose.js.map +1 -1
  151. package/dist/core/scene/effectivePose.js +5 -1
  152. package/dist/core/scene/effectivePose.js.map +1 -1
  153. package/dist/core/scene/historyFacade.js +19 -13
  154. package/dist/core/scene/historyFacade.js.map +1 -1
  155. package/dist/core/scene/nodeFnFields.js +7 -0
  156. package/dist/core/scene/nodeFnFields.js.map +1 -1
  157. package/dist/core/scene/outerOps.js +2 -1
  158. package/dist/core/scene/outerOps.js.map +1 -1
  159. package/dist/core/scene/reconcileSpecs.js +1 -1
  160. package/dist/core/scene/reconcileSpecs.js.map +1 -1
  161. package/dist/core/scene/sceneRecording.js +2 -1
  162. package/dist/core/scene/sceneRecording.js.map +1 -1
  163. package/dist/core/scene/types.js.map +1 -1
  164. package/dist/core/viewport/screenSlop.js.map +1 -1
  165. package/dist/core/viewport/useDecayLoop.js +28 -30
  166. package/dist/core/viewport/useDecayLoop.js.map +1 -1
  167. package/dist/core/viewport/useNodeOverlayFrame.js.map +1 -1
  168. package/dist/core/viewport/useViewAnimation.js +70 -69
  169. package/dist/core/viewport/useViewAnimation.js.map +1 -1
  170. package/dist/core/viewport/view.js +8 -5
  171. package/dist/core/viewport/view.js.map +1 -1
  172. package/dist/core/viewport/zoomAt.js +11 -2
  173. package/dist/core/viewport/zoomAt.js.map +1 -1
  174. package/dist/{cornerHandles-BEshc4z3.d.ts → cornerHandles-BSKbyuvQ.d.ts} +1 -1
  175. package/dist/features/paths/editablePath.js +24 -8
  176. package/dist/features/paths/editablePath.js.map +1 -1
  177. package/dist/features/paths/index.js +4 -4
  178. package/dist/features/paths/shapeGenerator.js +89 -0
  179. package/dist/features/paths/shapeGenerator.js.map +1 -0
  180. package/dist/features/paths/usePathEdit.js +13 -13
  181. package/dist/features/poseRun/usePoseRun.js +25 -25
  182. package/dist/features/poseRun/usePoseRun.js.map +1 -1
  183. package/dist/features/text/useSceneTextEdit.js +82 -64
  184. package/dist/features/text/useSceneTextEdit.js.map +1 -1
  185. package/dist/features/viewports/viewResolver.js +28 -24
  186. package/dist/features/viewports/viewResolver.js.map +1 -1
  187. package/dist/icons/SliceIcon.js +24 -0
  188. package/dist/icons/SliceIcon.js.map +1 -0
  189. package/dist/index.d.ts +956 -581
  190. package/dist/index.js +260 -251
  191. package/dist/interactions/actions/align/align.js +9 -9
  192. package/dist/interactions/actions/align/align.js.map +1 -1
  193. package/dist/interactions/actions/booleans/booleans.js +18 -18
  194. package/dist/interactions/actions/booleans/booleans.js.map +1 -1
  195. package/dist/interactions/actions/booleans/operands.js.map +1 -1
  196. package/dist/interactions/actions/clone/action.js +35 -30
  197. package/dist/interactions/actions/clone/action.js.map +1 -1
  198. package/dist/interactions/actions/defaults/anchorEditing.js +56 -59
  199. package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
  200. package/dist/interactions/actions/defaults/anchorHandles.js +42 -0
  201. package/dist/interactions/actions/defaults/anchorHandles.js.map +1 -0
  202. package/dist/interactions/actions/defaults/anchorJoin.js +92 -0
  203. package/dist/interactions/actions/defaults/anchorJoin.js.map +1 -0
  204. package/dist/interactions/actions/defaults/bendSegment.js +46 -0
  205. package/dist/interactions/actions/defaults/bendSegment.js.map +1 -0
  206. package/dist/interactions/actions/defaults/copySubtree.js +1 -1
  207. package/dist/interactions/actions/defaults/copySubtree.js.map +1 -1
  208. package/dist/interactions/actions/defaults/duplicate.js +32 -17
  209. package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
  210. package/dist/interactions/actions/defaults/enterPathEdit.js +19 -7
  211. package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
  212. package/dist/interactions/actions/defaults/icons/anchorIcons.js +75 -0
  213. package/dist/interactions/actions/defaults/icons/anchorIcons.js.map +1 -0
  214. package/dist/interactions/actions/defaults/icons/editIcons.js +35 -8
  215. package/dist/interactions/actions/defaults/icons/editIcons.js.map +1 -1
  216. package/dist/interactions/actions/defaults/insertPathAnchor.js +16 -31
  217. package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
  218. package/dist/interactions/actions/defaults/nudge.js +28 -23
  219. package/dist/interactions/actions/defaults/nudge.js.map +1 -1
  220. package/dist/interactions/actions/defaults/pinchZoom.js +29 -29
  221. package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
  222. package/dist/interactions/actions/defaults/segmentUnder.js +22 -0
  223. package/dist/interactions/actions/defaults/segmentUnder.js.map +1 -0
  224. package/dist/interactions/actions/defaults/viewportZoom.js +82 -79
  225. package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
  226. package/dist/interactions/actions/distribute/distribute.js +8 -8
  227. package/dist/interactions/actions/distribute/distribute.js.map +1 -1
  228. package/dist/interactions/actions/edit-anchors/action.js +58 -55
  229. package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
  230. package/dist/interactions/actions/edit-anchors/preview.js +26 -0
  231. package/dist/interactions/actions/edit-anchors/preview.js.map +1 -0
  232. package/dist/interactions/actions/edit-anchors/symmetry.js +11 -0
  233. package/dist/interactions/actions/edit-anchors/symmetry.js.map +1 -0
  234. package/dist/interactions/actions/flip/action.js.map +1 -1
  235. package/dist/interactions/actions/geometryProjection.js.map +1 -1
  236. package/dist/interactions/actions/insert/action.js +37 -30
  237. package/dist/interactions/actions/insert/action.js.map +1 -1
  238. package/dist/interactions/actions/insert/frame.js +9 -2
  239. package/dist/interactions/actions/insert/frame.js.map +1 -1
  240. package/dist/interactions/actions/insert/repositionAction.js +31 -0
  241. package/dist/interactions/actions/insert/repositionAction.js.map +1 -0
  242. package/dist/interactions/actions/lasso-select/action.js +32 -32
  243. package/dist/interactions/actions/move/action.js +29 -25
  244. package/dist/interactions/actions/move/action.js.map +1 -1
  245. package/dist/interactions/actions/move/behaviors/snapToContainer.js +12 -6
  246. package/dist/interactions/actions/move/behaviors/snapToContainer.js.map +1 -1
  247. package/dist/interactions/actions/move/capture.js +32 -23
  248. package/dist/interactions/actions/move/capture.js.map +1 -1
  249. package/dist/interactions/actions/move/frame.js +2 -14
  250. package/dist/interactions/actions/move/frame.js.map +1 -1
  251. package/dist/interactions/actions/move/session.js +1 -1
  252. package/dist/interactions/actions/move/session.js.map +1 -1
  253. package/dist/interactions/actions/move/translate.js.map +1 -1
  254. package/dist/interactions/actions/outlines/createOutlines.js.map +1 -1
  255. package/dist/interactions/actions/outlines/replaceWithPaths.js +13 -13
  256. package/dist/interactions/actions/outlines/replaceWithPaths.js.map +1 -1
  257. package/dist/interactions/actions/outlines/strokeRegion.js.map +1 -1
  258. package/dist/interactions/actions/pathCommands/cutJoin.js +102 -0
  259. package/dist/interactions/actions/pathCommands/cutJoin.js.map +1 -0
  260. package/dist/interactions/actions/pathCommands/livePathEdit.js +64 -0
  261. package/dist/interactions/actions/pathCommands/livePathEdit.js.map +1 -0
  262. package/dist/interactions/actions/pathCommands/pathNodes.js +99 -0
  263. package/dist/interactions/actions/pathCommands/pathNodes.js.map +1 -0
  264. package/dist/interactions/actions/pathCommands/simplifyOffset.js +42 -0
  265. package/dist/interactions/actions/pathCommands/simplifyOffset.js.map +1 -0
  266. package/dist/interactions/actions/resize/action.js +31 -20
  267. package/dist/interactions/actions/resize/action.js.map +1 -1
  268. package/dist/interactions/actions/resize/commit.js.map +1 -1
  269. package/dist/interactions/actions/resize/frame.js +29 -15
  270. package/dist/interactions/actions/resize/frame.js.map +1 -1
  271. package/dist/interactions/actions/resize/replay.js +47 -0
  272. package/dist/interactions/actions/resize/replay.js.map +1 -0
  273. package/dist/interactions/actions/resize/session.js +49 -35
  274. package/dist/interactions/actions/resize/session.js.map +1 -1
  275. package/dist/interactions/actions/resolveOverlays.js +2 -1
  276. package/dist/interactions/actions/resolveOverlays.js.map +1 -1
  277. package/dist/interactions/actions/rotate/action.js +40 -25
  278. package/dist/interactions/actions/rotate/action.js.map +1 -1
  279. package/dist/interactions/actions/rotate/frame.js +1 -1
  280. package/dist/interactions/actions/rotate/frame.js.map +1 -1
  281. package/dist/interactions/actions/rotate/replay.js +22 -0
  282. package/dist/interactions/actions/rotate/replay.js.map +1 -0
  283. package/dist/interactions/actions/rotate/session.js +42 -32
  284. package/dist/interactions/actions/rotate/session.js.map +1 -1
  285. package/dist/interactions/actions/transform-again/action.js +63 -0
  286. package/dist/interactions/actions/transform-again/action.js.map +1 -0
  287. package/dist/interactions/actions/transform-again/memory.js +35 -0
  288. package/dist/interactions/actions/transform-again/memory.js.map +1 -0
  289. package/dist/interactions/actions/useStandardActions.js +101 -83
  290. package/dist/interactions/actions/useStandardActions.js.map +1 -1
  291. package/dist/move.d.ts +4 -4
  292. package/dist/{originProjection-PPT88jZv.d.ts → originProjection-CoNVpITV.d.ts} +1 -1
  293. package/dist/resize.d.ts +3 -3
  294. package/dist/style.css +1 -1
  295. package/dist/tools/builtin/ellipse/useEllipseTool.js +16 -15
  296. package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
  297. package/dist/tools/builtin/line/useLineTool.js +16 -15
  298. package/dist/tools/builtin/line/useLineTool.js.map +1 -1
  299. package/dist/tools/builtin/pen/usePenTool.js +111 -111
  300. package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
  301. package/dist/tools/builtin/pickTopMostHit.js +11 -14
  302. package/dist/tools/builtin/pickTopMostHit.js.map +1 -1
  303. package/dist/tools/builtin/polygon/usePolygonTool.js +17 -15
  304. package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
  305. package/dist/tools/builtin/rect/useRectTool.js +15 -14
  306. package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
  307. package/dist/tools/builtin/select/adapterPointQuery.js +22 -18
  308. package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
  309. package/dist/tools/builtin/select/selectionContributions.js +1 -1
  310. package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
  311. package/dist/tools/builtin/select/useSelectTool.js +2 -1
  312. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  313. package/dist/tools/builtin/slice/useSliceTool.js +42 -40
  314. package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
  315. package/dist/tools/builtin/star/useStarTool.js +18 -16
  316. package/dist/tools/builtin/star/useStarTool.js.map +1 -1
  317. package/dist/tools/builtin/text/useTextTool.js +2 -2
  318. package/dist/tools/builtin/text/useTextTool.js.map +1 -1
  319. package/dist/{types-CIKDwlsP.d.ts → types-DBbbCy0i.d.ts} +22 -15
  320. package/dist/version.js +1 -1
  321. package/package.json +22 -22
  322. package/dist/canvas/surface.module.js +0 -6
  323. package/dist/canvas/surface.module.js.map +0 -1
  324. package/dist/canvas/surfaceBox.js +0 -16
  325. package/dist/canvas/surfaceBox.js.map +0 -1
  326. package/dist/layout/arrivals.js +0 -33
  327. package/dist/layout/arrivals.js.map +0 -1
@@ -1,98 +1,101 @@
1
1
  import { editingLayer as e, inPlane as t } from "../planeInput.js";
2
- import { anchorAt as n, editAnchorSet as r, moveHandleTo as i, pathToAnchors as a, translateAnchorBy as o, worldEditToStorage as s } from "@weasel-js/geom";
3
- import { isAnchorOrControl as c } from "@weasel-js/routing";
2
+ import { pathEditPreview as n } from "./preview.js";
3
+ import { editedPathSymmetries as r } from "./symmetry.js";
4
+ import { anchorAt as i, editAnchorSetTracked as a, handleAlongItsAngle as o, moveHandleSymmetric as s, moveHandleTo as c, pathToAnchors as l, translateAnchorsSymmetric as u } from "@weasel-js/geom";
5
+ import { isAnchorOrControl as d } from "@weasel-js/routing";
4
6
  //#region src/interactions/actions/edit-anchors/action.ts
5
- function l(e) {
7
+ function f(e) {
6
8
  let t = e.match(/^(anchor|controlIn|controlOut):(\d+)$/);
7
9
  return t ? {
8
10
  anchorIndex: parseInt(t[2], 10),
9
11
  part: t[1]
10
12
  } : null;
11
13
  }
12
- function u(e, t, n) {
14
+ function p(e, t, n) {
13
15
  if (n) {
14
16
  let n = new Set(e);
15
17
  return n.has(t) ? n.delete(t) : n.add(t), n;
16
18
  }
17
19
  return e.has(t) && e.size > 1 ? new Set(e) : /* @__PURE__ */ new Set([t]);
18
20
  }
19
- var d = t({
21
+ function m(e, t) {
22
+ t && (e.currentPose = t.path, e.origins = t.origins);
23
+ }
24
+ var h = t({
20
25
  id: "editAnchors",
21
26
  label: "Edit Anchors",
22
27
  defaultBinding: {
23
28
  kind: "drag",
24
- target: { kindOf: c }
29
+ target: { kindOf: d }
25
30
  },
26
31
  eligible: { capability: "edits-anchors" },
27
- requires: ["selection", "editAnchors"],
32
+ requires: [
33
+ "selection",
34
+ "editAnchors",
35
+ "pathSymmetry"
36
+ ],
28
37
  invoker: {
29
38
  timing: "ongoing",
30
39
  start(e, t) {
31
- let c = e.deps.editAnchors;
32
- if (!c) return {};
33
- let d = e.drag?.affordance;
40
+ let d = e.deps.editAnchors;
34
41
  if (!d) return {};
35
- let f = l(d.kind);
36
- if (!f) return {};
37
- let { editingId: p } = c, m = c.getEditablePath(p);
38
- if (!m || m.kind !== "polygon") return {};
39
- let h = c.getStorageKind(p);
40
- if (h !== "pose" && h !== "data") return {};
41
- let g = a(m), _ = n(g, f.anchorIndex);
42
- if (!_ || f.part === "controlIn" && !_.inHandle || f.part === "controlOut" && !_.outHandle) return {};
43
- let v = [f.anchorIndex];
44
- if (f.part === "anchor") {
45
- let t = u(c.selectedAnchors, f.anchorIndex, e.modifiers.shift);
46
- c.setSelectedAnchors(t), v = t.has(f.anchorIndex) ? [...t] : [];
42
+ let h = e.drag?.affordance;
43
+ if (!h) return {};
44
+ let g = f(h.kind);
45
+ if (!g) return {};
46
+ let { editingId: _ } = d, v = d.getEditablePath(_);
47
+ if (!v || v.kind !== "polygon") return {};
48
+ let y = l(v), b = i(y, g.anchorIndex);
49
+ if (!b || g.part === "controlIn" && !b.inHandle || g.part === "controlOut" && !b.outHandle) return {};
50
+ let x = !1, S = n(d, _, () => x ? w.currentPose : null);
51
+ if (!S) return {};
52
+ let C = [g.anchorIndex];
53
+ if (g.part === "anchor") {
54
+ let t = p(d.selectedAnchors, g.anchorIndex, e.modifiers.shift);
55
+ d.setSelectedAnchors(t), C = t.has(g.anchorIndex) ? [...t] : [];
47
56
  }
48
- let y = h === "data" ? c.getNodeShape(p) : null, b = y ? y.pose : void 0, x = y ? y.data : void 0, S = {
49
- dep: c,
50
- id: p,
51
- anchorIndex: f.anchorIndex,
52
- part: f.part,
53
- dragging: v,
57
+ let w = {
58
+ dep: d,
59
+ id: _,
60
+ anchorIndex: g.anchorIndex,
61
+ part: g.part,
62
+ dragging: C,
54
63
  anchorOrigin: {
55
- x: _.x,
56
- y: _.y
64
+ x: b.x,
65
+ y: b.y
57
66
  },
58
- originPose: m,
59
- currentPose: m,
60
- storageKind: h
61
- }, C = !1;
67
+ handleOrigin: (g.part === "controlIn" ? b.inHandle : g.part === "controlOut" ? b.outHandle : null) ?? null,
68
+ originPose: v,
69
+ currentPose: v,
70
+ symmetries: r(e.deps.pathSymmetry, _, y)
71
+ };
62
72
  return {
63
73
  kind: "edit-anchors",
64
74
  onMove(e) {
65
- if (S.part === "anchor") {
66
- if (S.dragging.length === 0) return;
67
- let t = e.world.x - S.anchorOrigin.x, n = e.world.y - S.anchorOrigin.y, i = r(S.originPose, (e) => {
68
- for (let r of S.dragging) o(e, r, t, n);
69
- });
70
- i && (S.currentPose = i);
75
+ if (w.part === "anchor") {
76
+ if (w.dragging.length === 0) return;
77
+ let t = e.world.x - w.anchorOrigin.x, n = e.world.y - w.anchorOrigin.y;
78
+ m(w, a(w.originPose, (e) => {
79
+ u(e, w.dragging, t, n, w.symmetries);
80
+ }));
71
81
  } else {
72
- let t = S.part === "controlIn" ? "in" : "out", n = r(S.originPose, (n) => i(n, S.anchorIndex, t, e.world.x, e.world.y, e.modifiers.alt));
73
- n && (S.currentPose = n);
82
+ let t = w.part === "controlIn" ? "in" : "out", n = e.modifiers.shift && w.handleOrigin ? o(w.anchorOrigin, w.handleOrigin, e.world) : e.world, r = w.symmetries.length > 0 ? (r) => s(r, w.anchorIndex, t, n.x, n.y, e.modifiers.alt, w.symmetries) : (r) => c(r, w.anchorIndex, t, n.x, n.y, e.modifiers.alt);
83
+ m(w, a(w.originPose, r));
74
84
  }
75
- C = !0;
85
+ x = !0;
76
86
  },
77
87
  onEnd(e, t) {
78
- C = !1, t !== "cancel" && S.originPose !== S.currentPose && S.dep.applyEdit(S.id, S.currentPose, "Edit anchors");
88
+ x = !1, t !== "cancel" && w.originPose !== w.currentPose && w.dep.applyEdit(w.id, w.currentPose, "Edit anchors", w.origins);
79
89
  },
80
- previewIds: () => C ? [S.id] : null,
81
- previewPose: (e) => !C || e !== S.id ? null : S.storageKind === "pose" ? S.currentPose : b ? s(b, S.currentPose).pose : null,
82
- previewData: (e) => {
83
- if (!C || e !== S.id || S.storageKind !== "data" || !x) return null;
84
- let { path: t } = s(b, S.currentPose);
85
- return {
86
- ...x,
87
- path: t
88
- };
89
- }
90
+ previewIds: S.previewIds,
91
+ previewPose: S.previewPose,
92
+ previewData: S.previewData
90
93
  };
91
94
  }
92
95
  },
93
96
  enabled: () => !0
94
97
  }, e);
95
98
  //#endregion
96
- export { d as editAnchorsAction, u as selectionAfterAnchorPress };
99
+ export { h as editAnchorsAction, p as selectionAfterAnchorPress };
97
100
 
98
101
  //# sourceMappingURL=action.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"action.js","names":["parseAnchorAffordance","m","kind","selectionAfterAnchorPress","additive","next","current","anchorIndex","editAnchorsAction","inPlane","isAnchorOrControl","dep","ctx","affordance","anchorInfo","worldPath","editingId","storageKind","decoded","pathToAnchors","origin","anchorAt","dragging","nodeShape","originRectPose","originData","scratch","active","dx","moveCtx","dy","editAnchorSet","flat","set","side","moveHandleTo","reason","id","worldEditToStorage","editingLayer"],"sources":["../../../../src/interactions/actions/edit-anchors/action.ts"],"sourcesContent":["/**\n * `editAnchorsAction` — ongoing Action descriptor for editing polygon anchors.\n *\n * - **Press** selects. Bare press replaces the anchor selection; Shift\n * toggles the pressed anchor in or out of it. Pressing an anchor that\n * is already part of a multi-anchor selection leaves the selection\n * alone, so you can grab a set and move it.\n * - **Drag** then moves whatever the press left selected — one anchor,\n * or every selected anchor together.\n * - **Dragging a control handle** moves just that handle, mirroring the\n * opposite one while the anchor is smooth. Alt breaks the mirror.\n *\n * Selection lives on the `editAnchors` dep (flat anchor indices) rather\n * than in this handle's scratch, because the keyboard actions\n * (`nudgeAnchors`, `deleteAnchors`) and the overlay all need to read it\n * between gestures.\n *\n * Operational when the consumer wires the `editAnchors` dep, and\n * `buildAffordanceAt` with a `getAnchorState` thunk so the dispatcher\n * classifies anchor/control-handle hits as `anchor:N`, `controlIn:N`, or\n * `controlOut:N` affordances on pointerdown. On any other affordance `start`\n * returns `{}` so other bindings can handle the drag.\n */\n\nimport type { Action } from '@weasel-js/routing';\nimport type { InvocationCtx, OngoingHandle } from '@weasel-js/routing';\nimport type { EditAnchorsDep } from '../depSchema';\nimport { isAnchorOrControl } from '@weasel-js/routing';\nimport {\n anchorAt,\n editAnchorSet,\n moveHandleTo,\n translateAnchorBy,\n type AnchorSet,\n} from '@weasel-js/geom';\nimport { pathToAnchors, type PolygonPath } from '@weasel-js/geom';\nimport { worldEditToStorage } from '@weasel-js/geom';\nimport { inPlane, editingLayer } from '../planeInput';\n\n/**\n * Parse an anchor affordance kind — `'anchor:N'` | `'controlIn:N'` |\n * `'controlOut:N'`, where N is the flat anchor index — or `null` when the\n * affordance is not an anchor kind.\n */\nfunction parseAnchorAffordance(\n kind: string,\n): { anchorIndex: number; part: 'anchor' | 'controlIn' | 'controlOut' } | null {\n const m = kind.match(/^(anchor|controlIn|controlOut):(\\d+)$/);\n if (!m) return null;\n return {\n anchorIndex: parseInt(m[2], 10),\n part: m[1] as 'anchor' | 'controlIn' | 'controlOut',\n };\n}\n\n/**\n * Resolve what a press on `anchorIndex` should leave selected.\n *\n * Shift toggles. A bare press on an anchor that's already part of the\n * selection keeps the selection intact (so the drag moves the whole set);\n * a bare press anywhere else collapses to just that anchor.\n */\nexport function selectionAfterAnchorPress(\n current: ReadonlySet<number>,\n anchorIndex: number,\n additive: boolean,\n): Set<number> {\n if (additive) {\n const next = new Set(current);\n if (next.has(anchorIndex)) next.delete(anchorIndex);\n else next.add(anchorIndex);\n return next;\n }\n if (current.has(anchorIndex) && current.size > 1) return new Set(current);\n return new Set([anchorIndex]);\n}\n\ninterface EditAnchorsScratch {\n dep: EditAnchorsDep;\n id: string;\n anchorIndex: number;\n /** `'anchor'` drags on-curve points (and their handles) by a delta.\n * `'controlIn'` / `'controlOut'` drag one control point to an absolute\n * world coord. */\n part: 'anchor' | 'controlIn' | 'controlOut';\n /** Anchors this drag moves — the pressed one, or the whole anchor\n * selection when the press landed inside it. Flat indices. */\n dragging: readonly number[];\n /** World position of the pressed anchor at drag start; the drag delta is\n * measured from here rather than accumulated per move, so the geometry\n * is always derived from `originPose` in one step. */\n anchorOrigin: { x: number; y: number };\n originPose: PolygonPath;\n currentPose: PolygonPath;\n /** Where the polygon lives on the node — drives which preview-ghost\n * axes the handle populates (`previewPose` only for pose-as-polygon,\n * `previewPose` + `previewData` for data.path). */\n storageKind: 'pose' | 'data';\n}\n\n/** Static descriptor for the `editAnchors` Action. */\nexport const editAnchorsAction: Action & { requires: string[] } = inPlane({\n id: 'editAnchors',\n label: 'Edit Anchors',\n defaultBinding: {\n kind: 'drag',\n // Only matches drags whose pointerdown hit an anchor or control handle.\n // Ties moveAction's `selected-body` on specificity and registers after\n // it, so move declines these presses in `start`.\n target: { kindOf: isAnchorOrControl },\n },\n eligible: { capability: 'edits-anchors' },\n requires: ['selection', 'editAnchors'],\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx, _opts): OngoingHandle {\n const dep = ctx.deps.editAnchors;\n if (!dep) return {};\n\n const affordance = ctx.drag?.affordance;\n if (!affordance) {\n // No affordance hit → this drag doesn't target an anchor handle.\n // Let other bindings handle it (the handle returns {}, dispatcher moves on).\n return {};\n }\n\n const anchorInfo = parseAnchorAffordance(affordance.kind);\n if (!anchorInfo) {\n // Affordance is a non-anchor kind (e.g. resize handle, rotate handle).\n return {};\n }\n\n const { editingId } = dep;\n // World-coord polygon. For pose-as-polygon nodes this equals\n // node.pose; for data.path nodes it's pathInWorld-projected (translate +\n // rotation) so the anchor world coords match what the user clicked.\n const worldPath = dep.getEditablePath(editingId) as PolygonPath | undefined;\n if (!worldPath || worldPath.kind !== 'polygon') return {};\n const storageKind = dep.getStorageKind(editingId);\n if (storageKind !== 'pose' && storageKind !== 'data') return {};\n\n // Decode once to validate the affordance index against the actual\n // geometry and to read the press origin. A stale affordance (the\n // path changed between pointerdown and now) fails here rather than\n // silently editing whichever anchor now holds that index.\n const decoded = pathToAnchors(worldPath) as AnchorSet;\n const origin = anchorAt(decoded, anchorInfo.anchorIndex);\n if (!origin) return {};\n if (anchorInfo.part === 'controlIn' && !origin.inHandle) return {};\n if (anchorInfo.part === 'controlOut' && !origin.outHandle) return {};\n\n // --- Press semantics: selection updates immediately, before any move.\n let dragging: readonly number[] = [anchorInfo.anchorIndex];\n if (anchorInfo.part === 'anchor') {\n const next = selectionAfterAnchorPress(\n dep.selectedAnchors,\n anchorInfo.anchorIndex,\n ctx.modifiers.shift,\n );\n dep.setSelectedAnchors(next);\n // Shift-pressing an anchor OUT of the selection must not then drag\n // it; drag whatever the press left selected, intersected with the\n // pressed anchor's membership.\n dragging = next.has(anchorInfo.anchorIndex)\n ? [...next]\n : [];\n }\n\n // For data.path nodes, the original rect pose + data that previewPose /\n // previewData derive from, captured at start.\n const nodeShape = storageKind === 'data' ? dep.getNodeShape(editingId) : null;\n const originRectPose =\n nodeShape ? (nodeShape.pose as { x: number; y: number; width: number; height: number }) : undefined;\n const originData = nodeShape ? (nodeShape.data as object) : undefined;\n\n const scratch: EditAnchorsScratch = {\n dep,\n id: editingId,\n anchorIndex: anchorInfo.anchorIndex,\n part: anchorInfo.part,\n dragging,\n anchorOrigin: { x: origin.x, y: origin.y },\n originPose: worldPath,\n currentPose: worldPath,\n storageKind,\n };\n\n let active = false;\n\n return {\n kind: 'edit-anchors',\n onMove(moveCtx: InvocationCtx): void {\n if (scratch.part === 'anchor') {\n if (scratch.dragging.length === 0) return;\n const dx = moveCtx.world.x - scratch.anchorOrigin.x;\n const dy = moveCtx.world.y - scratch.anchorOrigin.y;\n const next = editAnchorSet(scratch.originPose, (set) => {\n for (const flat of scratch.dragging) translateAnchorBy(set, flat, dx, dy);\n });\n if (next) scratch.currentPose = next;\n } else {\n const side = scratch.part === 'controlIn' ? 'in' : 'out';\n const next = editAnchorSet(scratch.originPose, (set) =>\n moveHandleTo(\n set,\n scratch.anchorIndex,\n side,\n moveCtx.world.x,\n moveCtx.world.y,\n // Alt breaks the smooth mirror for this drag — read live so\n // the user can press or release Alt mid-drag.\n moveCtx.modifiers.alt,\n ),\n );\n if (next) scratch.currentPose = next;\n }\n active = true;\n },\n onEnd(_endCtx: InvocationCtx, reason: 'commit' | 'cancel'): void {\n active = false;\n if (reason === 'cancel') return;\n if (scratch.originPose === scratch.currentPose) return;\n scratch.dep.applyEdit(scratch.id, scratch.currentPose, 'Edit anchors');\n },\n previewIds: () => (active ? [scratch.id] : null),\n previewPose: (id: string): unknown | null => {\n if (!active || id !== scratch.id) return null;\n if (scratch.storageKind === 'pose') {\n // Pose IS the polygon — preview pose is the polygon itself.\n return scratch.currentPose;\n }\n // data.path: invert the world edit the same way the commit does, so\n // the preview ghost (which re-applies pose.rotation via the render\n // wrap) matches the committed result — no double-rotation.\n if (!originRectPose) return null;\n return worldEditToStorage(originRectPose, scratch.currentPose).pose;\n },\n previewData: (id: string): unknown | null => {\n if (!active || id !== scratch.id) return null;\n if (scratch.storageKind !== 'data') return null;\n if (!originData) return null;\n // Stored path is the unrotated, origin-aligned contour; the render\n // invariant `pathInWorld(stored, pose) === world` holds.\n const { path } = worldEditToStorage(originRectPose!, scratch.currentPose);\n return { ...originData, path };\n },\n };\n },\n },\n enabled: () => true,\n}, editingLayer);\n"],"mappings":"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"}
1
+ {"version":3,"file":"action.js","names":["parseAnchorAffordance","m","kind","selectionAfterAnchorPress","additive","next","current","anchorIndex","track","editAnchorsAction","inPlane","isAnchorOrControl","dep","ctx","affordance","anchorInfo","worldPath","editingId","decoded","pathToAnchors","origin","anchorAt","active","preview","pathEditPreview","scratch","dragging","editedPathSymmetries","dx","moveCtx","dy","editAnchorSetTracked","set","side","to","handleAlongItsAngle","move","moveHandleSymmetric","moveHandleTo","reason","editingLayer"],"sources":["../../../../src/interactions/actions/edit-anchors/action.ts"],"sourcesContent":["/**\n * `editAnchorsAction` — ongoing Action descriptor for editing polygon anchors.\n *\n * - **Press** selects. Bare press replaces the anchor selection; Shift\n * toggles the pressed anchor in or out of it. Pressing an anchor that\n * is already part of a multi-anchor selection leaves the selection\n * alone, so you can grab a set and move it.\n * - **Drag** then moves whatever the press left selected — one anchor,\n * or every selected anchor together.\n * - **Dragging a control handle** moves just that handle, mirroring the\n * opposite one while the anchor is smooth. Alt breaks the mirror; Shift\n * holds the handle to the angle it had at the press, changing only its\n * length.\n * - **Under a `pathSymmetry` dep**, on a path that is its own image under\n * some of its transforms, every image of what is dragged follows it.\n *\n * Selection lives on the `editAnchors` dep (flat anchor indices) rather\n * than in this handle's scratch, because the keyboard actions\n * (`nudgeAnchors`, `deleteAnchors`) and the overlay all need to read it\n * between gestures.\n *\n * Operational when the consumer wires the `editAnchors` dep, and\n * `buildAffordanceAt` with a `getAnchorState` thunk so the dispatcher\n * classifies anchor/control-handle hits as `anchor:N`, `controlIn:N`, or\n * `controlOut:N` affordances on pointerdown. On any other affordance `start`\n * returns `{}` so other bindings can handle the drag.\n */\n\nimport type { Action } from '@weasel-js/routing';\nimport type { InvocationCtx, OngoingHandle } from '@weasel-js/routing';\nimport type { EditAnchorsDep } from '../depSchema';\nimport { isAnchorOrControl } from '@weasel-js/routing';\nimport {\n anchorAt,\n editAnchorSetTracked,\n handleAlongItsAngle,\n moveHandleSymmetric,\n moveHandleTo,\n translateAnchorsSymmetric,\n type AnchorSet,\n type AnchorSymmetry,\n type PointOrigin,\n} from '@weasel-js/geom';\nimport { pathToAnchors, type PolygonPath } from '@weasel-js/geom';\nimport { inPlane, editingLayer } from '../planeInput';\nimport { pathEditPreview } from './preview';\nimport { editedPathSymmetries } from './symmetry';\n\n/**\n * Parse an anchor affordance kind — `'anchor:N'` | `'controlIn:N'` |\n * `'controlOut:N'`, where N is the flat anchor index — or `null` when the\n * affordance is not an anchor kind.\n */\nfunction parseAnchorAffordance(\n kind: string,\n): { anchorIndex: number; part: 'anchor' | 'controlIn' | 'controlOut' } | null {\n const m = kind.match(/^(anchor|controlIn|controlOut):(\\d+)$/);\n if (!m) return null;\n return {\n anchorIndex: parseInt(m[2], 10),\n part: m[1] as 'anchor' | 'controlIn' | 'controlOut',\n };\n}\n\n/**\n * Resolve what a press on `anchorIndex` should leave selected.\n *\n * Shift toggles. A bare press on an anchor that's already part of the\n * selection keeps the selection intact (so the drag moves the whole set);\n * a bare press anywhere else collapses to just that anchor.\n */\nexport function selectionAfterAnchorPress(\n current: ReadonlySet<number>,\n anchorIndex: number,\n additive: boolean,\n): Set<number> {\n if (additive) {\n const next = new Set(current);\n if (next.has(anchorIndex)) next.delete(anchorIndex);\n else next.add(anchorIndex);\n return next;\n }\n if (current.has(anchorIndex) && current.size > 1) return new Set(current);\n return new Set([anchorIndex]);\n}\n\ninterface EditAnchorsScratch {\n dep: EditAnchorsDep;\n id: string;\n anchorIndex: number;\n /** `'anchor'` drags on-curve points (and their handles) by a delta.\n * `'controlIn'` / `'controlOut'` drag one control point to an absolute\n * world coord. */\n part: 'anchor' | 'controlIn' | 'controlOut';\n /** Anchors this drag moves — the pressed one, or the whole anchor\n * selection when the press landed inside it. Flat indices. */\n dragging: readonly number[];\n /** World position of the pressed anchor at drag start; the drag delta is\n * measured from here rather than accumulated per move, so the geometry\n * is always derived from `originPose` in one step. */\n anchorOrigin: { x: number; y: number };\n /** The dragged control handle's position at the press, for a handle drag. */\n handleOrigin: { x: number; y: number } | null;\n originPose: PolygonPath;\n currentPose: PolygonPath;\n /** The transforms `originPose` is its own image under, read at the press. */\n symmetries: readonly AnchorSymmetry[];\n /** Where each point of `currentPose` came from in `originPose`. */\n origins?: readonly (PointOrigin | null)[];\n}\n\nfunction track(scratch: EditAnchorsScratch, next: ReturnType<typeof editAnchorSetTracked>): void {\n if (!next) return;\n scratch.currentPose = next.path;\n scratch.origins = next.origins;\n}\n\n/** Static descriptor for the `editAnchors` Action. */\nexport const editAnchorsAction: Action & { requires: string[] } = inPlane({\n id: 'editAnchors',\n label: 'Edit Anchors',\n defaultBinding: {\n kind: 'drag',\n // Only matches drags whose pointerdown hit an anchor or control handle.\n // Ties moveAction's `selected-body` on specificity and registers after\n // it, so move declines these presses in `start`.\n target: { kindOf: isAnchorOrControl },\n },\n eligible: { capability: 'edits-anchors' },\n requires: ['selection', 'editAnchors', 'pathSymmetry'],\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx, _opts): OngoingHandle {\n const dep = ctx.deps.editAnchors;\n if (!dep) return {};\n\n const affordance = ctx.drag?.affordance;\n if (!affordance) {\n // No affordance hit → this drag doesn't target an anchor handle.\n // Let other bindings handle it (the handle returns {}, dispatcher moves on).\n return {};\n }\n\n const anchorInfo = parseAnchorAffordance(affordance.kind);\n if (!anchorInfo) {\n // Affordance is a non-anchor kind (e.g. resize handle, rotate handle).\n return {};\n }\n\n const { editingId } = dep;\n // World-coord polygon. For pose-as-polygon nodes this equals\n // node.pose; for data.path nodes it's pathInWorld-projected (translate +\n // rotation) so the anchor world coords match what the user clicked.\n const worldPath = dep.getEditablePath(editingId) as PolygonPath | undefined;\n if (!worldPath || worldPath.kind !== 'polygon') return {};\n\n // Decode once to validate the affordance index against the actual\n // geometry and to read the press origin. A stale affordance (the\n // path changed between pointerdown and now) fails here rather than\n // silently editing whichever anchor now holds that index.\n const decoded = pathToAnchors(worldPath) as AnchorSet;\n const origin = anchorAt(decoded, anchorInfo.anchorIndex);\n if (!origin) return {};\n if (anchorInfo.part === 'controlIn' && !origin.inHandle) return {};\n if (anchorInfo.part === 'controlOut' && !origin.outHandle) return {};\n\n let active = false;\n const preview = pathEditPreview(dep, editingId, () => (active ? scratch.currentPose : null));\n if (!preview) return {};\n\n // --- Press semantics: selection updates immediately, before any move.\n let dragging: readonly number[] = [anchorInfo.anchorIndex];\n if (anchorInfo.part === 'anchor') {\n const next = selectionAfterAnchorPress(\n dep.selectedAnchors,\n anchorInfo.anchorIndex,\n ctx.modifiers.shift,\n );\n dep.setSelectedAnchors(next);\n // Shift-pressing an anchor OUT of the selection must not then drag\n // it; drag whatever the press left selected, intersected with the\n // pressed anchor's membership.\n dragging = next.has(anchorInfo.anchorIndex)\n ? [...next]\n : [];\n }\n\n const scratch: EditAnchorsScratch = {\n dep,\n id: editingId,\n anchorIndex: anchorInfo.anchorIndex,\n part: anchorInfo.part,\n dragging,\n anchorOrigin: { x: origin.x, y: origin.y },\n handleOrigin: (anchorInfo.part === 'controlIn' ? origin.inHandle\n : anchorInfo.part === 'controlOut' ? origin.outHandle : null) ?? null,\n originPose: worldPath,\n currentPose: worldPath,\n symmetries: editedPathSymmetries(ctx.deps.pathSymmetry, editingId, decoded),\n };\n\n return {\n kind: 'edit-anchors',\n onMove(moveCtx: InvocationCtx): void {\n if (scratch.part === 'anchor') {\n if (scratch.dragging.length === 0) return;\n const dx = moveCtx.world.x - scratch.anchorOrigin.x;\n const dy = moveCtx.world.y - scratch.anchorOrigin.y;\n track(scratch, editAnchorSetTracked(scratch.originPose, (set) => {\n translateAnchorsSymmetric(set, scratch.dragging, dx, dy, scratch.symmetries);\n }));\n } else {\n const side = scratch.part === 'controlIn' ? 'in' : 'out';\n // Modifiers are read live so the user can press or release them mid-drag.\n const to = moveCtx.modifiers.shift && scratch.handleOrigin\n ? handleAlongItsAngle(scratch.anchorOrigin, scratch.handleOrigin, moveCtx.world)\n : moveCtx.world;\n const move = scratch.symmetries.length > 0\n ? (set: AnchorSet) => moveHandleSymmetric(\n set, scratch.anchorIndex, side, to.x, to.y, moveCtx.modifiers.alt, scratch.symmetries)\n : (set: AnchorSet) => moveHandleTo(set, scratch.anchorIndex, side, to.x, to.y, moveCtx.modifiers.alt);\n track(scratch, editAnchorSetTracked(scratch.originPose, move));\n }\n active = true;\n },\n onEnd(_endCtx: InvocationCtx, reason: 'commit' | 'cancel'): void {\n active = false;\n if (reason === 'cancel') return;\n if (scratch.originPose === scratch.currentPose) return;\n scratch.dep.applyEdit(scratch.id, scratch.currentPose, 'Edit anchors', scratch.origins);\n },\n previewIds: preview.previewIds,\n previewPose: preview.previewPose,\n previewData: preview.previewData,\n };\n },\n },\n enabled: () => true,\n}, editingLayer);\n"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { worldEditToStorage as e } from "@weasel-js/geom";
2
+ //#region src/interactions/actions/edit-anchors/preview.ts
3
+ function t(t, n, r) {
4
+ let i = t.getStorageKind(n);
5
+ if (i !== "pose" && i !== "data") return null;
6
+ let a = i === "data" ? t.getNodeShape(n) : null, o = a ? a.pose : void 0, s = a ? a.data : void 0;
7
+ return {
8
+ storageKind: i,
9
+ previewIds: () => r() ? [n] : null,
10
+ previewPose: (t) => {
11
+ let a = t === n ? r() : null;
12
+ return a ? i === "pose" ? a : o ? e(o, a).pose : null : null;
13
+ },
14
+ previewData: (t) => {
15
+ let a = t === n ? r() : null;
16
+ return !a || i !== "data" || !s ? null : {
17
+ ...s,
18
+ path: e(o, a).path
19
+ };
20
+ }
21
+ };
22
+ }
23
+ //#endregion
24
+ export { t as pathEditPreview };
25
+
26
+ //# sourceMappingURL=preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.js","names":["pathEditPreview","storageKind","dep","id","nodeShape","originRectPose","originData","current","path","pid","worldEditToStorage"],"sources":["../../../../src/interactions/actions/edit-anchors/preview.ts"],"sourcesContent":["import type { OngoingHandle } from '@weasel-js/routing';\nimport type { EditAnchorsDep } from '../depSchema';\nimport { worldEditToStorage, type PolygonPath } from '@weasel-js/geom';\n\n/** Where the edited polygon lives on its node: the pose itself, or `data.path`\n * under a rect pose. */\nexport type PathStorageKind = 'pose' | 'data';\n\n/** The preview half of an ongoing path edit, captured at press. */\nexport interface PathEditPreview {\n storageKind: PathStorageKind;\n previewIds: NonNullable<OngoingHandle['previewIds']>;\n previewPose: NonNullable<OngoingHandle['previewPose']>;\n previewData: NonNullable<OngoingHandle['previewData']>;\n}\n\n/**\n * Preview ghosts for a drag that reshapes the path in edit mode. `current`\n * answers the world-space polygon to show, or null while there is nothing to\n * preview yet. Null when the node stores its path in neither supported place.\n */\nexport function pathEditPreview(\n dep: EditAnchorsDep,\n id: string,\n current: () => PolygonPath | null,\n): PathEditPreview | null {\n const storageKind = dep.getStorageKind(id);\n if (storageKind !== 'pose' && storageKind !== 'data') return null;\n // For data.path nodes, the original rect pose + data that previewPose /\n // previewData derive from, captured at start.\n const nodeShape = storageKind === 'data' ? dep.getNodeShape(id) : null;\n const originRectPose =\n nodeShape ? (nodeShape.pose as { x: number; y: number; width: number; height: number }) : undefined;\n const originData = nodeShape ? (nodeShape.data as object) : undefined;\n\n return {\n storageKind,\n previewIds: () => (current() ? [id] : null),\n previewPose: (pid: string): unknown | null => {\n const path = pid === id ? current() : null;\n if (!path) return null;\n // Pose IS the polygon — preview pose is the polygon itself.\n if (storageKind === 'pose') return path;\n // data.path: invert the world edit the same way the commit does, so\n // the preview ghost (which re-applies pose.rotation via the render\n // wrap) matches the committed result — no double-rotation.\n if (!originRectPose) return null;\n return worldEditToStorage(originRectPose, path).pose;\n },\n previewData: (pid: string): unknown | null => {\n const path = pid === id ? current() : null;\n if (!path || storageKind !== 'data' || !originData) return null;\n // Stored path is the unrotated, origin-aligned contour; the render\n // invariant `pathInWorld(stored, pose) === world` holds.\n return { ...originData, path: worldEditToStorage(originRectPose!, path).path };\n },\n };\n}\n"],"mappings":";;AAqBA,SAAgBA,EACd,GACA,GACA,GACwB;CACxB,IAAMC,IAAcC,EAAI,eAAeC,CAAE;CACzC,IAAIF,MAAgB,UAAUA,MAAgB,QAAQ,OAAO;CAG7D,IAAMG,IAAYH,MAAgB,SAASC,EAAI,aAAaC,CAAE,IAAI,MAC5DE,IACJD,IAAaA,EAAU,OAAmE,KAAA,GACtFE,IAAaF,IAAaA,EAAU,OAAkB,KAAA;CAE5D,OAAO;EACL;EACA,kBAAmBG,EAAQ,IAAI,CAACJ,CAAE,IAAI;EACtC,cAAc,MAAgC;GAC5C,IAAMK,IAAOC,MAAQN,IAAKI,EAAQ,IAAI;GAQtC,OAPKC,IAEDP,MAAgB,SAAeO,IAI9BH,IACEK,EAAmBL,GAAgBG,CAAI,CAAC,CAAC,OADpB,OANV;EAQpB;EACA,cAAc,MAAgC;GAC5C,IAAMA,IAAOC,MAAQN,IAAKI,EAAQ,IAAI;GAItC,OAHI,CAACC,KAAQP,MAAgB,UAAU,CAACK,IAAmB,OAGpD;IAAE,GAAGA;IAAY,MAAMI,EAAmBL,GAAiBG,CAAI,CAAC,CAAC;GAAK;EAC/E;CACF;AACF"}
@@ -0,0 +1,11 @@
1
+ import { anchorSymmetries as e } from "@weasel-js/geom";
2
+ //#region src/interactions/actions/edit-anchors/symmetry.ts
3
+ var t = .001;
4
+ function n(n, r, i) {
5
+ let a = n?.transforms(r) ?? [];
6
+ return a.length === 0 ? [] : e(i, a, t);
7
+ }
8
+ //#endregion
9
+ export { n as editedPathSymmetries };
10
+
11
+ //# sourceMappingURL=symmetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symmetry.js","names":["SYMMETRY_TOLERANCE","editedPathSymmetries","transforms","dep","id","anchorSymmetries","set"],"sources":["../../../../src/interactions/actions/edit-anchors/symmetry.ts"],"sourcesContent":["import { anchorSymmetries, type AnchorSet, type AnchorSymmetry } from '@weasel-js/geom';\nimport type { PathSymmetryDep } from '../depSchema';\n\n/** World-unit slack for \"this anchor is the image of that one\": enough to\n * absorb a path's float32 coordinates, far under anything drawn by hand. */\nconst SYMMETRY_TOLERANCE = 1e-3;\n\n/** The transforms the path `id`, decoded as `set`, is already its own image\n * under, of those the `pathSymmetry` dep names. Empty without the dep. */\nexport function editedPathSymmetries(\n dep: PathSymmetryDep | undefined,\n id: string,\n set: AnchorSet,\n): AnchorSymmetry[] {\n const transforms = dep?.transforms(id) ?? [];\n return transforms.length === 0 ? [] : anchorSymmetries(set, transforms, SYMMETRY_TOLERANCE);\n}\n"],"mappings":";;AAKA,IAAMA,IAAqB;AAI3B,SAAgBC,EACd,GACA,GACA,GACkB;CAClB,IAAMC,IAAaC,GAAK,WAAWC,CAAE,KAAK,CAAC;CAC3C,OAAOF,EAAW,WAAW,IAAI,CAAC,IAAIG,EAAiBC,GAAKJ,GAAYF,CAAkB;AAC5F"}
@@ -1 +1 @@
1
- {"version":3,"file":"action.js","names":["localMirror","at","frame","id","pivotWorld","theta","mirror","axis","turned","multiply","rotate","translate","flipSelection","selected","selection","scenePoseFrame","scene","ids","ownPivot","members","shapeSources","box","visualBoundsViaDescriptor","geom","member","worlds","sharedPivot","pivot","unionAABB","p","resolveSpatialReference","actionReferenceSources","params","pointer","ops","node","world","about","flipped","flipPoseAboutBounds","flipPoseViaDescriptor","createTransformOp","g","dataOp","geometryDataOp","geometryProjection","applyOps","defaultCommitAdapter","flipAction","createElement","FlipXIcon","FlipYIcon","deps","poseDescriptorOf","requiresSelection"],"sources":["../../../../src/interactions/actions/flip/action.ts"],"sourcesContent":["import type { NodeId, Scene } from 'core/scene/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { shapeSources } from '../selectionSubtree';\nimport { createElement } from 'react';\nimport { FlipXIcon, FlipYIcon } from '../defaults/icons/editIcons';\nimport type { Op } from 'core/ops/types';\nimport { multiply, rotate, translate, type Mat3 } from '@weasel-js/geom';\nimport { createTransformOp } from 'core/ops/transform';\nimport type { PoseDescriptor } from '../resize/geometry';\nimport { poseDescriptorOf } from '../poseDescriptorDep';\nimport {\n flipPoseAboutBounds,\n flipPoseViaDescriptor,\n type FlipAxis,\n type FlipPivot,\n} from './helpers';\nimport { unionAABB } from 'core/geometry/unionBounds';\nimport { visualBoundsViaDescriptor } from '../align/align';\nimport { scenePoseFrame, type PoseFrame } from '../poseFrame';\nimport { actionReferenceSources, resolveSpatialReference } from '../spatialReference';\nimport type { PointerContextValue } from '@weasel-js/routing/react';\nimport type { RectPose } from 'core/scene/types';\nimport type { Action } from '@weasel-js/routing';\nimport { defaultCommitAdapter } from '../defaultCommitAdapter';\nimport { requiresSelection } from '../defaults/requiresSelection';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport { geometryDataOp, type GeometryProjection } from '../geometryProjection';\n\n/**\n * The world mirror about `axis` through the center of `pivotWorld`, written in\n * the frame `id` stores its data in. `geometryProjection.transform` is given\n * the affine the node's own pose underwent, and data-held geometry is local —\n * so a world mirror has to be conjugated by the frame or an asymmetric shape\n * reflects across the wrong line while its pose lands correctly.\n *\n * The frame's turn comes back as the rotation on a rebased zero-size pose at\n * the pivot point, so no composition strategy has to expose one.\n */\nfunction localMirror(\n axis: FlipAxis,\n frame: PoseFrame<unknown>,\n id: Parameters<PoseFrame<unknown>['local']>[0],\n pivotWorld: { x: number; y: number; width: number; height: number },\n): Mat3 {\n const at = frame.local(id, {\n x: pivotWorld.x + pivotWorld.width / 2,\n y: pivotWorld.y + pivotWorld.height / 2,\n width: 0,\n height: 0,\n rotation: 0,\n }) as RectPose;\n const theta = at.rotation ?? 0;\n const mirror: Mat3 = axis === 'x' ? [-1, 0, 0, 1, 0, 0] : [1, 0, 0, -1, 0, 0];\n const turned = multiply(multiply(rotate(theta), mirror), rotate(-theta));\n return multiply(translate(at.x, at.y), multiply(turned, translate(-at.x, -at.y)));\n}\n\n/**\n * Flip the current selection in `scene` along `axis`, as one undo entry.\n *\n * Pivot: `'each'` (per-item own AABB) unless the caller passes `'union'`,\n * which mirrors every pose about the selection's *visual* union AABB — the\n * rotated ink extents — so items swap sides as well as reflect, or a\n * `SpatialReference`, which mirrors about the center of what it names. A rotated\n * member's own mirrored position needs no expansion: `axisAlignedBounds`\n * keeps the stored box's center, so mirroring either box lands the same place.\n */\nfunction flipSelection(\n selection: SelectionApi,\n scene: Scene<unknown, string, unknown>,\n axis: FlipAxis,\n pivot: FlipPivot,\n applyOps: ((ops: Op[], label: string) => void) | undefined,\n geometryProjection: GeometryProjection | undefined,\n geom: PoseDescriptor<unknown>,\n params: Record<string, unknown> | undefined,\n pointer: PointerContextValue | undefined,\n): void {\n const selected = selection.get();\n if (selected.length === 0) return;\n const frame = scenePoseFrame(scene);\n\n // A group whose pose derives from its children mirrors by mirroring them,\n // about the group's own box where each selected node flips in place.\n const ids: NodeId[] = [];\n const ownPivot = new Map<string, Bounds>();\n for (const id of selected) {\n const members = shapeSources(scene, [id]) as NodeId[];\n const stoodIn = members.length !== 1 || members[0] !== id;\n const box = stoodIn ? visualBoundsViaDescriptor(frame.world(id), geom) : null;\n for (const member of members) {\n if (ids.includes(member)) continue;\n ids.push(member);\n if (box) ownPivot.set(member, box);\n }\n }\n\n const worlds = new Map<string, unknown>();\n for (const id of ids) {\n if (scene.get(id) !== undefined) worlds.set(id as string, frame.world(id));\n }\n let sharedPivot: ReturnType<typeof unionAABB> = null;\n if (pivot === 'union') {\n sharedPivot = unionAABB([...worlds.values()].map((p) => visualBoundsViaDescriptor(p, geom)));\n } else if (pivot !== 'each') {\n sharedPivot = resolveSpatialReference(pivot, actionReferenceSources(params, pointer, scene, frame, geom));\n if (sharedPivot === null) return;\n }\n\n const ops: Op[] = [];\n for (const id of ids) {\n const node = scene.get(id);\n if (!node) continue;\n const world = worlds.get(id as string)!;\n const about = sharedPivot ?? ownPivot.get(id as string) ?? null;\n const flipped = about\n ? flipPoseAboutBounds(world, axis, geom, about)\n : flipPoseViaDescriptor(world, axis, geom);\n ops.push(createTransformOp<unknown>({\n id: id as string,\n from: node.pose,\n to: frame.local(id, flipped),\n label: 'Flip',\n }));\n // Mirror the data-held geometry about the SAME pivot the pose flip uses.\n // A pose-only flip of a bare-AABB pose is identity, so this data op is\n // the only way an asymmetric shape actually mirrors.\n const g = about ?? geom.getBounds(world);\n const dataOp = geometryDataOp(\n geometryProjection,\n { id: id as string, data: node.data, pose: node.pose },\n localMirror(axis, frame, id, g),\n 'Flip',\n );\n if (dataOp) ops.push(dataOp);\n }\n if (ops.length === 0) return;\n\n if (applyOps) applyOps(ops, 'Flip');\n else scene.applyBatch(ops, 'Flip', defaultCommitAdapter(scene, selection.adapterMethods));\n}\n\n/**\n * @experimental\n * Flips the selection. `params.axis` (`'x'` | `'y'`, default `'x'`) picks the\n * direction, and the optional `params.pivot` (a `FlipPivot`, default `'each'`)\n * picks what it mirrors about.\n */\nexport const flipAction: Action & { requires: string[] } = {\n id: 'flip',\n label: 'Flip',\n group: 'flip',\n variants: [\n { key: 'x', label: 'Flip Horizontal', icon: createElement(FlipXIcon), params: { axis: 'x' } },\n { key: 'y', label: 'Flip Vertical', icon: createElement(FlipYIcon), params: { axis: 'y' } },\n ],\n defaultBinding: [\n { spec: { kind: 'key', key: ['h', 'H'], mods: { shift: true } }, opts: { params: { axis: 'x' } } },\n { spec: { kind: 'key', key: ['v', 'V'], mods: { shift: true } }, opts: { params: { axis: 'y' } } },\n ],\n eligible: { capability: 'transforms-selection' },\n requires: ['selection', 'scene', 'applyOps', 'geometryProjection', 'poseDescriptor', 'pointer'],\n invoker: {\n timing: 'immediate',\n run: (deps, params) => {\n const axis = (params?.axis as FlipAxis | undefined) ?? 'x';\n const pivot = (params?.pivot as FlipPivot | undefined) ?? 'each';\n const selection = deps.selection;\n const scene = deps.scene;\n const applyOps = deps.applyOps;\n const geometryProjection = deps.geometryProjection;\n if (!selection || !scene) return;\n flipSelection(\n selection, scene, axis, pivot, applyOps, geometryProjection,\n poseDescriptorOf(deps.poseDescriptor),\n params, deps.pointer,\n );\n },\n },\n enabled: requiresSelection,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"action.js","names":["localMirror","at","frame","id","pivotWorld","theta","mirror","axis","turned","multiply","rotate","translate","flipSelection","selected","selection","scenePoseFrame","scene","ids","ownPivot","members","shapeSources","box","visualBoundsViaDescriptor","geom","member","worlds","sharedPivot","pivot","unionAABB","p","resolveSpatialReference","actionReferenceSources","params","pointer","ops","node","world","about","flipped","flipPoseAboutBounds","flipPoseViaDescriptor","createTransformOp","g","dataOp","geometryDataOp","geometryProjection","applyOps","defaultCommitAdapter","flipAction","createElement","FlipXIcon","FlipYIcon","deps","poseDescriptorOf","requiresSelection"],"sources":["../../../../src/interactions/actions/flip/action.ts"],"sourcesContent":["import type { NodeId, Scene } from 'core/scene/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { shapeSources } from '../selectionSubtree';\nimport { createElement } from 'react';\nimport { FlipXIcon, FlipYIcon } from '../defaults/icons/editIcons';\nimport type { Op } from 'core/ops/types';\nimport { multiply, rotate, translate, type Affine } from '@weasel-js/geom';\nimport { createTransformOp } from 'core/ops/transform';\nimport type { PoseDescriptor } from '../resize/geometry';\nimport { poseDescriptorOf } from '../poseDescriptorDep';\nimport {\n flipPoseAboutBounds,\n flipPoseViaDescriptor,\n type FlipAxis,\n type FlipPivot,\n} from './helpers';\nimport { unionAABB } from 'core/geometry/unionBounds';\nimport { visualBoundsViaDescriptor } from '../align/align';\nimport { scenePoseFrame, type PoseFrame } from '../poseFrame';\nimport { actionReferenceSources, resolveSpatialReference } from '../spatialReference';\nimport type { PointerContextValue } from '@weasel-js/routing/react';\nimport type { RectPose } from 'core/scene/types';\nimport type { Action } from '@weasel-js/routing';\nimport { defaultCommitAdapter } from '../defaultCommitAdapter';\nimport { requiresSelection } from '../defaults/requiresSelection';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport { geometryDataOp, type GeometryProjection } from '../geometryProjection';\n\n/**\n * The world mirror about `axis` through the center of `pivotWorld`, written in\n * the frame `id` stores its data in. `geometryProjection.transform` is given\n * the affine the node's own pose underwent, and data-held geometry is local —\n * so a world mirror has to be conjugated by the frame or an asymmetric shape\n * reflects across the wrong line while its pose lands correctly.\n *\n * The frame's turn comes back as the rotation on a rebased zero-size pose at\n * the pivot point, so no composition strategy has to expose one.\n */\nfunction localMirror(\n axis: FlipAxis,\n frame: PoseFrame<unknown>,\n id: Parameters<PoseFrame<unknown>['local']>[0],\n pivotWorld: { x: number; y: number; width: number; height: number },\n): Affine {\n const at = frame.local(id, {\n x: pivotWorld.x + pivotWorld.width / 2,\n y: pivotWorld.y + pivotWorld.height / 2,\n width: 0,\n height: 0,\n rotation: 0,\n }) as RectPose;\n const theta = at.rotation ?? 0;\n const mirror: Affine = axis === 'x' ? [-1, 0, 0, 1, 0, 0] : [1, 0, 0, -1, 0, 0];\n const turned = multiply(multiply(rotate(theta), mirror), rotate(-theta));\n return multiply(translate(at.x, at.y), multiply(turned, translate(-at.x, -at.y)));\n}\n\n/**\n * Flip the current selection in `scene` along `axis`, as one undo entry.\n *\n * Pivot: `'each'` (per-item own AABB) unless the caller passes `'union'`,\n * which mirrors every pose about the selection's *visual* union AABB — the\n * rotated ink extents — so items swap sides as well as reflect, or a\n * `SpatialReference`, which mirrors about the center of what it names. A rotated\n * member's own mirrored position needs no expansion: `axisAlignedBounds`\n * keeps the stored box's center, so mirroring either box lands the same place.\n */\nfunction flipSelection(\n selection: SelectionApi,\n scene: Scene<unknown, string, unknown>,\n axis: FlipAxis,\n pivot: FlipPivot,\n applyOps: ((ops: Op[], label: string) => void) | undefined,\n geometryProjection: GeometryProjection | undefined,\n geom: PoseDescriptor<unknown>,\n params: Record<string, unknown> | undefined,\n pointer: PointerContextValue | undefined,\n): void {\n const selected = selection.get();\n if (selected.length === 0) return;\n const frame = scenePoseFrame(scene);\n\n // A group whose pose derives from its children mirrors by mirroring them,\n // about the group's own box where each selected node flips in place.\n const ids: NodeId[] = [];\n const ownPivot = new Map<string, Bounds>();\n for (const id of selected) {\n const members = shapeSources(scene, [id]) as NodeId[];\n const stoodIn = members.length !== 1 || members[0] !== id;\n const box = stoodIn ? visualBoundsViaDescriptor(frame.world(id), geom) : null;\n for (const member of members) {\n if (ids.includes(member)) continue;\n ids.push(member);\n if (box) ownPivot.set(member, box);\n }\n }\n\n const worlds = new Map<string, unknown>();\n for (const id of ids) {\n if (scene.get(id) !== undefined) worlds.set(id as string, frame.world(id));\n }\n let sharedPivot: ReturnType<typeof unionAABB> = null;\n if (pivot === 'union') {\n sharedPivot = unionAABB([...worlds.values()].map((p) => visualBoundsViaDescriptor(p, geom)));\n } else if (pivot !== 'each') {\n sharedPivot = resolveSpatialReference(pivot, actionReferenceSources(params, pointer, scene, frame, geom));\n if (sharedPivot === null) return;\n }\n\n const ops: Op[] = [];\n for (const id of ids) {\n const node = scene.get(id);\n if (!node) continue;\n const world = worlds.get(id as string)!;\n const about = sharedPivot ?? ownPivot.get(id as string) ?? null;\n const flipped = about\n ? flipPoseAboutBounds(world, axis, geom, about)\n : flipPoseViaDescriptor(world, axis, geom);\n ops.push(createTransformOp<unknown>({\n id: id as string,\n from: node.pose,\n to: frame.local(id, flipped),\n label: 'Flip',\n }));\n // Mirror the data-held geometry about the SAME pivot the pose flip uses.\n // A pose-only flip of a bare-AABB pose is identity, so this data op is\n // the only way an asymmetric shape actually mirrors.\n const g = about ?? geom.getBounds(world);\n const dataOp = geometryDataOp(\n geometryProjection,\n { id: id as string, data: node.data, pose: node.pose },\n localMirror(axis, frame, id, g),\n 'Flip',\n );\n if (dataOp) ops.push(dataOp);\n }\n if (ops.length === 0) return;\n\n if (applyOps) applyOps(ops, 'Flip');\n else scene.applyBatch(ops, 'Flip', defaultCommitAdapter(scene, selection.adapterMethods));\n}\n\n/**\n * @experimental\n * Flips the selection. `params.axis` (`'x'` | `'y'`, default `'x'`) picks the\n * direction, and the optional `params.pivot` (a `FlipPivot`, default `'each'`)\n * picks what it mirrors about.\n */\nexport const flipAction: Action & { requires: string[] } = {\n id: 'flip',\n label: 'Flip',\n group: 'flip',\n variants: [\n { key: 'x', label: 'Flip Horizontal', icon: createElement(FlipXIcon), params: { axis: 'x' } },\n { key: 'y', label: 'Flip Vertical', icon: createElement(FlipYIcon), params: { axis: 'y' } },\n ],\n defaultBinding: [\n { spec: { kind: 'key', key: ['h', 'H'], mods: { shift: true } }, opts: { params: { axis: 'x' } } },\n { spec: { kind: 'key', key: ['v', 'V'], mods: { shift: true } }, opts: { params: { axis: 'y' } } },\n ],\n eligible: { capability: 'transforms-selection' },\n requires: ['selection', 'scene', 'applyOps', 'geometryProjection', 'poseDescriptor', 'pointer'],\n invoker: {\n timing: 'immediate',\n run: (deps, params) => {\n const axis = (params?.axis as FlipAxis | undefined) ?? 'x';\n const pivot = (params?.pivot as FlipPivot | undefined) ?? 'each';\n const selection = deps.selection;\n const scene = deps.scene;\n const applyOps = deps.applyOps;\n const geometryProjection = deps.geometryProjection;\n if (!selection || !scene) return;\n flipSelection(\n selection, scene, axis, pivot, applyOps, geometryProjection,\n poseDescriptorOf(deps.poseDescriptor),\n params, deps.pointer,\n );\n },\n },\n enabled: requiresSelection,\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"geometryProjection.js","names":["geometryDataOp","projection","node","next","m","createSetDataOp"],"sources":["../../../src/interactions/actions/geometryProjection.ts"],"sourcesContent":["import { type Mat3 } from '@weasel-js/geom';\nimport { createSetDataOp } from 'core/ops/setData';\nimport type { Op } from 'core/ops/types';\n\n/** Optional consumer seam: given a node and the affine `m` that a pose-transform\n * action applied to the node's POSE, return updated `data` with the node's\n * data-held geometry transformed by `m`, or `null` if this node has no\n * data-held geometry (the kit leaves `data` alone). */\nexport interface GeometryProjection {\n transform(node: { id?: string; data: unknown; pose: unknown }, m: Mat3): unknown | null;\n}\n\n/** Build the `setData` op for a node when a geometryProjection is wired and\n * returns non-null. Returns `undefined` (no op) otherwise — keeping the data\n * op strictly opt-in so adapters without `setData` are never invoked. */\nexport function geometryDataOp(\n projection: GeometryProjection | undefined,\n node: { id: string; data: unknown; pose: unknown } | undefined,\n m: Mat3,\n label: string,\n): Op | undefined {\n if (!projection || !node) return undefined;\n const next = projection.transform(node, m);\n if (next == null) return undefined;\n return createSetDataOp({ id: node.id, from: node.data, to: next, label, coalesceKey: `setData:${node.id}` });\n}\n"],"mappings":";;;AAeA,SAAgBA,EACd,GACA,GACA,GACA,GACgB;CAChB,IAAI,CAACC,KAAc,CAACC,GAAM;CAC1B,IAAMC,IAAOF,EAAW,UAAUC,GAAME,CAAC;CACzC,IAAID,KAAQ,MACZ,OAAOE,EAAgB;EAAE,IAAIH,EAAK;EAAI,MAAMA,EAAK;EAAM,IAAIC;EAAM;EAAO,aAAa,WAAWD,EAAK;CAAK,CAAC;AAC7G"}
1
+ {"version":3,"file":"geometryProjection.js","names":["geometryDataOp","projection","node","next","m","createSetDataOp"],"sources":["../../../src/interactions/actions/geometryProjection.ts"],"sourcesContent":["import { type Affine } from '@weasel-js/geom';\nimport { createSetDataOp } from 'core/ops/setData';\nimport type { Op } from 'core/ops/types';\n\n/** Optional consumer seam: given a node and the affine `m` that a pose-transform\n * action applied to the node's POSE, return updated `data` with the node's\n * data-held geometry transformed by `m`, or `null` if this node has no\n * data-held geometry (the kit leaves `data` alone). */\nexport interface GeometryProjection {\n transform(node: { id?: string; data: unknown; pose: unknown }, m: Affine): unknown | null;\n}\n\n/** Build the `setData` op for a node when a geometryProjection is wired and\n * returns non-null. Returns `undefined` (no op) otherwise — keeping the data\n * op strictly opt-in so adapters without `setData` are never invoked. */\nexport function geometryDataOp(\n projection: GeometryProjection | undefined,\n node: { id: string; data: unknown; pose: unknown } | undefined,\n m: Affine,\n label: string,\n): Op | undefined {\n if (!projection || !node) return undefined;\n const next = projection.transform(node, m);\n if (next == null) return undefined;\n return createSetDataOp({ id: node.id, from: node.data, to: next, label, coalesceKey: `setData:${node.id}` });\n}\n"],"mappings":";;;AAeA,SAAgBA,EACd,GACA,GACA,GACA,GACgB;CAChB,IAAI,CAACC,KAAc,CAACC,GAAM;CAC1B,IAAMC,IAAOF,EAAW,UAAUC,GAAME,CAAC;CACzC,IAAID,KAAQ,MACZ,OAAOE,EAAgB;EAAE,IAAIH,EAAK;EAAI,MAAMA,EAAK;EAAM,IAAIC;EAAM;EAAO,aAAa,WAAWD,EAAK;CAAK,CAAC;AAC7G"}
@@ -1,13 +1,15 @@
1
- import { runBehaviorSettled as e } from "../gestureLifecycle.js";
2
- import { inPlane as t, insertLayer as n } from "../planeInput.js";
3
- import { shapeKindInfo as r } from "../../../core/shapeKinds.js";
4
- import { insertKind as i, insertShape as a } from "./shape.js";
5
- import { cancelInsert as o, commitInsert as s } from "./commit.js";
6
- import { beginInsert as c } from "./session.js";
7
- import { nextFrame as l } from "./frame.js";
8
- import { resolveParams as u } from "@weasel-js/routing";
1
+ import { scenePaintedPoseOf as e } from "../../../canvas/derivedPath.js";
2
+ import { runBehaviorSettled as t } from "../gestureLifecycle.js";
3
+ import { inPlane as n, insertLayer as r } from "../planeInput.js";
4
+ import { shapeKindInfo as i } from "../../../core/shapeKinds.js";
5
+ import { copiesWithin as a } from "../../../canvas/copiesAbove.js";
6
+ import { insertKind as o, insertShape as s } from "./shape.js";
7
+ import { cancelInsert as c, commitInsert as l } from "./commit.js";
8
+ import { beginInsert as u } from "./session.js";
9
+ import { carryStart as d, nextFrame as f } from "./frame.js";
10
+ import { resolveParams as p } from "@weasel-js/routing";
9
11
  //#region src/interactions/actions/insert/action.ts
10
- var d = t({
12
+ var m = n({
11
13
  id: "insert",
12
14
  label: "Insert",
13
15
  group: "insert",
@@ -15,6 +17,7 @@ var d = t({
15
17
  requires: [
16
18
  "insert",
17
19
  "insertLive",
20
+ "insertInto",
18
21
  "selection",
19
22
  "snap",
20
23
  "textEdit",
@@ -24,49 +27,53 @@ var d = t({
24
27
  ],
25
28
  invoker: {
26
29
  timing: "ongoing",
27
- start(t, n) {
28
- let d = c(t, n);
29
- if (!d) return {};
30
- let { session: f, readers: p } = d, m = d.frame, h = {
30
+ start(n, r) {
31
+ let m = u(n, r);
32
+ if (!m) return {};
33
+ let { session: h, readers: g } = m, _ = m.frame, v = {
31
34
  open: !0,
32
- userRotation: 0
33
- }, g = t.deps.insertLive;
34
- return g && (g.gesture = h), {
35
+ userRotation: 0,
36
+ repositioning: !1
37
+ }, y = n.deps.insertLive;
38
+ y && (y.gesture = v);
39
+ let b = n.deps.scene, x = n.deps.insertInto?.get() ?? null, S = b ? a(b, x, e(b)) : [];
40
+ return {
35
41
  kind: "insert",
36
42
  onMove(e) {
37
- m = l(f, p, m, e);
43
+ v.repositioning && d(h, _.pointer, e.world), _ = f(h, g, _, e);
38
44
  },
39
45
  overlay() {
40
- if (!h.open) return null;
41
- let e = u(f.opts?.params);
42
- if (!r(i(e))?.insertPreview) return null;
43
- let { bounds: t, extras: n, start: o } = a(e, m, m.points, h.userRotation);
46
+ if (!v.open) return null;
47
+ let e = p(h.opts?.params);
48
+ if (!i(o(e))?.insertPreview) return null;
49
+ let { bounds: t, extras: n, start: r } = s(e, _, _.points, v.userRotation);
44
50
  return {
45
51
  kind: "insertPreview",
46
52
  shape: n.kind,
47
53
  bounds: t,
48
54
  extras: n,
49
55
  anchorPoint: {
50
- x: o.x,
51
- y: o.y
52
- }
56
+ x: r.x,
57
+ y: r.y
58
+ },
59
+ ...S.length > 0 ? { copies: S } : {}
53
60
  };
54
61
  },
55
- onEnd(t, n) {
56
- h.open = !1, g?.gesture === h && (g.gesture = null);
62
+ onEnd(e, n) {
63
+ v.open = !1, y?.gesture === v && (y.gesture = null);
57
64
  let r = !1;
58
65
  try {
59
- n === "cancel" ? o(f) : r = s(f, m, t.drag?.points ?? m.points, h.userRotation);
66
+ n === "cancel" ? c(h) : r = l(h, _, e.drag?.points ?? _.points, v.userRotation);
60
67
  } finally {
61
- e(f.behaviors, f.gestureCtx, r);
68
+ t(h.behaviors, h.gestureCtx, r);
62
69
  }
63
70
  }
64
71
  };
65
72
  }
66
73
  },
67
74
  enabled: () => !0
68
- }, n);
75
+ }, r);
69
76
  //#endregion
70
- export { d as insertAction };
77
+ export { m as insertAction };
71
78
 
72
79
  //# sourceMappingURL=action.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"action.js","names":["insertAction","inPlane","begun","beginInsert","ctx","opts","frame","gesture","live","nextFrame","session","readers","moveCtx","resolved","resolveParams","shapeKindInfo","insertKind","insertShape","extras","start","committed","reason","cancelInsert","commitInsert","endCtx","insertLayer"],"sources":["../../../../src/interactions/actions/insert/action.ts"],"sourcesContent":["import type { Action } from '@weasel-js/routing';\nimport type { InvocationCtx, OngoingHandle, BindingOpts, OngoingOverlay } from '@weasel-js/routing';\nimport { resolveParams } from '@weasel-js/routing';\nimport { shapeKindInfo } from 'core/shapeKinds';\nimport type { KitInsertShape } from 'core/shapeKinds';\nimport { runBehaviorSettled } from '../gestureLifecycle';\nimport { inPlane, insertLayer } from '../planeInput';\nimport { cancelInsert, commitInsert } from './commit';\nimport { nextFrame } from './frame';\nimport type { InsertLiveGesture } from './live';\nimport { beginInsert } from './session';\nimport { insertKind, insertShape } from './shape';\n\n/**\n * @experimental\n * Drag-to-insert. The node `kind` is read from `opts.params.kind`.\n *\n * Grid snapping comes from the optional `snap` dep, applied to the drag's\n * start and current point (never to freehand pencil samples). Behaviors from\n * the binding's `opts.behaviors` (`InsertBehavior[]`) run over the two points\n * after that; on release the first answer wins (`null` aborts, `Op[]` commits\n * in place of the insert).\n *\n * The new node has no scene id until commit, so the live preview is the\n * `overlay()` surface with `kind: 'insertPreview'`. A consumer-defined kind\n * the kit cannot draw skips the preview and still commits on release.\n */\nexport const insertAction: Action & { requires: string[] } = inPlane({\n id: 'insert',\n label: 'Insert',\n group: 'insert',\n eligible: { capability: 'creates-shapes' },\n requires: ['insert', 'insertLive', 'selection', 'snap', 'textEdit', 'scene', 'applyOps', 'view'],\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle {\n const begun = beginInsert(ctx, opts);\n if (!begun) return {};\n const { session, readers } = begun;\n let frame = begun.frame;\n const gesture: InsertLiveGesture = { open: true, userRotation: 0 };\n const live = ctx.deps.insertLive;\n if (live) live.gesture = gesture;\n\n return {\n kind: 'insert',\n onMove(moveCtx: InvocationCtx): void {\n frame = nextFrame(session, readers, frame, moveCtx);\n },\n overlay(): OngoingOverlay | null {\n if (!gesture.open) return null;\n // Resolve params on every overlay() so thunked params (polygon\n // sides ticking via ArrowUp mid-drag) reflect in the live preview.\n const resolved = resolveParams(session.opts?.params);\n if (!shapeKindInfo(insertKind(resolved))?.insertPreview) return null;\n const { bounds, extras, start } = insertShape(resolved, frame, frame.points, gesture.userRotation);\n return {\n kind: 'insertPreview',\n shape: extras.kind as KitInsertShape,\n bounds,\n extras,\n // Anchor dot at the click point: radial shapes have no vertex\n // there, and in center mode it marks the growth axis.\n anchorPoint: { x: start.x, y: start.y },\n };\n },\n onEnd(endCtx: InvocationCtx, reason: 'commit' | 'cancel'): void {\n gesture.open = false;\n if (live?.gesture === gesture) live.gesture = null;\n let committed = false;\n try {\n if (reason === 'cancel') cancelInsert(session);\n else committed = commitInsert(session, frame, endCtx.drag?.points ?? frame.points, gesture.userRotation);\n } finally {\n runBehaviorSettled(session.behaviors, session.gestureCtx, committed);\n }\n },\n };\n },\n },\n /** Insert needs no selection; anything but `true` blocks dispatcher-routed inserts. */\n enabled: () => true as const,\n}, insertLayer);\n"],"mappings":";;;;;;;;;AA2BA,IAAaA,IAAgDC,EAAQ;CACnE,IAAI;CACJ,OAAO;CACP,OAAO;CACP,UAAU,EAAE,YAAY,iBAAiB;CACzC,UAAU;EAAC;EAAU;EAAc;EAAa;EAAQ;EAAY;EAAS;EAAY;CAAM;CAC/F,SAAS;EACP,QAAQ;EACR,MAAM,GAAoB,GAAmC;GAC3D,IAAMC,IAAQC,EAAYC,GAAKC,CAAI;GACnC,IAAI,CAACH,GAAO,OAAO,CAAC;GACpB,IAAM,EAAE,YAAS,eAAYA,GACzBI,IAAQJ,EAAM,OACZK,IAA6B;IAAE,MAAM;IAAM,cAAc;GAAE,GAC3DC,IAAOJ,EAAI,KAAK;GAGtB,OAFII,MAAM,EAAK,UAAUD,IAElB;IACL,MAAM;IACN,OAAO,GAA8B;KACnC,IAAQE,EAAUC,GAASC,GAASL,GAAOM,CAAO;IACpD;IACA,UAAiC;KAC/B,IAAI,CAACL,EAAQ,MAAM,OAAO;KAG1B,IAAMM,IAAWC,EAAcJ,EAAQ,MAAM,MAAM;KACnD,IAAI,CAACK,EAAcC,EAAWH,CAAQ,CAAC,CAAC,EAAE,eAAe,OAAO;KAChE,IAAM,EAAE,WAAQ,WAAQ,aAAUI,EAAYJ,GAAUP,GAAOA,EAAM,QAAQC,EAAQ,YAAY;KACjG,OAAO;MACL,MAAM;MACN,OAAOW,EAAO;MACd;MACA;MAGA,aAAa;OAAE,GAAGC,EAAM;OAAG,GAAGA,EAAM;MAAE;KACxC;IACF;IACA,MAAM,GAAuB,GAAmC;KAE9D,AADA,EAAQ,OAAO,IACXX,GAAM,YAAYD,MAAS,EAAK,UAAU;KAC9C,IAAIa,IAAY;KAChB,IAAI;MACF,AAAIC,MAAW,WAAUC,EAAaZ,CAAO,IACxC,IAAYa,EAAab,GAASJ,GAAOkB,EAAO,MAAM,UAAUlB,EAAM,QAAQC,EAAQ,YAAY;KACzG,UAAU;MACR,EAAmBG,EAAQ,WAAWA,EAAQ,YAAYU,CAAS;KACrE;IACF;GACF;EACF;CACF;CAEA,eAAe;AACjB,GAAGK,CAAW"}
1
+ {"version":3,"file":"action.js","names":["insertAction","inPlane","begun","beginInsert","ctx","opts","frame","gesture","live","scene","into","copies","copiesWithin","scenePaintedPoseOf","carryStart","session","moveCtx","nextFrame","readers","resolved","resolveParams","shapeKindInfo","insertKind","insertShape","extras","start","committed","reason","cancelInsert","commitInsert","endCtx","insertLayer"],"sources":["../../../../src/interactions/actions/insert/action.ts"],"sourcesContent":["import type { Action, NodeId } from '@weasel-js/routing';\nimport type { InvocationCtx, OngoingHandle, BindingOpts, OngoingOverlay } from '@weasel-js/routing';\nimport { resolveParams } from '@weasel-js/routing';\nimport { shapeKindInfo } from 'core/shapeKinds';\nimport type { KitInsertShape } from 'core/shapeKinds';\nimport { runBehaviorSettled } from '../gestureLifecycle';\nimport { inPlane, insertLayer } from '../planeInput';\nimport { copiesWithin } from 'canvas/copiesAbove';\nimport { scenePaintedPoseOf } from 'canvas/derivedPath';\nimport { cancelInsert, commitInsert } from './commit';\nimport { carryStart, nextFrame } from './frame';\nimport type { InsertLiveGesture } from './live';\nimport { beginInsert } from './session';\nimport { insertKind, insertShape } from './shape';\n\n/**\n * @experimental\n * Drag-to-insert. The node `kind` is read from `opts.params.kind`.\n *\n * Grid snapping comes from the optional `snap` dep, applied to the drag's\n * start and current point (never to freehand pencil samples). Behaviors from\n * the binding's `opts.behaviors` (`InsertBehavior[]`) run over the two points\n * after that; on release the first answer wins (`null` aborts, `Op[]` commits\n * in place of the insert).\n *\n * The new node has no scene id until commit, so the live preview is the\n * `overlay()` surface with `kind: 'insertPreview'`. A consumer-defined kind\n * the kit cannot draw skips the preview and still commits on release.\n */\nexport const insertAction: Action & { requires: string[] } = inPlane({\n id: 'insert',\n label: 'Insert',\n group: 'insert',\n eligible: { capability: 'creates-shapes' },\n requires: ['insert', 'insertLive', 'insertInto', 'selection', 'snap', 'textEdit', 'scene', 'applyOps', 'view'],\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle {\n const begun = beginInsert(ctx, opts);\n if (!begun) return {};\n const { session, readers } = begun;\n let frame = begun.frame;\n const gesture: InsertLiveGesture = { open: true, userRotation: 0, repositioning: false };\n const live = ctx.deps.insertLive;\n if (live) live.gesture = gesture;\n // Where the shape will land decides the copies it will be drawn in.\n const scene = ctx.deps.scene;\n const into = (ctx.deps.insertInto?.get() ?? null) as NodeId | null;\n const copies = scene ? copiesWithin(scene, into, scenePaintedPoseOf(scene)) : [];\n\n return {\n kind: 'insert',\n onMove(moveCtx: InvocationCtx): void {\n if (gesture.repositioning) carryStart(session, frame.pointer, moveCtx.world);\n frame = nextFrame(session, readers, frame, moveCtx);\n },\n overlay(): OngoingOverlay | null {\n if (!gesture.open) return null;\n // Resolve params on every overlay() so thunked params (polygon\n // sides ticking via ArrowUp mid-drag) reflect in the live preview.\n const resolved = resolveParams(session.opts?.params);\n if (!shapeKindInfo(insertKind(resolved))?.insertPreview) return null;\n const { bounds, extras, start } = insertShape(resolved, frame, frame.points, gesture.userRotation);\n return {\n kind: 'insertPreview',\n shape: extras.kind as KitInsertShape,\n bounds,\n extras,\n // Anchor dot at the click point: radial shapes have no vertex\n // there, and in center mode it marks the growth axis.\n anchorPoint: { x: start.x, y: start.y },\n ...(copies.length > 0 ? { copies } : {}),\n };\n },\n onEnd(endCtx: InvocationCtx, reason: 'commit' | 'cancel'): void {\n gesture.open = false;\n if (live?.gesture === gesture) live.gesture = null;\n let committed = false;\n try {\n if (reason === 'cancel') cancelInsert(session);\n else committed = commitInsert(session, frame, endCtx.drag?.points ?? frame.points, gesture.userRotation);\n } finally {\n runBehaviorSettled(session.behaviors, session.gestureCtx, committed);\n }\n },\n };\n },\n },\n /** Insert needs no selection; anything but `true` blocks dispatcher-routed inserts. */\n enabled: () => true as const,\n}, insertLayer);\n"],"mappings":";;;;;;;;;;;AA6BA,IAAaA,IAAgDC,EAAQ;CACnE,IAAI;CACJ,OAAO;CACP,OAAO;CACP,UAAU,EAAE,YAAY,iBAAiB;CACzC,UAAU;EAAC;EAAU;EAAc;EAAc;EAAa;EAAQ;EAAY;EAAS;EAAY;CAAM;CAC7G,SAAS;EACP,QAAQ;EACR,MAAM,GAAoB,GAAmC;GAC3D,IAAMC,IAAQC,EAAYC,GAAKC,CAAI;GACnC,IAAI,CAACH,GAAO,OAAO,CAAC;GACpB,IAAM,EAAE,YAAS,eAAYA,GACzBI,IAAQJ,EAAM,OACZK,IAA6B;IAAE,MAAM;IAAM,cAAc;IAAG,eAAe;GAAM,GACjFC,IAAOJ,EAAI,KAAK;GACtB,AAAII,MAAM,EAAK,UAAUD;GAEzB,IAAME,IAAQL,EAAI,KAAK,OACjBM,IAAQN,EAAI,KAAK,YAAY,IAAI,KAAK,MACtCO,IAASF,IAAQG,EAAaH,GAAOC,GAAMG,EAAmBJ,CAAK,CAAC,IAAI,CAAC;GAE/E,OAAO;IACL,MAAM;IACN,OAAO,GAA8B;KAEnC,AADIF,EAAQ,iBAAeO,EAAWC,GAAST,EAAM,SAASU,EAAQ,KAAK,GAC3E,IAAQC,EAAUF,GAASG,GAASZ,GAAOU,CAAO;IACpD;IACA,UAAiC;KAC/B,IAAI,CAACT,EAAQ,MAAM,OAAO;KAG1B,IAAMY,IAAWC,EAAcL,EAAQ,MAAM,MAAM;KACnD,IAAI,CAACM,EAAcC,EAAWH,CAAQ,CAAC,CAAC,EAAE,eAAe,OAAO;KAChE,IAAM,EAAE,WAAQ,WAAQ,aAAUI,EAAYJ,GAAUb,GAAOA,EAAM,QAAQC,EAAQ,YAAY;KACjG,OAAO;MACL,MAAM;MACN,OAAOiB,EAAO;MACd;MACA;MAGA,aAAa;OAAE,GAAGC,EAAM;OAAG,GAAGA,EAAM;MAAE;MACtC,GAAId,EAAO,SAAS,IAAI,EAAE,UAAO,IAAI,CAAC;KACxC;IACF;IACA,MAAM,GAAuB,GAAmC;KAE9D,AADA,EAAQ,OAAO,IACXH,GAAM,YAAYD,MAAS,EAAK,UAAU;KAC9C,IAAImB,IAAY;KAChB,IAAI;MACF,AAAIC,MAAW,WAAUC,EAAab,CAAO,IACxC,IAAYc,EAAad,GAAST,GAAOwB,EAAO,MAAM,UAAUxB,EAAM,QAAQC,EAAQ,YAAY;KACzG,UAAU;MACR,EAAmBQ,EAAQ,WAAWA,EAAQ,YAAYW,CAAS;KACrE;IACF;GACF;EACF;CACF;CAEA,eAAe;AACjB,GAAGK,CAAW"}
@@ -27,7 +27,14 @@ function o(o, s, c, l) {
27
27
  current: m
28
28
  };
29
29
  }
30
- function s(e, t, n, r) {
30
+ function s(e, t, n) {
31
+ let r = e.snap(t), a = e.snap(n), o = e.gestureCtx.origin, s = o.get(i);
32
+ o.set(i, {
33
+ x: s.x + a.x - r.x,
34
+ y: s.y + a.y - r.y
35
+ });
36
+ }
37
+ function c(e, t, n, r) {
31
38
  let i = {
32
39
  pointer: {
33
40
  x: r.world.x,
@@ -43,6 +50,6 @@ function s(e, t, n, r) {
43
50
  };
44
51
  }
45
52
  //#endregion
46
- export { s as nextFrame };
53
+ export { s as carryStart, c as nextFrame };
47
54
 
48
55
  //# sourceMappingURL=frame.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"frame.js","names":["shapeEndpoints","resolved","resolveParams","session","kind","\"kind\"","gctx","start","GESTURE_KEY","current","raw","snapTo15Degrees","moveCtx","pointerStateOf","readers","mode","effectiveOriginMode","\"originMode\"","b","bounds","computeBounds","r","nextFrame","prev"],"sources":["../../../../src/interactions/actions/insert/frame.ts"],"sourcesContent":["import type { InvocationCtx } from '@weasel-js/routing';\nimport { pointerStateOf } from '../pointerState';\nimport { resolveParams } from '@weasel-js/routing';\nimport type { GestureReaders } from '../../gestures/shared/screenTolerance';\nimport type { InsertPoint } from '../../gestures/types';\nimport { computeBounds, effectiveOriginMode, snapTo15Degrees } from './geometry';\nimport { GESTURE_KEY, type InsertFrame, type InsertSession } from './session';\n\n/**\n * Shape the raw pointer into the drag's endpoints, in order: the line tool's\n * Shift-constrain (on the raw pointer, around the shaped start, so the locked\n * angle survives), the `snap` dep, then each behavior's `onMove`, each seeing\n * the points the one before it returned.\n */\nfunction shapeEndpoints(\n session: Pick<InsertSession, 'opts' | 'snap' | 'behaviors' | 'gestureCtx'>,\n readers: GestureReaders,\n raw: Pick<InsertFrame, 'pointer' | 'altHeld' | 'shiftHeld'>,\n moveCtx: Pick<InvocationCtx, 'modifiers' | 'screen'>,\n): { start: InsertPoint; current: InsertPoint } {\n const resolved = resolveParams(session.opts?.params);\n const kind = (resolved?.['kind'] as string | undefined) ?? 'rect';\n const gctx = session.gestureCtx;\n let start = gctx.origin.get(GESTURE_KEY) as InsertPoint;\n let current = { x: raw.pointer.x, y: raw.pointer.y };\n if (kind === 'line' && raw.shiftHeld) {\n current = snapTo15Degrees(start, current);\n }\n current = session.snap(current);\n\n if (session.behaviors.length > 0) {\n gctx.modifiers = { ...moveCtx.modifiers };\n gctx.pointer = pointerStateOf(raw.pointer, moveCtx);\n gctx.view = readers.view();\n gctx.plane = readers.plane();\n gctx.current.set(GESTURE_KEY, current);\n const mode = effectiveOriginMode(resolved?.['originMode'], raw.altHeld);\n for (const b of session.behaviors) {\n const bounds = computeBounds(start.x, start.y, current.x, current.y, mode);\n const r = b.onMove?.(gctx, { start, current, bounds, pose: bounds });\n if (!r) continue;\n if (r.start) start = r.start;\n if (r.current) current = r.current;\n }\n gctx.current.set(GESTURE_KEY, current);\n }\n return { start, current };\n}\n\n/** One pump event's pointer, modifiers, and trail, and the endpoints they\n * shape to. Each behavior's `onMove` runs once per call. */\nexport function nextFrame(\n session: InsertSession,\n readers: GestureReaders,\n prev: Pick<InsertFrame, 'points'>,\n moveCtx: InvocationCtx,\n): InsertFrame {\n const raw = {\n pointer: { x: moveCtx.world.x, y: moveCtx.world.y },\n // Each pointermove carries fresh modifier state from the dispatcher;\n // releasing / pressing a key mid-drag takes effect on the next move tick\n // (true modifier-only events don't fire without a cursor change).\n altHeld: moveCtx.modifiers.alt,\n shiftHeld: moveCtx.modifiers.shift,\n };\n return {\n ...raw,\n // The dispatcher mutates its own per-gesture trail in place but\n // attaches the array reference to each InvocationCtx.drag — keep\n // the latest reference in case the dispatcher swaps it.\n points: moveCtx.drag?.points ? moveCtx.drag.points : prev.points,\n shaped: shapeEndpoints(session, readers, raw, moveCtx),\n };\n}\n"],"mappings":";;;;;AAcA,SAASA,EACP,GACA,GACA,GACA,GAC8C;CAC9C,IAAMC,IAAWC,EAAcC,EAAQ,MAAM,MAAM,GAC7CC,IAAQH,GAAWI,QAAkC,QACrDC,IAAOH,EAAQ,YACjBI,IAAQD,EAAK,OAAO,IAAIE,CAAW,GACnCC,IAAU;EAAE,GAAGC,EAAI,QAAQ;EAAG,GAAGA,EAAI,QAAQ;CAAE;CAMnD,IALIN,MAAS,UAAUM,EAAI,cACzB,IAAUC,EAAgBJ,GAAOE,CAAO,IAE1C,IAAUN,EAAQ,KAAKM,CAAO,GAE1BN,EAAQ,UAAU,SAAS,GAAG;EAKhC,AAJA,EAAK,YAAY,EAAE,GAAGS,EAAQ,UAAU,GACxC,EAAK,UAAUC,EAAeH,EAAI,SAASE,CAAO,GAClD,EAAK,OAAOE,EAAQ,KAAK,GACzB,EAAK,QAAQA,EAAQ,MAAM,GAC3BR,EAAK,QAAQ,IAAIE,GAAaC,CAAO;EACrC,IAAMM,IAAOC,EAAoBf,GAAWgB,YAAeP,EAAI,OAAO;EACtE,KAAK,IAAMQ,KAAKf,EAAQ,WAAW;GACjC,IAAMgB,IAASC,EAAcb,EAAM,GAAGA,EAAM,GAAGE,EAAQ,GAAGA,EAAQ,GAAGM,CAAI,GACnEM,IAAIH,EAAE,SAASZ,GAAM;IAAE;IAAO;IAAS;IAAQ,MAAMa;GAAO,CAAC;GACnE,AAAKE,MACDA,EAAE,UAAO,IAAQA,EAAE,QACnBA,EAAE,YAAS,IAAUA,EAAE;EAC7B;EACA,EAAK,QAAQ,IAAIb,GAAaC,CAAO;CACvC;CACA,OAAO;EAAE;EAAO;CAAQ;AAC1B;AAIA,SAAgBa,EACd,GACA,GACA,GACA,GACa;CACb,IAAMZ,IAAM;EACV,SAAS;GAAE,GAAGE,EAAQ,MAAM;GAAG,GAAGA,EAAQ,MAAM;EAAE;EAIlD,SAASA,EAAQ,UAAU;EAC3B,WAAWA,EAAQ,UAAU;CAC/B;CACA,OAAO;EACL,GAAGF;EAIH,QAAQE,EAAQ,MAAM,SAASA,EAAQ,KAAK,SAASW,EAAK;EAC1D,QAAQvB,EAAeG,GAASW,GAASJ,GAAKE,CAAO;CACvD;AACF"}
1
+ {"version":3,"file":"frame.js","names":["shapeEndpoints","resolved","resolveParams","session","kind","\"kind\"","gctx","start","GESTURE_KEY","current","raw","snapTo15Degrees","moveCtx","pointerStateOf","readers","mode","effectiveOriginMode","\"originMode\"","b","bounds","computeBounds","r","carryStart","a","from","to","origin","nextFrame","prev"],"sources":["../../../../src/interactions/actions/insert/frame.ts"],"sourcesContent":["import type { InvocationCtx } from '@weasel-js/routing';\nimport { pointerStateOf } from '../pointerState';\nimport { resolveParams } from '@weasel-js/routing';\nimport type { GestureReaders } from '../../gestures/shared/screenTolerance';\nimport type { InsertPoint } from '../../gestures/types';\nimport { computeBounds, effectiveOriginMode, snapTo15Degrees } from './geometry';\nimport { GESTURE_KEY, type InsertFrame, type InsertSession } from './session';\n\n/**\n * Shape the raw pointer into the drag's endpoints, in order: the line tool's\n * Shift-constrain (on the raw pointer, around the shaped start, so the locked\n * angle survives), the `snap` dep, then each behavior's `onMove`, each seeing\n * the points the one before it returned.\n */\nfunction shapeEndpoints(\n session: Pick<InsertSession, 'opts' | 'snap' | 'behaviors' | 'gestureCtx'>,\n readers: GestureReaders,\n raw: Pick<InsertFrame, 'pointer' | 'altHeld' | 'shiftHeld'>,\n moveCtx: Pick<InvocationCtx, 'modifiers' | 'screen'>,\n): { start: InsertPoint; current: InsertPoint } {\n const resolved = resolveParams(session.opts?.params);\n const kind = (resolved?.['kind'] as string | undefined) ?? 'rect';\n const gctx = session.gestureCtx;\n let start = gctx.origin.get(GESTURE_KEY) as InsertPoint;\n let current = { x: raw.pointer.x, y: raw.pointer.y };\n if (kind === 'line' && raw.shiftHeld) {\n current = snapTo15Degrees(start, current);\n }\n current = session.snap(current);\n\n if (session.behaviors.length > 0) {\n gctx.modifiers = { ...moveCtx.modifiers };\n gctx.pointer = pointerStateOf(raw.pointer, moveCtx);\n gctx.view = readers.view();\n gctx.plane = readers.plane();\n gctx.current.set(GESTURE_KEY, current);\n const mode = effectiveOriginMode(resolved?.['originMode'], raw.altHeld);\n for (const b of session.behaviors) {\n const bounds = computeBounds(start.x, start.y, current.x, current.y, mode);\n const r = b.onMove?.(gctx, { start, current, bounds, pose: bounds });\n if (!r) continue;\n if (r.start) start = r.start;\n if (r.current) current = r.current;\n }\n gctx.current.set(GESTURE_KEY, current);\n }\n return { start, current };\n}\n\n/** Move the drag's start point by the pointer's step from `from` to `to`, so\n * the shape travels with the pointer instead of resizing. The step is taken\n * between snapped points, keeping a grid-snapped start on the grid. */\nexport function carryStart(\n session: Pick<InsertSession, 'snap' | 'gestureCtx'>,\n from: InsertPoint,\n to: InsertPoint,\n): void {\n const a = session.snap(from);\n const b = session.snap(to);\n const origin = session.gestureCtx.origin;\n const start = origin.get(GESTURE_KEY) as InsertPoint;\n origin.set(GESTURE_KEY, { x: start.x + b.x - a.x, y: start.y + b.y - a.y });\n}\n\n/** One pump event's pointer, modifiers, and trail, and the endpoints they\n * shape to. Each behavior's `onMove` runs once per call. */\nexport function nextFrame(\n session: InsertSession,\n readers: GestureReaders,\n prev: Pick<InsertFrame, 'points'>,\n moveCtx: InvocationCtx,\n): InsertFrame {\n const raw = {\n pointer: { x: moveCtx.world.x, y: moveCtx.world.y },\n // Each pointermove carries fresh modifier state from the dispatcher;\n // releasing / pressing a key mid-drag takes effect on the next move tick\n // (true modifier-only events don't fire without a cursor change).\n altHeld: moveCtx.modifiers.alt,\n shiftHeld: moveCtx.modifiers.shift,\n };\n return {\n ...raw,\n // The dispatcher mutates its own per-gesture trail in place but\n // attaches the array reference to each InvocationCtx.drag — keep\n // the latest reference in case the dispatcher swaps it.\n points: moveCtx.drag?.points ? moveCtx.drag.points : prev.points,\n shaped: shapeEndpoints(session, readers, raw, moveCtx),\n };\n}\n"],"mappings":";;;;;AAcA,SAASA,EACP,GACA,GACA,GACA,GAC8C;CAC9C,IAAMC,IAAWC,EAAcC,EAAQ,MAAM,MAAM,GAC7CC,IAAQH,GAAWI,QAAkC,QACrDC,IAAOH,EAAQ,YACjBI,IAAQD,EAAK,OAAO,IAAIE,CAAW,GACnCC,IAAU;EAAE,GAAGC,EAAI,QAAQ;EAAG,GAAGA,EAAI,QAAQ;CAAE;CAMnD,IALIN,MAAS,UAAUM,EAAI,cACzB,IAAUC,EAAgBJ,GAAOE,CAAO,IAE1C,IAAUN,EAAQ,KAAKM,CAAO,GAE1BN,EAAQ,UAAU,SAAS,GAAG;EAKhC,AAJA,EAAK,YAAY,EAAE,GAAGS,EAAQ,UAAU,GACxC,EAAK,UAAUC,EAAeH,EAAI,SAASE,CAAO,GAClD,EAAK,OAAOE,EAAQ,KAAK,GACzB,EAAK,QAAQA,EAAQ,MAAM,GAC3BR,EAAK,QAAQ,IAAIE,GAAaC,CAAO;EACrC,IAAMM,IAAOC,EAAoBf,GAAWgB,YAAeP,EAAI,OAAO;EACtE,KAAK,IAAMQ,KAAKf,EAAQ,WAAW;GACjC,IAAMgB,IAASC,EAAcb,EAAM,GAAGA,EAAM,GAAGE,EAAQ,GAAGA,EAAQ,GAAGM,CAAI,GACnEM,IAAIH,EAAE,SAASZ,GAAM;IAAE;IAAO;IAAS;IAAQ,MAAMa;GAAO,CAAC;GACnE,AAAKE,MACDA,EAAE,UAAO,IAAQA,EAAE,QACnBA,EAAE,YAAS,IAAUA,EAAE;EAC7B;EACA,EAAK,QAAQ,IAAIb,GAAaC,CAAO;CACvC;CACA,OAAO;EAAE;EAAO;CAAQ;AAC1B;AAKA,SAAgBa,EACd,GACA,GACA,GACM;CACN,IAAMC,IAAIpB,EAAQ,KAAKqB,CAAI,GACrBN,IAAIf,EAAQ,KAAKsB,CAAE,GACnBC,IAASvB,EAAQ,WAAW,QAC5BI,IAAQmB,EAAO,IAAIlB,CAAW;CACpC,EAAO,IAAIA,GAAa;EAAE,GAAGD,EAAM,IAAIW,EAAE,IAAIK,EAAE;EAAG,GAAGhB,EAAM,IAAIW,EAAE,IAAIK,EAAE;CAAE,CAAC;AAC5E;AAIA,SAAgBI,EACd,GACA,GACA,GACA,GACa;CACb,IAAMjB,IAAM;EACV,SAAS;GAAE,GAAGE,EAAQ,MAAM;GAAG,GAAGA,EAAQ,MAAM;EAAE;EAIlD,SAASA,EAAQ,UAAU;EAC3B,WAAWA,EAAQ,UAAU;CAC/B;CACA,OAAO;EACL,GAAGF;EAIH,QAAQE,EAAQ,MAAM,SAASA,EAAQ,KAAK,SAASgB,EAAK;EAC1D,QAAQ5B,EAAeG,GAASW,GAASJ,GAAKE,CAAO;CACvD;AACF"}
@@ -0,0 +1,31 @@
1
+ import { heldKeySpec as e } from "@weasel-js/routing";
2
+ //#region src/interactions/actions/insert/repositionAction.ts
3
+ var t = {
4
+ id: "insert.reposition",
5
+ label: "Insert — reposition",
6
+ group: "insert",
7
+ eligible: { capability: "creates-shapes" },
8
+ invoker: {
9
+ timing: "ongoing",
10
+ start: (e) => {
11
+ let t = e.deps.insertLive?.gesture;
12
+ return !t || !t.open ? {} : (t.repositioning = !0, {
13
+ kind: "insert.reposition",
14
+ onEnd: () => {
15
+ t.repositioning = !1;
16
+ }
17
+ });
18
+ }
19
+ },
20
+ requires: ["insertLive"]
21
+ }, n = {
22
+ spec: {
23
+ ...e(" "),
24
+ phase: "engaged"
25
+ },
26
+ actionId: "insert.reposition"
27
+ };
28
+ //#endregion
29
+ export { n as INSERT_REPOSITION_BINDING, t as insertRepositionAction };
30
+
31
+ //# sourceMappingURL=repositionAction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repositionAction.js","names":["insertRepositionAction","gesture","ctx","INSERT_REPOSITION_BINDING","heldKeySpec"],"sources":["../../../../src/interactions/actions/insert/repositionAction.ts"],"sourcesContent":["import { heldKeySpec, type Action, type GestureBinding } from '@weasel-js/routing';\nimport type { InsertLiveGesture } from './live';\n\n/**\n * @experimental\n * Companion to `insertAction`: while its key is held, dragging moves the\n * shape being drawn instead of resizing it; letting go resumes resizing from\n * where the shape now sits. No-op when no insert is in flight.\n */\nexport const insertRepositionAction: Action = {\n id: 'insert.reposition',\n label: 'Insert — reposition',\n group: 'insert',\n eligible: { capability: 'creates-shapes' },\n invoker: {\n timing: 'ongoing',\n start: (ctx) => {\n const gesture = ctx.deps.insertLive?.gesture as InsertLiveGesture | null | undefined;\n if (!gesture || !gesture.open) return {};\n gesture.repositioning = true;\n return {\n kind: 'insert.reposition',\n onEnd: () => { gesture.repositioning = false; },\n };\n },\n },\n requires: ['insertLive'],\n};\n\n/**\n * The binding a shape-drawing tool adds so holding Space mid-draw repositions\n * the shape. Gated to the tool's own engaged phase, so Space between drags is\n * left to the offhand hand tool. Shift and Alt may be down with it, so a\n * constrained or from-center draw can be moved without letting go of them.\n */\nexport const INSERT_REPOSITION_BINDING: GestureBinding = {\n spec: { ...heldKeySpec(' '), phase: 'engaged' },\n actionId: 'insert.reposition',\n};\n"],"mappings":";;AASA,IAAaA,IAAiC;CAC5C,IAAI;CACJ,OAAO;CACP,OAAO;CACP,UAAU,EAAE,YAAY,iBAAiB;CACzC,SAAS;EACP,QAAQ;EACR,QAAQ,MAAQ;GACd,IAAMC,IAAUC,EAAI,KAAK,YAAY;GAGrC,OAFI,CAACD,KAAW,CAACA,EAAQ,OAAa,CAAC,KACvC,EAAQ,gBAAgB,IACjB;IACL,MAAM;IACN,aAAa;KAAE,EAAQ,gBAAgB;IAAO;GAChD;EACF;CACF;CACA,UAAU,CAAC,YAAY;AACzB,GAQaE,IAA4C;CACvD,MAAM;EAAE,GAAGC,EAAY,GAAG;EAAG,OAAO;CAAU;CAC9C,UAAU;AACZ"}