@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,9 +1,9 @@
1
1
  import { IDENTITY_POSE_COMPOSITION as e } from "../../../core/scene/composeFrame.js";
2
2
  import { unionAABB as t } from "../../../core/geometry/unionBounds.js";
3
3
  import { RECT_POSE_DESCRIPTOR as n, translatePoseViaDescriptor as r, visualBoundsViaDescriptor as i } from "../../../core/geometry/poseDescriptor.js";
4
- import { createTransformOp as a } from "../../../core/ops/transform.js";
5
- import { poseFrame as o } from "../poseFrame.js";
6
- import { dispatchApplyBatch as s } from "../../../core/applyOps.js";
4
+ import { dispatchApplyBatch as a } from "../../../core/applyOps.js";
5
+ import { createTransformOp as o } from "../../../core/ops/transform.js";
6
+ import { poseFrame as s } from "../poseFrame.js";
7
7
  import { resolveSpatialReference as c } from "../spatialReference.js";
8
8
  import { useCallback as l } from "react";
9
9
  import { useLatest as u } from "@weasel-js/react";
@@ -43,9 +43,9 @@ function p(e, t, n) {
43
43
  function m(t, c = {}) {
44
44
  let m = u(t), h = u(c), g = d(), _ = u(g);
45
45
  return { align: l((t, c) => {
46
- let l = m.current, u = h.current, d = l.getSelection(), g = u.geometry ?? n, v = o({
46
+ let l = m.current, u = h.current, d = l.getSelection(), g = u.geometry ?? n, v = s({
47
47
  getPose: (e) => l.getPose(e),
48
- getParent: (e) => l.getParent?.(e) ?? null
48
+ getParent: (e) => l.getParent(e)
49
49
  }, u.composition ?? e), y = d.map((e) => v.world(e)), b = y.map((e) => i(e, g)), x = f(b, c, {
50
50
  pointer: () => {
51
51
  let e = _.current?.get();
@@ -61,14 +61,14 @@ function m(t, c = {}) {
61
61
  for (let e = 0; e < d.length; e++) {
62
62
  let { dx: n, dy: i } = p(b[e], x, t);
63
63
  if (n === 0 && i === 0) continue;
64
- let o = r(y[e], n, i, g);
65
- S.push(a({
64
+ let a = r(y[e], n, i, g);
65
+ S.push(o({
66
66
  id: d[e],
67
67
  from: l.getPose(d[e]),
68
- to: v.local(d[e], o)
68
+ to: v.local(d[e], a)
69
69
  }));
70
70
  }
71
- S.length !== 0 && s(l, S, u.label ?? "Align");
71
+ S.length !== 0 && a(l, S, u.label ?? "Align");
72
72
  }, [
73
73
  m,
74
74
  h,
@@ -1 +1 @@
1
- {"version":3,"file":"align.js","names":["alignTargetBounds","to","bounds","unionAABB","resolveSpatialReference","src","alignDeltaFor","edge","u","b","useAlign","adapterRef","useLatest","adapter","optsRef","options","pointer","usePointerContext","pointerRef","useCallback","a","o","sel","geom","RECT_POSE_DESCRIPTOR","frame","poseFrame","id","IDENTITY_POSE_COMPOSITION","poses","visualBoundsViaDescriptor","p","target","ops","i","dx","dy","translatePoseViaDescriptor","createTransformOp","dispatchApplyBatch"],"sources":["../../../../src/interactions/actions/align/align.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useLatest } from '@weasel-js/react';\nimport { createTransformOp } from 'core/ops/transform';\nimport type { Op } from 'core/ops/types';\nimport { dispatchApplyBatch } from 'core/applyOps';\nimport type { NodeId } from 'core/scene/types';\nimport { RECT_POSE_DESCRIPTOR, type PoseDescriptor } from '../resize/geometry';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { unionAABB } from 'core/geometry/unionBounds';\nimport { poseFrame } from '../poseFrame';\nimport { IDENTITY_POSE_COMPOSITION, type PoseComposition } from 'core/scene/composePose';\nimport { usePointerContext } from '@weasel-js/routing/react';\nimport { resolveSpatialReference, type SpatialReference, type SpatialReferenceSources } from '../spatialReference';\n\nexport { visualBoundsViaDescriptor, translatePoseViaDescriptor } from '../resize/geometry';\nimport { visualBoundsViaDescriptor, translatePoseViaDescriptor } from '../resize/geometry';\n\n/** Edge or center the selection should align to within the reference's\n * bounds (rotated members contribute their ink extent). */\nexport type AlignEdge = 'left' | 'right' | 'top' | 'bottom' | 'center-x' | 'center-y';\n\n/** @experimental What the selection aligns to: `'union'` — the selection's\n * own visual union AABB, the default — or a {@link SpatialReference}. */\nexport type AlignReference = 'union' | SpatialReference;\n\n/** @experimental The bounds each member of `bounds` aligns to under `to`, or\n * `null` when there is nothing to align: a union of fewer than two members,\n * an empty selection, or a reference that names nothing right now. */\nexport function alignTargetBounds(\n bounds: readonly Bounds[],\n to: AlignReference | undefined,\n src: SpatialReferenceSources,\n): Bounds | null {\n if (to === undefined || to === 'union') return bounds.length < 2 ? null : unionAABB([...bounds]);\n if (bounds.length === 0) return null;\n return resolveSpatialReference(to, src);\n}\n\n/** Adapter for `useAlign`. */\nexport interface AlignAdapter<TPose> {\n getSelection(): NodeId[];\n /** The pose as stored — local to the node's parent. */\n getPose(id: NodeId): TPose;\n /** The parent chain, for a scene whose container poses define a frame.\n * Omit it (or leave `composition` unset) for an absolute-pose scene. */\n getParent?(id: NodeId): NodeId | null;\n applyOps?(ops: Op[], label?: string): void;\n}\n\n/** Options for `useAlign`. */\nexport interface UseAlignOptions<TPose> {\n /** Projection between `TPose` and bounds. Defaults to `RECT_POSE_DESCRIPTOR`\n * for `{x,y,width,height}` poses. Pass `pathPoseDescriptor` for `Path`\n * poses so polygon coords translate correctly. */\n geometry?: PoseDescriptor<TPose>;\n /** Label passed to applyOps. Default 'Align'. */\n label?: string;\n /** How local poses fold up to world. Default IDENTITY, where the two are\n * the same value and the alignment runs entirely in stored coordinates. */\n composition?: PoseComposition<TPose>;\n}\n\n/** Return shape of `useAlign`. */\nexport interface UseAlignReturn {\n /** Imperative trigger. `to` defaults to `'union'`, which needs two items;\n * any other reference aligns a single item too. `'pointer'` reads the\n * surrounding `<PointerContextProvider>`. */\n align(edge: AlignEdge, to?: AlignReference): void;\n}\n\n/** Compute the (dx, dy) translation that moves AABB `b` so that the requested\n * `edge`/center matches the corresponding feature of the union AABB `u`. */\nexport function alignDeltaFor(b: Bounds, u: Bounds, edge: AlignEdge): { dx: number; dy: number } {\n switch (edge) {\n case 'left': return { dx: u.x - b.x, dy: 0 };\n case 'right': return { dx: (u.x + u.width) - (b.x + b.width), dy: 0 };\n case 'top': return { dx: 0, dy: u.y - b.y };\n case 'bottom': return { dx: 0, dy: (u.y + u.height) - (b.y + b.height) };\n case 'center-x': return { dx: (u.x + u.width / 2) - (b.x + b.width / 2), dy: 0 };\n case 'center-y': return { dx: 0, dy: (u.y + u.height / 2) - (b.y + b.height / 2) };\n }\n}\n\n/** Align the current selection to a shared edge or center of the selection's\n * union AABB, or of another reference. Single batch — one undo step.\n *\n * The edge is a world edge: bounds are measured and translated in world, and\n * each result is stored back in its own parent's frame. */\nexport function useAlign<TPose>(\n adapter: AlignAdapter<TPose>,\n options: UseAlignOptions<TPose> = {},\n): UseAlignReturn {\n const adapterRef = useLatest(adapter);\n const optsRef = useLatest(options);\n const pointer = usePointerContext();\n const pointerRef = useLatest(pointer);\n\n const align = useCallback((edge: AlignEdge, to?: AlignReference): void => {\n const a = adapterRef.current;\n const o = optsRef.current;\n const sel = a.getSelection();\n const geom =\n o.geometry ??\n (RECT_POSE_DESCRIPTOR as unknown as PoseDescriptor<TPose>);\n const frame = poseFrame<TPose>(\n {\n getPose: (id) => a.getPose(id as NodeId),\n getParent: (id) => (a.getParent?.(id as NodeId) ?? null) as string | null,\n },\n o.composition ?? (IDENTITY_POSE_COMPOSITION as PoseComposition<TPose>),\n );\n const poses = sel.map((id) => frame.world(id));\n const bounds = poses.map((p) => visualBoundsViaDescriptor(p, geom));\n const target = alignTargetBounds(bounds, to, {\n pointer: () => {\n const p = pointerRef.current?.get();\n return p ? { x: p.worldX, y: p.worldY } : null;\n },\n nodeBounds: (id) => visualBoundsViaDescriptor(frame.world(id), geom),\n });\n if (target === null) return;\n const ops: Op[] = [];\n for (let i = 0; i < sel.length; i++) {\n const { dx, dy } = alignDeltaFor(bounds[i], target, edge);\n if (dx === 0 && dy === 0) continue;\n const to = translatePoseViaDescriptor(poses[i], dx, dy, geom);\n ops.push(createTransformOp<TPose>({\n id: sel[i],\n from: a.getPose(sel[i]),\n to: frame.local(sel[i], to),\n }));\n }\n if (ops.length === 0) return;\n dispatchApplyBatch(a, ops, o.label ?? 'Align');\n }, [adapterRef, optsRef, pointerRef]);\n\n return { align };\n}\n"],"mappings":";;;;;;;;;;;AA4BA,SAAgBA,EACd,GACA,GACA,GACe;CAGf,OAFIC,MAAO,KAAA,KAAaA,MAAO,UAAgBC,EAAO,SAAS,IAAI,OAAOC,EAAU,CAAC,GAAGD,CAAM,CAAC,IAC3FA,EAAO,WAAW,IAAU,OACzBE,EAAwBH,GAAII,CAAG;AACxC;AAoCA,SAAgBC,EAAc,GAAW,GAAW,GAA6C;CAC/F,QAAQC,GAAR;EACE,KAAK,QAAY,OAAO;GAAE,IAAIC,EAAE,IAAIC,EAAE;GAAG,IAAI;EAAE;EAC/C,KAAK,SAAY,OAAO;GAAE,IAAKD,EAAE,IAAIA,EAAE,SAAUC,EAAE,IAAIA,EAAE;GAAQ,IAAI;EAAE;EACvE,KAAK,OAAY,OAAO;GAAE,IAAI;GAAG,IAAID,EAAE,IAAIC,EAAE;EAAE;EAC/C,KAAK,UAAY,OAAO;GAAE,IAAI;GAAG,IAAKD,EAAE,IAAIA,EAAE,UAAWC,EAAE,IAAIA,EAAE;EAAQ;EACzE,KAAK,YAAY,OAAO;GAAE,IAAKD,EAAE,IAAIA,EAAE,QAAQ,KAAMC,EAAE,IAAIA,EAAE,QAAQ;GAAI,IAAI;EAAE;EAC/E,KAAK,YAAY,OAAO;GAAE,IAAI;GAAG,IAAKD,EAAE,IAAIA,EAAE,SAAS,KAAMC,EAAE,IAAIA,EAAE,SAAS;EAAG;CACnF;AACF;AAOA,SAAgBC,EACd,GACA,IAAkC,CAAC,GACnB;CAChB,IAAMC,IAAaC,EAAUC,CAAO,GAC9BC,IAAUF,EAAUG,CAAO,GAC3BC,IAAUC,EAAkB,GAC5BC,IAAaN,EAAUI,CAAO;CAyCpC,OAAO,EAAE,OAvCKG,GAAa,GAAiB,MAA8B;EACxE,IAAMC,IAAIT,EAAW,SACfU,IAAIP,EAAQ,SACZQ,IAAMF,EAAE,aAAa,GACrBG,IACJF,EAAE,YACDG,GACGC,IAAQC,EACZ;GACE,UAAU,MAAON,EAAE,QAAQO,CAAY;GACvC,YAAY,MAAQP,EAAE,YAAYO,CAAY,KAAK;EACrD,GACAN,EAAE,eAAgBO,CACpB,GACMC,IAAQP,EAAI,KAAK,MAAOG,EAAM,MAAME,CAAE,CAAC,GACvCzB,IAAS2B,EAAM,KAAK,MAAMC,EAA0BC,GAAGR,CAAI,CAAC,GAC5DS,IAAShC,EAAkBE,GAAQD,GAAI;GAC3C,eAAe;IACb,IAAM8B,IAAIb,EAAW,SAAS,IAAI;IAClC,OAAOa,IAAI;KAAE,GAAGA,EAAE;KAAQ,GAAGA,EAAE;IAAO,IAAI;GAC5C;GACA,aAAa,MAAOD,EAA0BL,EAAM,MAAME,CAAE,GAAGJ,CAAI;EACrE,CAAC;EACD,IAAIS,MAAW,MAAM;EACrB,IAAMC,IAAY,CAAC;EACnB,KAAK,IAAIC,IAAI,GAAGA,IAAIZ,EAAI,QAAQ,KAAK;GACnC,IAAM,EAAE,OAAI,UAAOhB,EAAcJ,EAAOgC,IAAIF,GAAQzB,CAAI;GACxD,IAAI4B,MAAO,KAAKC,MAAO,GAAG;GAC1B,IAAMnC,IAAKoC,EAA2BR,EAAMK,IAAIC,GAAIC,GAAIb,CAAI;GAC5D,EAAI,KAAKe,EAAyB;IAChC,IAAIhB,EAAIY;IACR,MAAMd,EAAE,QAAQE,EAAIY,EAAE;IACtB,IAAIT,EAAM,MAAMH,EAAIY,IAAIjC,CAAE;GAC5B,CAAC,CAAC;EACJ;EACA,AAAIgC,EAAI,WAAW,KACnBM,EAAmBnB,GAAGa,GAAKZ,EAAE,SAAS,OAAO;CAC/C,GAAG;EAACV;EAAYG;EAASI;CAAU,CAE1B,EAAM;AACjB"}
1
+ {"version":3,"file":"align.js","names":["alignTargetBounds","to","bounds","unionAABB","resolveSpatialReference","src","alignDeltaFor","edge","u","b","useAlign","adapterRef","useLatest","adapter","optsRef","options","pointer","usePointerContext","pointerRef","useCallback","a","o","sel","geom","RECT_POSE_DESCRIPTOR","frame","poseFrame","id","IDENTITY_POSE_COMPOSITION","poses","visualBoundsViaDescriptor","p","target","ops","i","dx","dy","translatePoseViaDescriptor","createTransformOp","dispatchApplyBatch"],"sources":["../../../../src/interactions/actions/align/align.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useLatest } from '@weasel-js/react';\nimport { createTransformOp } from 'core/ops/transform';\nimport type { Op } from 'core/ops/types';\nimport { dispatchApplyBatch } from 'core/applyOps';\nimport type { NodeId } from 'core/scene/types';\nimport { RECT_POSE_DESCRIPTOR, type PoseDescriptor } from '../resize/geometry';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { unionAABB } from 'core/geometry/unionBounds';\nimport { poseFrame } from '../poseFrame';\nimport { IDENTITY_POSE_COMPOSITION, type PoseComposition } from 'core/scene/composePose';\nimport { usePointerContext } from '@weasel-js/routing/react';\nimport { resolveSpatialReference, type SpatialReference, type SpatialReferenceSources } from '../spatialReference';\n\nexport { visualBoundsViaDescriptor, translatePoseViaDescriptor } from '../resize/geometry';\nimport { visualBoundsViaDescriptor, translatePoseViaDescriptor } from '../resize/geometry';\n\n/** Edge or center the selection should align to within the reference's\n * bounds (rotated members contribute their ink extent). */\nexport type AlignEdge = 'left' | 'right' | 'top' | 'bottom' | 'center-x' | 'center-y';\n\n/** @experimental What the selection aligns to: `'union'` — the selection's\n * own visual union AABB, the default — or a {@link SpatialReference}. */\nexport type AlignReference = 'union' | SpatialReference;\n\n/** @experimental The bounds each member of `bounds` aligns to under `to`, or\n * `null` when there is nothing to align: a union of fewer than two members,\n * an empty selection, or a reference that names nothing right now. */\nexport function alignTargetBounds(\n bounds: readonly Bounds[],\n to: AlignReference | undefined,\n src: SpatialReferenceSources,\n): Bounds | null {\n if (to === undefined || to === 'union') return bounds.length < 2 ? null : unionAABB([...bounds]);\n if (bounds.length === 0) return null;\n return resolveSpatialReference(to, src);\n}\n\n/** Adapter for `useAlign`. */\nexport interface AlignAdapter<TPose> {\n getSelection(): NodeId[];\n /** The pose as stored — local to the node's parent. */\n getPose(id: NodeId): TPose;\n /** The node's container, `null` for a root: the chain a pose is composed\n * through when container poses define a frame. */\n getParent(id: NodeId): NodeId | null;\n applyOps?(ops: Op[], label?: string): void;\n}\n\n/** Options for `useAlign`. */\nexport interface UseAlignOptions<TPose> {\n /** Projection between `TPose` and bounds. Defaults to `RECT_POSE_DESCRIPTOR`\n * for `{x,y,width,height}` poses. Pass `pathPoseDescriptor` for `Path`\n * poses so polygon coords translate correctly. */\n geometry?: PoseDescriptor<TPose>;\n /** Label passed to applyOps. Default 'Align'. */\n label?: string;\n /** How local poses fold up to world. Default IDENTITY, where the two are\n * the same value and the alignment runs entirely in stored coordinates. */\n composition?: PoseComposition<TPose>;\n}\n\n/** Return shape of `useAlign`. */\nexport interface UseAlignReturn {\n /** Imperative trigger. `to` defaults to `'union'`, which needs two items;\n * any other reference aligns a single item too. `'pointer'` reads the\n * surrounding `<PointerContextProvider>`. */\n align(edge: AlignEdge, to?: AlignReference): void;\n}\n\n/** Compute the (dx, dy) translation that moves AABB `b` so that the requested\n * `edge`/center matches the corresponding feature of the union AABB `u`. */\nexport function alignDeltaFor(b: Bounds, u: Bounds, edge: AlignEdge): { dx: number; dy: number } {\n switch (edge) {\n case 'left': return { dx: u.x - b.x, dy: 0 };\n case 'right': return { dx: (u.x + u.width) - (b.x + b.width), dy: 0 };\n case 'top': return { dx: 0, dy: u.y - b.y };\n case 'bottom': return { dx: 0, dy: (u.y + u.height) - (b.y + b.height) };\n case 'center-x': return { dx: (u.x + u.width / 2) - (b.x + b.width / 2), dy: 0 };\n case 'center-y': return { dx: 0, dy: (u.y + u.height / 2) - (b.y + b.height / 2) };\n }\n}\n\n/** Align the current selection to a shared edge or center of the selection's\n * union AABB, or of another reference. Single batch — one undo step.\n *\n * The edge is a world edge: bounds are measured and translated in world, and\n * each result is stored back in its own parent's frame. */\nexport function useAlign<TPose>(\n adapter: AlignAdapter<TPose>,\n options: UseAlignOptions<TPose> = {},\n): UseAlignReturn {\n const adapterRef = useLatest(adapter);\n const optsRef = useLatest(options);\n const pointer = usePointerContext();\n const pointerRef = useLatest(pointer);\n\n const align = useCallback((edge: AlignEdge, to?: AlignReference): void => {\n const a = adapterRef.current;\n const o = optsRef.current;\n const sel = a.getSelection();\n const geom =\n o.geometry ??\n (RECT_POSE_DESCRIPTOR as unknown as PoseDescriptor<TPose>);\n const frame = poseFrame<TPose>(\n {\n getPose: (id) => a.getPose(id as NodeId),\n getParent: (id) => a.getParent(id as NodeId) as string | null,\n },\n o.composition ?? (IDENTITY_POSE_COMPOSITION as PoseComposition<TPose>),\n );\n const poses = sel.map((id) => frame.world(id));\n const bounds = poses.map((p) => visualBoundsViaDescriptor(p, geom));\n const target = alignTargetBounds(bounds, to, {\n pointer: () => {\n const p = pointerRef.current?.get();\n return p ? { x: p.worldX, y: p.worldY } : null;\n },\n nodeBounds: (id) => visualBoundsViaDescriptor(frame.world(id), geom),\n });\n if (target === null) return;\n const ops: Op[] = [];\n for (let i = 0; i < sel.length; i++) {\n const { dx, dy } = alignDeltaFor(bounds[i], target, edge);\n if (dx === 0 && dy === 0) continue;\n const to = translatePoseViaDescriptor(poses[i], dx, dy, geom);\n ops.push(createTransformOp<TPose>({\n id: sel[i],\n from: a.getPose(sel[i]),\n to: frame.local(sel[i], to),\n }));\n }\n if (ops.length === 0) return;\n dispatchApplyBatch(a, ops, o.label ?? 'Align');\n }, [adapterRef, optsRef, pointerRef]);\n\n return { align };\n}\n"],"mappings":";;;;;;;;;;;AA4BA,SAAgBA,EACd,GACA,GACA,GACe;CAGf,OAFIC,MAAO,KAAA,KAAaA,MAAO,UAAgBC,EAAO,SAAS,IAAI,OAAOC,EAAU,CAAC,GAAGD,CAAM,CAAC,IAC3FA,EAAO,WAAW,IAAU,OACzBE,EAAwBH,GAAII,CAAG;AACxC;AAoCA,SAAgBC,EAAc,GAAW,GAAW,GAA6C;CAC/F,QAAQC,GAAR;EACE,KAAK,QAAY,OAAO;GAAE,IAAIC,EAAE,IAAIC,EAAE;GAAG,IAAI;EAAE;EAC/C,KAAK,SAAY,OAAO;GAAE,IAAKD,EAAE,IAAIA,EAAE,SAAUC,EAAE,IAAIA,EAAE;GAAQ,IAAI;EAAE;EACvE,KAAK,OAAY,OAAO;GAAE,IAAI;GAAG,IAAID,EAAE,IAAIC,EAAE;EAAE;EAC/C,KAAK,UAAY,OAAO;GAAE,IAAI;GAAG,IAAKD,EAAE,IAAIA,EAAE,UAAWC,EAAE,IAAIA,EAAE;EAAQ;EACzE,KAAK,YAAY,OAAO;GAAE,IAAKD,EAAE,IAAIA,EAAE,QAAQ,KAAMC,EAAE,IAAIA,EAAE,QAAQ;GAAI,IAAI;EAAE;EAC/E,KAAK,YAAY,OAAO;GAAE,IAAI;GAAG,IAAKD,EAAE,IAAIA,EAAE,SAAS,KAAMC,EAAE,IAAIA,EAAE,SAAS;EAAG;CACnF;AACF;AAOA,SAAgBC,EACd,GACA,IAAkC,CAAC,GACnB;CAChB,IAAMC,IAAaC,EAAUC,CAAO,GAC9BC,IAAUF,EAAUG,CAAO,GAC3BC,IAAUC,EAAkB,GAC5BC,IAAaN,EAAUI,CAAO;CAyCpC,OAAO,EAAE,OAvCKG,GAAa,GAAiB,MAA8B;EACxE,IAAMC,IAAIT,EAAW,SACfU,IAAIP,EAAQ,SACZQ,IAAMF,EAAE,aAAa,GACrBG,IACJF,EAAE,YACDG,GACGC,IAAQC,EACZ;GACE,UAAU,MAAON,EAAE,QAAQO,CAAY;GACvC,YAAY,MAAOP,EAAE,UAAUO,CAAY;EAC7C,GACAN,EAAE,eAAgBO,CACpB,GACMC,IAAQP,EAAI,KAAK,MAAOG,EAAM,MAAME,CAAE,CAAC,GACvCzB,IAAS2B,EAAM,KAAK,MAAMC,EAA0BC,GAAGR,CAAI,CAAC,GAC5DS,IAAShC,EAAkBE,GAAQD,GAAI;GAC3C,eAAe;IACb,IAAM8B,IAAIb,EAAW,SAAS,IAAI;IAClC,OAAOa,IAAI;KAAE,GAAGA,EAAE;KAAQ,GAAGA,EAAE;IAAO,IAAI;GAC5C;GACA,aAAa,MAAOD,EAA0BL,EAAM,MAAME,CAAE,GAAGJ,CAAI;EACrE,CAAC;EACD,IAAIS,MAAW,MAAM;EACrB,IAAMC,IAAY,CAAC;EACnB,KAAK,IAAIC,IAAI,GAAGA,IAAIZ,EAAI,QAAQ,KAAK;GACnC,IAAM,EAAE,OAAI,UAAOhB,EAAcJ,EAAOgC,IAAIF,GAAQzB,CAAI;GACxD,IAAI4B,MAAO,KAAKC,MAAO,GAAG;GAC1B,IAAMnC,IAAKoC,EAA2BR,EAAMK,IAAIC,GAAIC,GAAIb,CAAI;GAC5D,EAAI,KAAKe,EAAyB;IAChC,IAAIhB,EAAIY;IACR,MAAMd,EAAE,QAAQE,EAAIY,EAAE;IACtB,IAAIT,EAAM,MAAMH,EAAIY,IAAIjC,CAAE;GAC5B,CAAC,CAAC;EACJ;EACA,AAAIgC,EAAI,WAAW,KACnBM,EAAmBnB,GAAGa,GAAKZ,EAAE,SAAS,OAAO;CAC/C,GAAG;EAACV;EAAYG;EAASI;CAAU,CAE1B,EAAM;AACjB"}
@@ -1,7 +1,7 @@
1
- import { locateChild as e } from "../../../core/ops/slot.js";
2
- import { createDeleteOp as t } from "../../../core/ops/delete.js";
3
- import { createInsertOp as n } from "../../../core/ops/create.js";
4
- import { dispatchApplyBatch as r } from "../../../core/applyOps.js";
1
+ import { dispatchApplyBatch as e } from "../../../core/applyOps.js";
2
+ import { locateChild as t } from "../../../core/ops/slot.js";
3
+ import { createDeleteOp as n } from "../../../core/ops/delete.js";
4
+ import { createInsertOp as r } from "../../../core/ops/create.js";
5
5
  import { createSetSelectionOp as i } from "../../../core/ops/select.js";
6
6
  import { resolveOperand as a } from "./operands.js";
7
7
  import { booleanPieces as o } from "./pieces.js";
@@ -78,31 +78,31 @@ function d(e, t, n = {}) {
78
78
  function f(a, o, c = {}) {
79
79
  let d = [...a.getSelection()], f = u(a, d, o, c);
80
80
  if ("kind" in f) return f;
81
- let { entries: p, pieces: m } = f, h = (t) => a.getZOrder ? a.getZOrder(t) : e(a, t) ?? void 0, g = p.map((e) => h(e.id)), _ = m.map((e) => {
81
+ let { entries: p, pieces: m } = f, h = p.map((e) => t(a, e.id) ?? void 0), g = m.map((e) => {
82
82
  let { id: t, operand: n } = p[e.source];
83
83
  return {
84
84
  node: a.createPathNode(e.path, o, t, n.paint),
85
- slot: g[e.source]
85
+ slot: h[e.source]
86
86
  };
87
- }), v = _.map((e) => e.node), y = [], b = p.map((e, t) => ({
88
- node: a.getNode?.(e.id) ?? { id: e.id },
89
- index: g[t]?.index ?? -1
87
+ }), _ = g.map((e) => e.node), v = [], y = p.map((e, t) => ({
88
+ node: a.getNode(e.id) ?? { id: e.id },
89
+ index: h[t]?.index ?? -1
90
90
  }));
91
- b.sort((e, t) => t.index - e.index);
92
- for (let { node: e, index: n } of b) y.push(t({
91
+ y.sort((e, t) => t.index - e.index);
92
+ for (let { node: e, index: t } of y) v.push(n({
93
93
  node: e,
94
- index: n
94
+ index: t
95
95
  }));
96
- for (let e of v) y.push(n({ node: e }));
97
- return y.push(...s(_.map((e) => ({
96
+ for (let e of _) v.push(r({ node: e }));
97
+ return v.push(...s(g.map((e) => ({
98
98
  id: e.node.id,
99
99
  slot: e.slot
100
- })), g)), y.push(i({
100
+ })), h)), v.push(i({
101
101
  from: d,
102
- to: v.map((e) => e.id)
103
- })), r(a, y, l[o]), {
102
+ to: _.map((e) => e.id)
103
+ })), e(a, v, l[o]), {
104
104
  kind: "applied",
105
- resultIds: v.map((e) => e.id)
105
+ resultIds: _.map((e) => e.id)
106
106
  };
107
107
  }
108
108
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"booleans.js","names":["LABEL","planBooleanOp","resolved","sel","resolveOperand","adapter","id","opts","err","TextOutlinesError","entries","a","b","op","pieces","booleanPieces","i","paint","operand","previewBooleanOp","plan","piece","applyBooleanOp","zOrder","locateChild","slots","made","newNodes","m","ops","captured","createDeleteOp","n","createInsertOp","placementOps","createSetSelectionOp","dispatchApplyBatch"],"sources":["../../../../src/interactions/actions/booleans/booleans.ts"],"sourcesContent":["/**\n * Pure action core for path boolean operations. Resolves the current\n * selection into z-ordered paths, runs the chosen op, and dispatches a\n * single batch of `Op`s (delete sources + insert results + set selection).\n */\nimport type { Mat3, Path } from '@weasel-js/geom';\nimport { createInsertOp } from 'core/ops/create';\nimport { createDeleteOp } from 'core/ops/delete';\nimport { createSetSelectionOp } from 'core/ops/select';\nimport { locateChild } from 'core/ops/slot';\nimport type { Op } from 'core/ops/types';\nimport type { NodeId } from 'core/scene/types';\nimport { dispatchApplyBatch } from 'core/applyOps';\nimport { TextOutlinesError, type TextNodeSource } from '@weasel-js/text';\nimport type { PathPaint } from 'canvas/nodeShapes/nodePaint';\nimport { resolveOperand, type BooleanOpOptions, type BooleanOperand } from './operands';\nimport { booleanPieces, type ResultPiece } from './pieces';\nimport { placementOps } from './placement';\n\n/** Boolean op identifiers: the five Pathfinder primaries, and Crop, Trim, and Merge. */\nexport type BooleanOp = 'union' | 'intersect' | 'subtract' | 'exclude' | 'divide' | 'crop' | 'trim' | 'merge';\n\n/**\n * z-position descriptor for a path node. `parentId` is the direct parent\n * (or `null` for a top-level node); `index` is the position within that\n * parent's child order — never a global render index.\n */\n/** @internal */\nexport interface BooleanZOrder {\n parentId: string | null;\n index: number;\n}\n\n/** Adapter the hook and the pure core both consume. */\nexport interface BooleansAdapter {\n getSelection(): readonly NodeId[] | readonly string[];\n getWorldPath(id: NodeId): Path | undefined;\n /**\n * Optional: the text node behind `id`, for an id `getWorldPath` has no path\n * for. Its glyph outlines (`textToPath`) become the operand, so text can be\n * cut, united or intersected like any shape.\n */\n getTextSource?(id: NodeId): TextNodeSource | undefined;\n /**\n * Optional: the paint the path node behind `id` wears. An operation told to\n * count strokes (`BooleanOpOptions.strokes`) reads it to learn whether the\n * node is stroked and whether it is filled; without it every operand is its\n * path alone.\n */\n getPaint?(id: NodeId): PathPaint | undefined;\n /**\n * Optional: the frame the node behind `id` draws its stroke in, as the\n * matrix taking that frame to world (`poseFrameOf`); null or absent for a\n * node drawn straight. A counted world-width stroke is skewed and stretched\n * with it. Without it every stroke is outlined in world.\n */\n getStrokeFrame?(id: NodeId): Mat3 | null | undefined;\n compareZ(a: NodeId, b: NodeId): number;\n /**\n * Mint a new node from a boolean-op result `Path`. `producedBy` names the\n * op that synthesized it — adapters that store provenance (e.g. for a\n * layer-panel icon) record it; others ignore the arg. `sourceId` is the\n * source the result stands in for: the topmost, except that each piece of\n * a Divide, Crop, Trim, or Merge names the topmost source it was cut from. Give the\n * node that source's parent, or the kit's reorder cannot find it there.\n * `paint` is passed when that source's stroke went into the geometry, and\n * the node must wear it as given: wearing the source's stroke again would\n * draw it twice. Without `paint`, give it that source's paint. The kit never\n * reads a node's `data`: anything else the result should keep of its source,\n * a name say, is the adapter's to copy from `sourceId`.\n */\n createPathNode(path: Path, producedBy: BooleanOp, sourceId: NodeId, paint?: PathPaint): { id: string };\n /**\n * Optional: return the full object for an id, used by the delete ops so\n * their `invert` (an insert) can restore the complete object on undo.\n * If omitted, a `{ id }` stub is captured — undo will reinstate the id\n * but consumers reading other fields (path, fill, etc.) will see them as\n * undefined.\n */\n getNode?(id: NodeId): { id: string } | undefined | null;\n /**\n * Optional: the reorder contract. With `getChildren` (and `getParent`, or\n * every node reads as top-level) the kit reads each source's slot among its\n * siblings, and with `setChildOrder` as well it moves each result into the\n * slot of the topmost source in its own source's parent. Without them the result lands wherever\n * `insertNode` defaults to. `defaultCommitAdapter` supplies all three.\n */\n getParent?(id: string): string | null;\n getChildren?(parentId: string | null): readonly string[];\n setChildOrder?(parentId: string | null, ids: string[]): void;\n /**\n * Optional override for the slot the kit otherwise reads through\n * `getParent` / `getChildren`: `id`'s parent and its index in that\n * parent's child order.\n *\n * @deprecated Supply `getParent` and `getChildren` instead, and the kit\n * derives this.\n */\n getZOrder?(id: NodeId): BooleanZOrder | undefined;\n applyOps?(ops: Op[], label?: string): void;\n setSelection?(ids: NodeId[]): void;\n insertNode?(node: { id: string }): void;\n removeNode?(id: string): void;\n}\n\n/** Outcome reported back to callers (lets the hook surface no-op signals). */\nexport type BooleanOpResult =\n | { kind: 'applied'; resultIds: string[] }\n | { kind: 'noop'; reason: 'no-paths' | 'too-few-for-subtract' | 'empty-result' }\n /** A text operand had no outline geometry. Nothing was changed: an op that\n * quietly dropped the operand would commit a different shape than asked. */\n | { kind: 'failed'; reason: 'text-outlines'; error: TextOutlinesError };\n\nconst LABEL: Record<BooleanOp, string> = {\n union: 'Union',\n intersect: 'Intersect',\n subtract: 'Subtract',\n exclude: 'Exclude',\n divide: 'Divide',\n crop: 'Crop',\n trim: 'Trim',\n merge: 'Merge',\n};\n\n/** One path a Boolean operation would make. */\nexport interface BooleanPreviewPiece {\n path: Path;\n /** The source the piece stands in for; see `BooleansAdapter.createPathNode`. */\n sourceId: NodeId;\n /** Set when that source's stroke went into `path`: the paint the piece wears. */\n paint?: PathPaint;\n}\n\n/** What {@link previewBooleanOp} answers: the pieces, or why there are none. */\nexport type BooleanOpPreview =\n | { kind: 'pieces'; pieces: BooleanPreviewPiece[] }\n | Exclude<BooleanOpResult, { kind: 'applied' }>;\n\ntype Entry = { id: NodeId; operand: BooleanOperand };\n\n/** The operands back to front and the pieces `op` makes of them, or the\n * result that says why it makes none. Reads the adapter and changes nothing. */\nfunction planBooleanOp(\n adapter: BooleansAdapter,\n sel: readonly NodeId[],\n op: BooleanOp,\n opts: BooleanOpOptions,\n): { entries: Entry[]; pieces: ResultPiece[] } | Exclude<BooleanOpResult, { kind: 'applied' }> {\n // Sorted ascending by `compareZ` so the first entry is the bottommost\n // (back) member. `subtract` relies on this: it computes `back − union(rest)`.\n let resolved: { id: NodeId; operand: BooleanOperand | undefined }[];\n try {\n resolved = sel.map((id) => ({ id, operand: resolveOperand(adapter, id, opts) }));\n } catch (err) {\n if (err instanceof TextOutlinesError) return { kind: 'failed', reason: 'text-outlines', error: err };\n throw err;\n }\n const entries = resolved\n .filter((e): e is Entry => e.operand != null)\n .sort((a, b) => adapter.compareZ(a.id, b.id));\n\n if (entries.length === 0) return { kind: 'noop', reason: 'no-paths' };\n if (op === 'subtract' && entries.length < 2) {\n return { kind: 'noop', reason: 'too-few-for-subtract' };\n }\n if (entries.length < 2) return { kind: 'noop', reason: 'no-paths' };\n\n // Merge unites the operands that wear one fill; one whose paint is unknown is united with nothing.\n const fillKey = (i: number): unknown => {\n const { id, operand } = entries[i]!;\n const paint = operand.paint ?? adapter.getPaint?.(id);\n return paint ? JSON.stringify(paint.fill ?? null) : i;\n };\n const pieces = booleanPieces(op, entries.map((e) => e.operand.path), fillKey);\n if (pieces.length === 0) return { kind: 'noop', reason: 'empty-result' };\n return { entries, pieces };\n}\n\n/**\n * What {@link applyBooleanOp} would make of the selection, without minting a\n * node or committing anything: for drawing an operation before it is applied.\n */\nexport function previewBooleanOp(\n adapter: BooleansAdapter,\n op: BooleanOp,\n opts: BooleanOpOptions = {},\n): BooleanOpPreview {\n const plan = planBooleanOp(adapter, [...adapter.getSelection()] as NodeId[], op, opts);\n if ('kind' in plan) return plan;\n return {\n kind: 'pieces',\n pieces: plan.pieces.map((piece) => {\n const { id, operand } = plan.entries[piece.source]!;\n return operand.paint ? { path: piece.path, sourceId: id, paint: operand.paint } : { path: piece.path, sourceId: id };\n }),\n };\n}\n\n/**\n * Run one Boolean operation over the selected paths and commit the result as a\n * single undoable batch.\n *\n * Operands are ordered back-to-front, which is what makes `subtract` mean\n * \"everything in front removed from the backmost shape\". Returns without\n * mutating anything when the selection holds no paths, or too few for the\n * requested operation. `opts.strokes` says what a stroked operand gives.\n */\nexport function applyBooleanOp(\n adapter: BooleansAdapter,\n op: BooleanOp,\n opts: BooleanOpOptions = {},\n): BooleanOpResult {\n const sel = [...adapter.getSelection()] as NodeId[];\n const plan = planBooleanOp(adapter, sel, op, opts);\n if ('kind' in plan) return plan;\n const { entries, pieces } = plan;\n const zOrder = (id: NodeId): BooleanZOrder | undefined =>\n adapter.getZOrder ? adapter.getZOrder(id) : locateChild(adapter, id) ?? undefined;\n\n // Every source's slot, read before the scene is mutated.\n const slots = entries.map((e) => zOrder(e.id));\n // A piece stands in for its own source: it is minted from that source, wears\n // the paint a stroke folded into that source left it, and takes its slot.\n const made = pieces.map((piece) => {\n const { id, operand } = entries[piece.source]!;\n return { node: adapter.createPathNode(piece.path, op, id, operand.paint), slot: slots[piece.source] };\n });\n const newNodes = made.map((m) => m.node);\n const ops: Op[] = [];\n // Capture the full node so the delete's `invert()` (an insert) restores\n // every field on undo; a `{ id }` stub for an adapter with no `getNode`.\n // The index is -1 when the adapter exposes no order.\n const captured = entries.map((e, i) => ({\n node: adapter.getNode?.(e.id) ?? { id: e.id },\n index: slots[i]?.index ?? -1,\n }));\n // Order by index DESC so reverse-then-invert (history's undo path)\n // re-inserts ASC — each splice lands on a correctly-sized array\n // instead of clamping high indices to a still-growing length.\n captured.sort((a, b) => b.index - a.index);\n for (const { node, index } of captured) ops.push(createDeleteOp({ node, index }));\n for (const n of newNodes) ops.push(createInsertOp({ node: n }));\n ops.push(...placementOps(made.map((m) => ({ id: m.node.id, slot: m.slot })), slots));\n ops.push(createSetSelectionOp({\n from: sel,\n to: newNodes.map((n) => n.id as NodeId),\n }));\n dispatchApplyBatch(adapter, ops, LABEL[op]);\n\n return { kind: 'applied', resultIds: newNodes.map((n) => n.id) };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"booleans.js","names":["LABEL","planBooleanOp","resolved","sel","resolveOperand","adapter","id","opts","err","TextOutlinesError","entries","a","b","op","pieces","booleanPieces","i","paint","operand","previewBooleanOp","plan","piece","applyBooleanOp","slots","locateChild","made","newNodes","m","ops","captured","createDeleteOp","n","createInsertOp","placementOps","createSetSelectionOp","dispatchApplyBatch"],"sources":["../../../../src/interactions/actions/booleans/booleans.ts"],"sourcesContent":["/**\n * Pure action core for path boolean operations. Resolves the current\n * selection into z-ordered paths, runs the chosen op, and dispatches a\n * single batch of `Op`s (delete sources + insert results + set selection).\n */\nimport type { Affine, Path } from '@weasel-js/geom';\nimport { createInsertOp } from 'core/ops/create';\nimport { createDeleteOp } from 'core/ops/delete';\nimport { createSetSelectionOp } from 'core/ops/select';\nimport { locateChild } from 'core/ops/slot';\nimport type { Op } from 'core/ops/types';\nimport type { NodeId } from 'core/scene/types';\nimport { dispatchApplyBatch } from 'core/applyOps';\nimport { TextOutlinesError, type TextNodeSource } from '@weasel-js/text';\nimport type { PathPaint } from 'canvas/nodeShapes/nodePaint';\nimport { resolveOperand, type BooleanOpOptions, type BooleanOperand } from './operands';\nimport { booleanPieces, type ResultPiece } from './pieces';\nimport { placementOps } from './placement';\n\n/** Boolean op identifiers: the five Pathfinder primaries, and Crop, Trim, and Merge. */\nexport type BooleanOp = 'union' | 'intersect' | 'subtract' | 'exclude' | 'divide' | 'crop' | 'trim' | 'merge';\n\n/**\n * z-position descriptor for a path node. `parentId` is the direct parent\n * (or `null` for a top-level node); `index` is the position within that\n * parent's child order — never a global render index.\n */\n/** @internal */\nexport interface BooleanZOrder {\n parentId: string | null;\n index: number;\n}\n\n/** Adapter the hook and the pure core both consume. */\nexport interface BooleansAdapter {\n getSelection(): readonly NodeId[] | readonly string[];\n getWorldPath(id: NodeId): Path | undefined;\n /**\n * Optional: the text node behind `id`, for an id `getWorldPath` has no path\n * for. Its glyph outlines (`textToPath`) become the operand, so text can be\n * cut, united or intersected like any shape.\n */\n getTextSource?(id: NodeId): TextNodeSource | undefined;\n /**\n * Optional: the paint the path node behind `id` wears. An operation told to\n * count strokes (`BooleanOpOptions.strokes`) reads it to learn whether the\n * node is stroked and whether it is filled; without it every operand is its\n * path alone.\n */\n getPaint?(id: NodeId): PathPaint | undefined;\n /**\n * Optional: the frame the node behind `id` draws its stroke in, as the\n * matrix taking that frame to world (`poseFrameOf`); null or absent for a\n * node drawn straight. A counted world-width stroke is skewed and stretched\n * with it. Without it every stroke is outlined in world.\n */\n getStrokeFrame?(id: NodeId): Affine | null | undefined;\n compareZ(a: NodeId, b: NodeId): number;\n /**\n * Mint a new node from a boolean-op result `Path`. `producedBy` names the\n * op that synthesized it — adapters that store provenance (e.g. for a\n * layer-panel icon) record it; others ignore the arg. `sourceId` is the\n * source the result stands in for: the topmost, except that each piece of\n * a Divide, Crop, Trim, or Merge names the topmost source it was cut from. Give the\n * node that source's parent, or the kit's reorder cannot find it there.\n * `paint` is passed when that source's stroke went into the geometry, and\n * the node must wear it as given: wearing the source's stroke again would\n * draw it twice. Without `paint`, give it that source's paint. The kit never\n * reads a node's `data`: anything else the result should keep of its source,\n * a name say, is the adapter's to copy from `sourceId`.\n */\n createPathNode(path: Path, producedBy: BooleanOp, sourceId: NodeId, paint?: PathPaint): { id: string };\n /**\n * The full object for an id. The delete ops capture it, so their `invert`\n * (an insert) restores the complete object on undo.\n */\n getNode(id: NodeId): { id: string } | undefined | null;\n /**\n * The tree. The kit reads each source's slot among its siblings through\n * `getParent` and `getChildren`, and moves each result into the slot of\n * the topmost source in its own source's parent through `setChildOrder`.\n * `defaultCommitAdapter` supplies all three, and `getNode`.\n */\n getParent(id: string): string | null;\n getChildren(parentId: string | null): string[];\n setChildOrder(parentId: string | null, ids: string[]): void;\n applyOps?(ops: Op[], label?: string): void;\n setSelection?(ids: NodeId[]): void;\n insertNode?(node: { id: string }): void;\n removeNode?(id: string): void;\n}\n\n/** Outcome reported back to callers (lets the hook surface no-op signals). */\nexport type BooleanOpResult =\n | { kind: 'applied'; resultIds: string[] }\n | { kind: 'noop'; reason: 'no-paths' | 'too-few-for-subtract' | 'empty-result' }\n /** A text operand had no outline geometry. Nothing was changed: an op that\n * quietly dropped the operand would commit a different shape than asked. */\n | { kind: 'failed'; reason: 'text-outlines'; error: TextOutlinesError };\n\nconst LABEL: Record<BooleanOp, string> = {\n union: 'Union',\n intersect: 'Intersect',\n subtract: 'Subtract',\n exclude: 'Exclude',\n divide: 'Divide',\n crop: 'Crop',\n trim: 'Trim',\n merge: 'Merge',\n};\n\n/** One path a Boolean operation would make. */\nexport interface BooleanPreviewPiece {\n path: Path;\n /** The source the piece stands in for; see `BooleansAdapter.createPathNode`. */\n sourceId: NodeId;\n /** Set when that source's stroke went into `path`: the paint the piece wears. */\n paint?: PathPaint;\n}\n\n/** What {@link previewBooleanOp} answers: the pieces, or why there are none. */\nexport type BooleanOpPreview =\n | { kind: 'pieces'; pieces: BooleanPreviewPiece[] }\n | Exclude<BooleanOpResult, { kind: 'applied' }>;\n\ntype Entry = { id: NodeId; operand: BooleanOperand };\n\n/** The operands back to front and the pieces `op` makes of them, or the\n * result that says why it makes none. Reads the adapter and changes nothing. */\nfunction planBooleanOp(\n adapter: BooleansAdapter,\n sel: readonly NodeId[],\n op: BooleanOp,\n opts: BooleanOpOptions,\n): { entries: Entry[]; pieces: ResultPiece[] } | Exclude<BooleanOpResult, { kind: 'applied' }> {\n // Sorted ascending by `compareZ` so the first entry is the bottommost\n // (back) member. `subtract` relies on this: it computes `back − union(rest)`.\n let resolved: { id: NodeId; operand: BooleanOperand | undefined }[];\n try {\n resolved = sel.map((id) => ({ id, operand: resolveOperand(adapter, id, opts) }));\n } catch (err) {\n if (err instanceof TextOutlinesError) return { kind: 'failed', reason: 'text-outlines', error: err };\n throw err;\n }\n const entries = resolved\n .filter((e): e is Entry => e.operand != null)\n .sort((a, b) => adapter.compareZ(a.id, b.id));\n\n if (entries.length === 0) return { kind: 'noop', reason: 'no-paths' };\n if (op === 'subtract' && entries.length < 2) {\n return { kind: 'noop', reason: 'too-few-for-subtract' };\n }\n if (entries.length < 2) return { kind: 'noop', reason: 'no-paths' };\n\n // Merge unites the operands that wear one fill; one whose paint is unknown is united with nothing.\n const fillKey = (i: number): unknown => {\n const { id, operand } = entries[i]!;\n const paint = operand.paint ?? adapter.getPaint?.(id);\n return paint ? JSON.stringify(paint.fill ?? null) : i;\n };\n const pieces = booleanPieces(op, entries.map((e) => e.operand.path), fillKey);\n if (pieces.length === 0) return { kind: 'noop', reason: 'empty-result' };\n return { entries, pieces };\n}\n\n/**\n * What {@link applyBooleanOp} would make of the selection, without minting a\n * node or committing anything: for drawing an operation before it is applied.\n */\nexport function previewBooleanOp(\n adapter: BooleansAdapter,\n op: BooleanOp,\n opts: BooleanOpOptions = {},\n): BooleanOpPreview {\n const plan = planBooleanOp(adapter, [...adapter.getSelection()] as NodeId[], op, opts);\n if ('kind' in plan) return plan;\n return {\n kind: 'pieces',\n pieces: plan.pieces.map((piece) => {\n const { id, operand } = plan.entries[piece.source]!;\n return operand.paint ? { path: piece.path, sourceId: id, paint: operand.paint } : { path: piece.path, sourceId: id };\n }),\n };\n}\n\n/**\n * Run one Boolean operation over the selected paths and commit the result as a\n * single undoable batch.\n *\n * Operands are ordered back-to-front, which is what makes `subtract` mean\n * \"everything in front removed from the backmost shape\". Returns without\n * mutating anything when the selection holds no paths, or too few for the\n * requested operation. `opts.strokes` says what a stroked operand gives.\n */\nexport function applyBooleanOp(\n adapter: BooleansAdapter,\n op: BooleanOp,\n opts: BooleanOpOptions = {},\n): BooleanOpResult {\n const sel = [...adapter.getSelection()] as NodeId[];\n const plan = planBooleanOp(adapter, sel, op, opts);\n if ('kind' in plan) return plan;\n const { entries, pieces } = plan;\n // Every source's slot, read before the scene is mutated.\n const slots: (BooleanZOrder | undefined)[] =\n entries.map((e) => locateChild(adapter, e.id) ?? undefined);\n // A piece stands in for its own source: it is minted from that source, wears\n // the paint a stroke folded into that source left it, and takes its slot.\n const made = pieces.map((piece) => {\n const { id, operand } = entries[piece.source]!;\n return { node: adapter.createPathNode(piece.path, op, id, operand.paint), slot: slots[piece.source] };\n });\n const newNodes = made.map((m) => m.node);\n const ops: Op[] = [];\n // Capture the full node so the delete's `invert()` (an insert) restores\n // every field on undo. The index is -1 for a source its parent does not list.\n const captured = entries.map((e, i) => ({\n node: adapter.getNode(e.id) ?? { id: e.id },\n index: slots[i]?.index ?? -1,\n }));\n // Order by index DESC so reverse-then-invert (history's undo path)\n // re-inserts ASC — each splice lands on a correctly-sized array\n // instead of clamping high indices to a still-growing length.\n captured.sort((a, b) => b.index - a.index);\n for (const { node, index } of captured) ops.push(createDeleteOp({ node, index }));\n for (const n of newNodes) ops.push(createInsertOp({ node: n }));\n ops.push(...placementOps(made.map((m) => ({ id: m.node.id, slot: m.slot })), slots));\n ops.push(createSetSelectionOp({\n from: sel,\n to: newNodes.map((n) => n.id as NodeId),\n }));\n dispatchApplyBatch(adapter, ops, LABEL[op]);\n\n return { kind: 'applied', resultIds: newNodes.map((n) => n.id) };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"operands.js","names":["inkOf","source","strokePaint","stroke","strokes","fill","ribbon","strokeRegion","path","frame","scale","pathUnion","resolveOperand","adapter","id","paint","text","glyphs","textToPath","inks","textToPathsByPaint","p","ink","whole"],"sources":["../../../../src/interactions/actions/booleans/operands.ts"],"sourcesContent":["/**\n * What each selected node gives a Boolean operation: its path, its glyph\n * outlines, or the region its ink covers once a stroke is counted.\n */\nimport type { Mat3, Path, Scale2 } from '@weasel-js/geom';\nimport { pathUnion } from '@weasel-js/geom/booleans';\nimport { textToPath, textToPathsByPaint } from '@weasel-js/text';\nimport type { PathPaint } from 'canvas/nodeShapes/nodePaint';\nimport type { NodeId } from 'core/scene/types';\nimport type { PaintedPath } from '../outlines/replaceWithPaths';\nimport { strokeRegion } from '../outlines/strokeRegion';\nimport type { BooleansAdapter } from './booleans';\n\n/**\n * What a stroked operand gives a Boolean operation.\n *\n * - `'ignore'`: its path, as though it had no stroke.\n * - `'unfilled'`: the region its stroke covers when it has no fill, as a line\n * or an open pencil stroke has none; its path when it is filled.\n * - `'include'`: what it paints — its fill and its stroke together, or the\n * stroke alone when it has no fill.\n *\n * Any mode but `'ignore'` needs {@link BooleansAdapter.getPaint}.\n */\nexport type BooleanStrokes = 'ignore' | 'unfilled' | 'include';\n\n/** Options for `applyBooleanOp`. */\nexport interface BooleanOpOptions {\n /** Default `'ignore'`. */\n strokes?: BooleanStrokes;\n /** The view scale a `{ px }` stroke width resolves against: one number, or\n * the view's own scale per axis. Default 1. */\n scale?: number | Scale2;\n}\n\n/** @internal */\nexport interface BooleanOperand {\n path: Path;\n /** Set when a stroke went into `path`: what a result standing in for this\n * operand wears, the stroke being part of its shape now. */\n paint?: PathPaint;\n}\n\nfunction inkOf(\n source: PaintedPath,\n strokes: BooleanStrokes,\n frame: Mat3 | null | undefined,\n scale: number | Scale2 | undefined,\n): BooleanOperand {\n const { path, fill } = source;\n const { stroke } = source;\n const strokePaint = stroke?.paint;\n if (!stroke || strokePaint == null) return { path };\n if (strokes === 'ignore' || (strokes === 'unfilled' && fill !== null)) return { path };\n const ribbon = strokeRegion(path, stroke, frame, scale === undefined ? {} : { scale });\n if (ribbon.commands.length === 0) return { path };\n return { path: fill !== null ? pathUnion(path, ribbon) : ribbon, paint: { fill: fill ?? { paint: strokePaint } } };\n}\n\n/**\n * The operand behind `id`, or `undefined` for a node that is not one: a group,\n * blank text, anything the adapter has neither a path nor text for. Throws\n * `TextOutlinesError` for text whose face has no outlines.\n */\nexport function resolveOperand(\n adapter: BooleansAdapter,\n id: NodeId,\n { strokes = 'ignore', scale }: BooleanOpOptions,\n): BooleanOperand | undefined {\n const path = adapter.getWorldPath(id);\n if (path) {\n const paint = strokes === 'ignore' ? undefined : adapter.getPaint?.(id);\n return paint ? inkOf({ path, ...paint }, strokes, adapter.getStrokeFrame?.(id), scale) : { path };\n }\n const text = adapter.getTextSource?.(id);\n if (!text) return undefined;\n if (strokes === 'ignore') {\n const glyphs = textToPath(text.data, text.pose);\n return glyphs.commands.length > 0 ? { path: glyphs } : undefined;\n }\n const inks = textToPathsByPaint(text.data, text.pose).map((p) => inkOf(p, strokes, adapter.getStrokeFrame?.(id), scale));\n if (inks.length === 0) return undefined;\n const paint = inks.find((ink) => ink.paint)?.paint;\n const whole = inks.length === 1 ? inks[0]!.path : pathUnion(...inks.map((ink) => ink.path));\n return paint ? { path: whole, paint } : { path: whole };\n}\n"],"mappings":";;;;AA2CA,SAASA,EACP,GACA,GACA,GACA,GACgB;CAChB,IAAM,EAAE,SAAM,YAASC,GACjB,EAAE,cAAWA,GACbC,IAAcC,GAAQ;CAE5B,IADI,CAACA,KAAUD,KAAe,QAC1BE,MAAY,YAAaA,MAAY,cAAcC,MAAS,MAAO,OAAO,EAAE,QAAK;CACrF,IAAMC,IAASC,EAAaC,GAAML,GAAQM,GAAOC,MAAU,KAAA,IAAY,CAAC,IAAI,EAAE,SAAM,CAAC;CAErF,OADIJ,EAAO,SAAS,WAAW,IAAU,EAAE,QAAK,IACzC;EAAE,MAAMD,MAAS,OAAiCC,IAA1BK,EAAUH,GAAMF,CAAM;EAAY,OAAO,EAAE,MAAMD,KAAQ,EAAE,OAAOH,EAAY,EAAE;CAAE;AACnH;AAOA,SAAgBU,EACd,GACA,GACA,EAAE,aAAU,UAAU,YACM;CAC5B,IAAMJ,IAAOK,EAAQ,aAAaC,CAAE;CACpC,IAAIN,GAAM;EACR,IAAMO,IAAQX,MAAY,WAAW,KAAA,IAAYS,EAAQ,WAAWC,CAAE;EACtE,OAAOC,IAAQf,EAAM;GAAE;GAAM,GAAGe;EAAM,GAAGX,GAASS,EAAQ,iBAAiBC,CAAE,GAAGJ,CAAK,IAAI,EAAE,QAAK;CAClG;CACA,IAAMM,IAAOH,EAAQ,gBAAgBC,CAAE;CACvC,IAAI,CAACE,GAAM;CACX,IAAIZ,MAAY,UAAU;EACxB,IAAMa,IAASC,EAAWF,EAAK,MAAMA,EAAK,IAAI;EAC9C,OAAOC,EAAO,SAAS,SAAS,IAAI,EAAE,MAAMA,EAAO,IAAI,KAAA;CACzD;CACA,IAAME,IAAOC,EAAmBJ,EAAK,MAAMA,EAAK,IAAI,CAAC,CAAC,KAAK,MAAMhB,EAAMqB,GAAGjB,GAASS,EAAQ,iBAAiBC,CAAE,GAAGJ,CAAK,CAAC;CACvH,IAAIS,EAAK,WAAW,GAAG;CACvB,IAAMJ,IAAQI,EAAK,MAAM,MAAQG,EAAI,KAAK,CAAC,EAAE,OACvCC,IAAQJ,EAAK,WAAW,IAAIA,EAAK,EAAE,CAAE,OAAOR,EAAU,GAAGQ,EAAK,KAAK,MAAQG,EAAI,IAAI,CAAC;CAC1F,OAAOP,IAAQ;EAAE,MAAMQ;EAAO;CAAM,IAAI,EAAE,MAAMA,EAAM;AACxD"}
1
+ {"version":3,"file":"operands.js","names":["inkOf","source","strokePaint","stroke","strokes","fill","ribbon","strokeRegion","path","frame","scale","pathUnion","resolveOperand","adapter","id","paint","text","glyphs","textToPath","inks","textToPathsByPaint","p","ink","whole"],"sources":["../../../../src/interactions/actions/booleans/operands.ts"],"sourcesContent":["/**\n * What each selected node gives a Boolean operation: its path, its glyph\n * outlines, or the region its ink covers once a stroke is counted.\n */\nimport type { Affine, Path, Scale2 } from '@weasel-js/geom';\nimport { pathUnion } from '@weasel-js/geom/booleans';\nimport { textToPath, textToPathsByPaint } from '@weasel-js/text';\nimport type { PathPaint } from 'canvas/nodeShapes/nodePaint';\nimport type { NodeId } from 'core/scene/types';\nimport type { PaintedPath } from '../outlines/replaceWithPaths';\nimport { strokeRegion } from '../outlines/strokeRegion';\nimport type { BooleansAdapter } from './booleans';\n\n/**\n * What a stroked operand gives a Boolean operation.\n *\n * - `'ignore'`: its path, as though it had no stroke.\n * - `'unfilled'`: the region its stroke covers when it has no fill, as a line\n * or an open pencil stroke has none; its path when it is filled.\n * - `'include'`: what it paints — its fill and its stroke together, or the\n * stroke alone when it has no fill.\n *\n * Any mode but `'ignore'` needs {@link BooleansAdapter.getPaint}.\n */\nexport type BooleanStrokes = 'ignore' | 'unfilled' | 'include';\n\n/** Options for `applyBooleanOp`. */\nexport interface BooleanOpOptions {\n /** Default `'ignore'`. */\n strokes?: BooleanStrokes;\n /** The view scale a `{ px }` stroke width resolves against: one number, or\n * the view's own scale per axis. Default 1. */\n scale?: number | Scale2;\n}\n\n/** @internal */\nexport interface BooleanOperand {\n path: Path;\n /** Set when a stroke went into `path`: what a result standing in for this\n * operand wears, the stroke being part of its shape now. */\n paint?: PathPaint;\n}\n\nfunction inkOf(\n source: PaintedPath,\n strokes: BooleanStrokes,\n frame: Affine | null | undefined,\n scale: number | Scale2 | undefined,\n): BooleanOperand {\n const { path, fill } = source;\n const { stroke } = source;\n const strokePaint = stroke?.paint;\n if (!stroke || strokePaint == null) return { path };\n if (strokes === 'ignore' || (strokes === 'unfilled' && fill !== null)) return { path };\n const ribbon = strokeRegion(path, stroke, frame, scale === undefined ? {} : { scale });\n if (ribbon.commands.length === 0) return { path };\n return { path: fill !== null ? pathUnion(path, ribbon) : ribbon, paint: { fill: fill ?? { paint: strokePaint } } };\n}\n\n/**\n * The operand behind `id`, or `undefined` for a node that is not one: a group,\n * blank text, anything the adapter has neither a path nor text for. Throws\n * `TextOutlinesError` for text whose face has no outlines.\n */\nexport function resolveOperand(\n adapter: BooleansAdapter,\n id: NodeId,\n { strokes = 'ignore', scale }: BooleanOpOptions,\n): BooleanOperand | undefined {\n const path = adapter.getWorldPath(id);\n if (path) {\n const paint = strokes === 'ignore' ? undefined : adapter.getPaint?.(id);\n return paint ? inkOf({ path, ...paint }, strokes, adapter.getStrokeFrame?.(id), scale) : { path };\n }\n const text = adapter.getTextSource?.(id);\n if (!text) return undefined;\n if (strokes === 'ignore') {\n const glyphs = textToPath(text.data, text.pose);\n return glyphs.commands.length > 0 ? { path: glyphs } : undefined;\n }\n const inks = textToPathsByPaint(text.data, text.pose).map((p) => inkOf(p, strokes, adapter.getStrokeFrame?.(id), scale));\n if (inks.length === 0) return undefined;\n const paint = inks.find((ink) => ink.paint)?.paint;\n const whole = inks.length === 1 ? inks[0]!.path : pathUnion(...inks.map((ink) => ink.path));\n return paint ? { path: whole, paint } : { path: whole };\n}\n"],"mappings":";;;;AA2CA,SAASA,EACP,GACA,GACA,GACA,GACgB;CAChB,IAAM,EAAE,SAAM,YAASC,GACjB,EAAE,cAAWA,GACbC,IAAcC,GAAQ;CAE5B,IADI,CAACA,KAAUD,KAAe,QAC1BE,MAAY,YAAaA,MAAY,cAAcC,MAAS,MAAO,OAAO,EAAE,QAAK;CACrF,IAAMC,IAASC,EAAaC,GAAML,GAAQM,GAAOC,MAAU,KAAA,IAAY,CAAC,IAAI,EAAE,SAAM,CAAC;CAErF,OADIJ,EAAO,SAAS,WAAW,IAAU,EAAE,QAAK,IACzC;EAAE,MAAMD,MAAS,OAAiCC,IAA1BK,EAAUH,GAAMF,CAAM;EAAY,OAAO,EAAE,MAAMD,KAAQ,EAAE,OAAOH,EAAY,EAAE;CAAE;AACnH;AAOA,SAAgBU,EACd,GACA,GACA,EAAE,aAAU,UAAU,YACM;CAC5B,IAAMJ,IAAOK,EAAQ,aAAaC,CAAE;CACpC,IAAIN,GAAM;EACR,IAAMO,IAAQX,MAAY,WAAW,KAAA,IAAYS,EAAQ,WAAWC,CAAE;EACtE,OAAOC,IAAQf,EAAM;GAAE;GAAM,GAAGe;EAAM,GAAGX,GAASS,EAAQ,iBAAiBC,CAAE,GAAGJ,CAAK,IAAI,EAAE,QAAK;CAClG;CACA,IAAMM,IAAOH,EAAQ,gBAAgBC,CAAE;CACvC,IAAI,CAACE,GAAM;CACX,IAAIZ,MAAY,UAAU;EACxB,IAAMa,IAASC,EAAWF,EAAK,MAAMA,EAAK,IAAI;EAC9C,OAAOC,EAAO,SAAS,SAAS,IAAI,EAAE,MAAMA,EAAO,IAAI,KAAA;CACzD;CACA,IAAME,IAAOC,EAAmBJ,EAAK,MAAMA,EAAK,IAAI,CAAC,CAAC,KAAK,MAAMhB,EAAMqB,GAAGjB,GAASS,EAAQ,iBAAiBC,CAAE,GAAGJ,CAAK,CAAC;CACvH,IAAIS,EAAK,WAAW,GAAG;CACvB,IAAMJ,IAAQI,EAAK,MAAM,MAAQG,EAAI,KAAK,CAAC,EAAE,OACvCC,IAAQJ,EAAK,WAAW,IAAIA,EAAK,EAAE,CAAE,OAAOR,EAAU,GAAGQ,EAAK,KAAK,MAAQG,EAAI,IAAI,CAAC;CAC1F,OAAOP,IAAQ;EAAE,MAAMQ;EAAO;CAAM,IAAI,EAAE,MAAMA,EAAM;AACxD"}
@@ -5,25 +5,26 @@ import { nodeDescriptors as c, poseDescriptorOf as l } from "../poseDescriptorDe
5
5
  import { selectedRoots as u, translateTargets as d } from "../selectionSubtree.js";
6
6
  import { scenePoseFrame as f } from "../poseFrame.js";
7
7
  import { defaultCommitAdapter as p } from "../defaultCommitAdapter.js";
8
- import { startCloneBehaviors as m } from "./behaviors.js";
9
- import { copySelection as h } from "../defaults/copySubtree.js";
8
+ import { recordTransform as m } from "../transform-again/memory.js";
9
+ import { startCloneBehaviors as h } from "./behaviors.js";
10
+ import { copySelection as g } from "../defaults/copySubtree.js";
10
11
  //#region src/interactions/actions/clone/action.ts
11
- function g(t, n, i, a) {
12
+ function _(t, n, i, a) {
12
13
  let s = t.originPoses.get(n), c = t.scene.get(n);
13
14
  if (s === void 0 || !c) return;
14
15
  let l = t.descriptorOf(n), u = e(s, i, a, l);
15
16
  return t.frame.local(n, r(l, u, o(t.view, c.layer)));
16
17
  }
17
- function _(e, t, n) {
18
+ function v(e, t, n) {
18
19
  let { dx: r, dy: i } = e.currentDelta, a = (t) => {
19
20
  e.applyOps ? e.applyOps(t, "Clone") : e.scene.applyBatch(t, "Clone", p(e.scene, n.adapterMethods));
20
21
  }, o = t.end(e.currentDelta);
21
22
  if (o === null) return !1;
22
23
  if (o !== void 0) return o.length > 0 && a(o), !0;
23
- let { ops: s, copies: c } = h(e.scene, e.ids, (t, n) => e.originPoses.has(t) ? g(e, t, r, i) : n.pose, "Clone");
24
+ let { ops: s, copies: c } = g(e.scene, e.ids, (t, n) => e.originPoses.has(t) ? _(e, t, r, i) : n.pose, "Clone");
24
25
  return s.length !== 0 && (a(s), n.set(c), !0);
25
26
  }
26
- var v = i({
27
+ var y = i({
27
28
  id: "clone",
28
29
  label: "Clone",
29
30
  cursor: "copy",
@@ -38,61 +39,65 @@ var v = i({
38
39
  invoker: {
39
40
  timing: "ongoing",
40
41
  start(e, i) {
41
- let s = e.deps.selection, p = e.deps.scene, h = e.deps.applyOps;
42
+ let s = e.deps.selection, p = e.deps.scene, g = e.deps.applyOps;
42
43
  if (!s || !p) return {};
43
- let v = s.get();
44
- if (v.length === 0) return {};
45
- let y = f(p), b = /* @__PURE__ */ new Map(), x = c(p, l(e.deps.poseDescriptor));
46
- for (let t of d(p, y, v)) {
44
+ let y = s.get();
45
+ if (y.length === 0) return {};
46
+ let b = f(p), x = /* @__PURE__ */ new Map(), S = c(p, l(e.deps.poseDescriptor));
47
+ for (let t of d(p, b, y)) {
47
48
  let n = p.get(t);
48
49
  if (n === void 0) continue;
49
50
  let i = a(o(e.deps.view, n.layer));
50
- b.set(t, r(x(t), y.world(t), i));
51
+ x.set(t, r(S(t), b.world(t), i));
51
52
  }
52
- if (b.size === 0) return {};
53
- let S = {
54
- ids: u(p, v),
53
+ if (x.size === 0) return {};
54
+ let C = {
55
+ ids: u(p, y),
55
56
  scene: p,
56
- descriptorOf: x,
57
- frame: y,
58
- originPoses: b,
57
+ descriptorOf: S,
58
+ frame: b,
59
+ originPoses: x,
59
60
  view: e.deps.view,
60
61
  currentDelta: {
61
62
  dx: 0,
62
63
  dy: 0
63
64
  },
64
65
  previews: /* @__PURE__ */ new Map(),
65
- applyOps: h
66
- }, C = m(e, i, p, S.ids, b, x);
66
+ applyOps: g
67
+ }, w = h(e, i, p, C.ids, x, S);
67
68
  return {
68
69
  kind: "clone",
69
70
  previewHidesSource: !1,
70
71
  onMove(e) {
71
72
  if (!e.drag) return;
72
- S.currentDelta = C.shape(e, {
73
+ C.currentDelta = w.shape(e, {
73
74
  dx: e.drag.delta.x,
74
75
  dy: e.drag.delta.y
75
- }), S.previews.clear();
76
- let { dx: t, dy: n } = S.currentDelta;
77
- if (t !== 0 || n !== 0) for (let e of S.originPoses.keys()) S.previews.set(e, g(S, e, t, n));
76
+ }), C.previews.clear();
77
+ let { dx: t, dy: n } = C.currentDelta;
78
+ if (t !== 0 || n !== 0) for (let e of C.originPoses.keys()) C.previews.set(e, _(C, e, t, n));
78
79
  },
79
80
  onEnd(e, r) {
80
81
  let i = !1;
81
82
  try {
82
- let { dx: e, dy: n } = S.currentDelta;
83
- r === "commit" && (e !== 0 || n !== 0) ? i = _(S, C, s) : t(C.list, C.gctx);
83
+ let { dx: e, dy: n } = C.currentDelta;
84
+ r === "commit" && (e !== 0 || n !== 0) ? (i = v(C, w, s), i && m(p, {
85
+ kind: "duplicate",
86
+ dx: e,
87
+ dy: n
88
+ }, s.get())) : t(w.list, w.gctx);
84
89
  } finally {
85
- S.previews.clear(), n(C.list, C.gctx, i);
90
+ C.previews.clear(), n(w.list, w.gctx, i);
86
91
  }
87
92
  },
88
- previewIds: () => S.previews.keys(),
89
- previewPose: (e) => S.previews.get(e) ?? null
93
+ previewIds: () => C.previews.keys(),
94
+ previewPose: (e) => C.previews.get(e) ?? null
90
95
  };
91
96
  }
92
97
  },
93
98
  enabled: () => !0
94
99
  }, s);
95
100
  //#endregion
96
- export { v as cloneAction };
101
+ export { y as cloneAction };
97
102
 
98
103
  //# sourceMappingURL=action.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"action.js","names":["landed","origin","scratch","id","node","d","moved","translatePoseViaDescriptor","dx","dy","carryPose","planeOf","commitClone","apply","ops","defaultCommitAdapter","selection","answer","behaviors","copySelection","copies","cloneAction","inPlane","ctx","scene","applyOps","ids","frame","scenePoseFrame","originPoses","descriptorOf","nodeDescriptors","poseDescriptorOf","translateTargets","toEdited","invertPlane","selectedRoots","startCloneBehaviors","opts","moveCtx","committed","reason","runBehaviorCancel","runBehaviorSettled","selectionLayer"],"sources":["../../../../src/interactions/actions/clone/action.ts"],"sourcesContent":["/**\n * `cloneAction` — ongoing Action descriptor for alt-drag clone. Nothing is\n * written before the commit, so a cancel only clears the preview.\n *\n * The action does not gate on a modifier: it runs whenever the dispatcher\n * routes to it, and the binding that names it carries the `alt` requirement.\n */\n\nimport type { Action } from '@weasel-js/routing';\nimport type { BindingOpts, InvocationCtx, OngoingHandle } from '@weasel-js/routing';\nimport type { Scene, NodeId } from 'core/scene/types';\nimport type { Op } from 'core/ops/types';\nimport { defaultCommitAdapter } from '../defaultCommitAdapter';\nimport { runBehaviorCancel, runBehaviorSettled } from '../gestureLifecycle';\nimport { startCloneBehaviors, type CloneBehaviors } from './behaviors';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport { copySelection } from '../defaults/copySubtree';\nimport { selectedRoots, translateTargets } from '../selectionSubtree';\nimport { nodeDescriptors, poseDescriptorOf } from '../poseDescriptorDep';\nimport { translatePoseViaDescriptor, type PoseDescriptor } from '../resize/geometry';\nimport { scenePoseFrame, type PoseFrame } from '../poseFrame';\nimport { carryPose, invertPlane, inPlane, planeOf, selectionLayer } from '../planeInput';\n\ninterface CloneScratch {\n /** The selected ids a copy starts from; each brings its subtree. */\n ids: NodeId[];\n scene: Scene<unknown, string, unknown>;\n descriptorOf: (id: NodeId) => PoseDescriptor<unknown>;\n /** World reads and local writes over the scene's composition strategy. */\n frame: PoseFrame<unknown>;\n /** Origin poses captured at drag start, in world — the frame the drag\n * delta is expressed in, and the plane the edit is measured in. The copy\n * keeps its source's parent and layer, so each translated result is\n * carried back to that layer's plane and stored in that same frame.\n * Holds every node the copy moves itself: a node stored in a copied frame\n * is absent, because the copy of that frame carries it. */\n originPoses: Map<NodeId, unknown>;\n /** The view the invocation started with, read for `planeOf`. */\n view: unknown;\n /** Running drag delta — updated each onMove, applied once at commit. */\n currentDelta: { dx: number; dy: number };\n /** Preview poses keyed by the original's id: the copies have no ids yet. */\n previews: Map<NodeId, unknown>;\n /** Optional consumer commit hook captured at gesture start. When present,\n * the insert ops route through it instead of `scene.applyBatch`. */\n applyOps?: (ops: Op[], label: string) => void;\n}\n\n/** Where the copy of `id` lands for a drag of `(dx, dy)`, as stored. */\nfunction landed(scratch: CloneScratch, id: NodeId, dx: number, dy: number): unknown {\n const origin = scratch.originPoses.get(id);\n const node = scratch.scene.get(id);\n if (origin === undefined || !node) return undefined;\n const d = scratch.descriptorOf(id);\n const moved = translatePoseViaDescriptor(origin, dx, dy, d);\n return scratch.frame.local(id, carryPose(d, moved, planeOf(scratch.view, node.layer)));\n}\n\n/** Commit the drag: a behavior's ops when one claims it, else the copies,\n * which become the selection. Returns whether anything was committed. */\nfunction commitClone(scratch: CloneScratch, behaviors: CloneBehaviors, selection: SelectionApi): boolean {\n const { dx, dy } = scratch.currentDelta;\n const apply = (ops: Op[]): void => {\n if (scratch.applyOps) scratch.applyOps(ops, 'Clone');\n else scratch.scene.applyBatch(ops, 'Clone', defaultCommitAdapter(scratch.scene, selection.adapterMethods));\n };\n const answer = behaviors.end(scratch.currentDelta);\n if (answer === null) return false;\n if (answer !== undefined) {\n if (answer.length > 0) apply(answer);\n return true;\n }\n const { ops, copies } = copySelection(\n scratch.scene,\n scratch.ids,\n (id, node) => (scratch.originPoses.has(id) ? landed(scratch, id, dx, dy) : node.pose),\n 'Clone',\n );\n if (ops.length === 0) return false;\n apply(ops);\n selection.set(copies);\n return true;\n}\n\n/**\n * @experimental\n * Static descriptor for the `clone` Action. Copies each selected node, with\n * its subtree, to where the drag left it. One undo entry.\n */\nexport const cloneAction: Action & { requires: string[] } = inPlane({\n id: 'clone',\n label: 'Clone',\n // No default binding: a bare `{ kind: 'drag' }` would clone a non-empty\n // selection on any drag no tool claimed. Alt-drag on a body binds it\n // (`selectionMoveBindings`, the `move` preset).\n // The platform's duplicate cursor. The select tool binds clone behind\n // `mods: { alt: true }`, so the pump shows this only while Alt is down.\n cursor: 'copy',\n activeCursor: 'copy',\n eligible: { capability: ['edits-page', 'creates-selection'] },\n requires: ['selection', 'scene', 'applyOps', 'poseDescriptor'],\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle {\n const selection = ctx.deps.selection;\n const scene = ctx.deps.scene;\n const applyOps = ctx.deps.applyOps;\n\n if (!selection || !scene) return {};\n\n const ids = selection.get() as NodeId[];\n if (ids.length === 0) return {};\n\n // Capture origin poses once at drag start.\n const frame = scenePoseFrame(scene);\n const originPoses = new Map<NodeId, unknown>();\n const descriptorOf = nodeDescriptors(scene, poseDescriptorOf(ctx.deps.poseDescriptor));\n for (const id of translateTargets(scene, frame, ids)) {\n const node = scene.get(id);\n if (node === undefined) continue;\n const toEdited = invertPlane(planeOf(ctx.deps.view, node.layer));\n originPoses.set(id, carryPose(descriptorOf(id), frame.world(id), toEdited));\n }\n\n if (originPoses.size === 0) return {};\n\n const scratch: CloneScratch = {\n ids: selectedRoots(scene, ids),\n scene,\n descriptorOf,\n frame,\n originPoses,\n view: ctx.deps.view,\n currentDelta: { dx: 0, dy: 0 },\n previews: new Map<NodeId, unknown>(),\n applyOps,\n };\n const behaviors = startCloneBehaviors(ctx, opts, scene, scratch.ids, originPoses, descriptorOf);\n\n return {\n kind: 'clone',\n // The source stays visible at its committed pose; the ghost appears\n // at the drag target.\n previewHidesSource: false,\n onMove(moveCtx: InvocationCtx): void {\n if (!moveCtx.drag) return;\n scratch.currentDelta = behaviors.shape(moveCtx, { dx: moveCtx.drag.delta.x, dy: moveCtx.drag.delta.y });\n scratch.previews.clear();\n const { dx, dy } = scratch.currentDelta;\n if (dx === 0 && dy === 0) return;\n for (const id of scratch.originPoses.keys()) scratch.previews.set(id, landed(scratch, id, dx, dy)!);\n },\n onEnd(_endCtx: InvocationCtx, reason: 'commit' | 'cancel'): void {\n let committed = false;\n try {\n const { dx, dy } = scratch.currentDelta;\n // A zero-delta drag makes no clone, so its behaviors are canceled.\n if (reason === 'commit' && (dx !== 0 || dy !== 0)) committed = commitClone(scratch, behaviors, selection);\n else runBehaviorCancel(behaviors.list, behaviors.gctx);\n } finally {\n scratch.previews.clear();\n runBehaviorSettled(behaviors.list, behaviors.gctx, committed);\n }\n },\n previewIds: () => scratch.previews.keys(),\n previewPose: (id: string) => scratch.previews.get(id as NodeId) ?? null,\n };\n },\n },\n enabled: () => true,\n}, selectionLayer);\n"],"mappings":"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"}
1
+ {"version":3,"file":"action.js","names":["landed","origin","scratch","id","node","d","moved","translatePoseViaDescriptor","dx","dy","carryPose","planeOf","commitClone","apply","ops","defaultCommitAdapter","selection","answer","behaviors","copySelection","copies","cloneAction","inPlane","ctx","scene","applyOps","ids","frame","scenePoseFrame","originPoses","descriptorOf","nodeDescriptors","poseDescriptorOf","translateTargets","toEdited","invertPlane","selectedRoots","startCloneBehaviors","opts","moveCtx","committed","reason","recordTransform","runBehaviorCancel","runBehaviorSettled","selectionLayer"],"sources":["../../../../src/interactions/actions/clone/action.ts"],"sourcesContent":["/**\n * `cloneAction` — ongoing Action descriptor for alt-drag clone. Nothing is\n * written before the commit, so a cancel only clears the preview.\n *\n * The action does not gate on a modifier: it runs whenever the dispatcher\n * routes to it, and the binding that names it carries the `alt` requirement.\n */\n\nimport type { Action } from '@weasel-js/routing';\nimport type { BindingOpts, InvocationCtx, OngoingHandle } from '@weasel-js/routing';\nimport type { Scene, NodeId } from 'core/scene/types';\nimport type { Op } from 'core/ops/types';\nimport { defaultCommitAdapter } from '../defaultCommitAdapter';\nimport { runBehaviorCancel, runBehaviorSettled } from '../gestureLifecycle';\nimport { startCloneBehaviors, type CloneBehaviors } from './behaviors';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport { copySelection } from '../defaults/copySubtree';\nimport { selectedRoots, translateTargets } from '../selectionSubtree';\nimport { nodeDescriptors, poseDescriptorOf } from '../poseDescriptorDep';\nimport { translatePoseViaDescriptor, type PoseDescriptor } from '../resize/geometry';\nimport { scenePoseFrame, type PoseFrame } from '../poseFrame';\nimport { carryPose, invertPlane, inPlane, planeOf, selectionLayer } from '../planeInput';\nimport { recordTransform } from '../transform-again/memory';\n\ninterface CloneScratch {\n /** The selected ids a copy starts from; each brings its subtree. */\n ids: NodeId[];\n scene: Scene<unknown, string, unknown>;\n descriptorOf: (id: NodeId) => PoseDescriptor<unknown>;\n /** World reads and local writes over the scene's composition strategy. */\n frame: PoseFrame<unknown>;\n /** Origin poses captured at drag start, in world — the frame the drag\n * delta is expressed in, and the plane the edit is measured in. The copy\n * keeps its source's parent and layer, so each translated result is\n * carried back to that layer's plane and stored in that same frame.\n * Holds every node the copy moves itself: a node stored in a copied frame\n * is absent, because the copy of that frame carries it. */\n originPoses: Map<NodeId, unknown>;\n /** The view the invocation started with, read for `planeOf`. */\n view: unknown;\n /** Running drag delta — updated each onMove, applied once at commit. */\n currentDelta: { dx: number; dy: number };\n /** Preview poses keyed by the original's id: the copies have no ids yet. */\n previews: Map<NodeId, unknown>;\n /** Optional consumer commit hook captured at gesture start. When present,\n * the insert ops route through it instead of `scene.applyBatch`. */\n applyOps?: (ops: Op[], label: string) => void;\n}\n\n/** Where the copy of `id` lands for a drag of `(dx, dy)`, as stored. */\nfunction landed(scratch: CloneScratch, id: NodeId, dx: number, dy: number): unknown {\n const origin = scratch.originPoses.get(id);\n const node = scratch.scene.get(id);\n if (origin === undefined || !node) return undefined;\n const d = scratch.descriptorOf(id);\n const moved = translatePoseViaDescriptor(origin, dx, dy, d);\n return scratch.frame.local(id, carryPose(d, moved, planeOf(scratch.view, node.layer)));\n}\n\n/** Commit the drag: a behavior's ops when one claims it, else the copies,\n * which become the selection. Returns whether anything was committed. */\nfunction commitClone(scratch: CloneScratch, behaviors: CloneBehaviors, selection: SelectionApi): boolean {\n const { dx, dy } = scratch.currentDelta;\n const apply = (ops: Op[]): void => {\n if (scratch.applyOps) scratch.applyOps(ops, 'Clone');\n else scratch.scene.applyBatch(ops, 'Clone', defaultCommitAdapter(scratch.scene, selection.adapterMethods));\n };\n const answer = behaviors.end(scratch.currentDelta);\n if (answer === null) return false;\n if (answer !== undefined) {\n if (answer.length > 0) apply(answer);\n return true;\n }\n const { ops, copies } = copySelection(\n scratch.scene,\n scratch.ids,\n (id, node) => (scratch.originPoses.has(id) ? landed(scratch, id, dx, dy) : node.pose),\n 'Clone',\n );\n if (ops.length === 0) return false;\n apply(ops);\n selection.set(copies);\n return true;\n}\n\n/**\n * @experimental\n * Static descriptor for the `clone` Action. Copies each selected node, with\n * its subtree, to where the drag left it. One undo entry.\n */\nexport const cloneAction: Action & { requires: string[] } = inPlane({\n id: 'clone',\n label: 'Clone',\n // No default binding: a bare `{ kind: 'drag' }` would clone a non-empty\n // selection on any drag no tool claimed. Alt-drag on a body binds it\n // (`selectionMoveBindings`, the `move` preset).\n // The platform's duplicate cursor. The select tool binds clone behind\n // `mods: { alt: true }`, so the pump shows this only while Alt is down.\n cursor: 'copy',\n activeCursor: 'copy',\n eligible: { capability: ['edits-page', 'creates-selection'] },\n requires: ['selection', 'scene', 'applyOps', 'poseDescriptor'],\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle {\n const selection = ctx.deps.selection;\n const scene = ctx.deps.scene;\n const applyOps = ctx.deps.applyOps;\n\n if (!selection || !scene) return {};\n\n const ids = selection.get() as NodeId[];\n if (ids.length === 0) return {};\n\n // Capture origin poses once at drag start.\n const frame = scenePoseFrame(scene);\n const originPoses = new Map<NodeId, unknown>();\n const descriptorOf = nodeDescriptors(scene, poseDescriptorOf(ctx.deps.poseDescriptor));\n for (const id of translateTargets(scene, frame, ids)) {\n const node = scene.get(id);\n if (node === undefined) continue;\n const toEdited = invertPlane(planeOf(ctx.deps.view, node.layer));\n originPoses.set(id, carryPose(descriptorOf(id), frame.world(id), toEdited));\n }\n\n if (originPoses.size === 0) return {};\n\n const scratch: CloneScratch = {\n ids: selectedRoots(scene, ids),\n scene,\n descriptorOf,\n frame,\n originPoses,\n view: ctx.deps.view,\n currentDelta: { dx: 0, dy: 0 },\n previews: new Map<NodeId, unknown>(),\n applyOps,\n };\n const behaviors = startCloneBehaviors(ctx, opts, scene, scratch.ids, originPoses, descriptorOf);\n\n return {\n kind: 'clone',\n // The source stays visible at its committed pose; the ghost appears\n // at the drag target.\n previewHidesSource: false,\n onMove(moveCtx: InvocationCtx): void {\n if (!moveCtx.drag) return;\n scratch.currentDelta = behaviors.shape(moveCtx, { dx: moveCtx.drag.delta.x, dy: moveCtx.drag.delta.y });\n scratch.previews.clear();\n const { dx, dy } = scratch.currentDelta;\n if (dx === 0 && dy === 0) return;\n for (const id of scratch.originPoses.keys()) scratch.previews.set(id, landed(scratch, id, dx, dy)!);\n },\n onEnd(_endCtx: InvocationCtx, reason: 'commit' | 'cancel'): void {\n let committed = false;\n try {\n const { dx, dy } = scratch.currentDelta;\n // A zero-delta drag makes no clone, so its behaviors are canceled.\n if (reason === 'commit' && (dx !== 0 || dy !== 0)) {\n committed = commitClone(scratch, behaviors, selection);\n if (committed) recordTransform(scene, { kind: 'duplicate', dx, dy }, selection.get());\n } else runBehaviorCancel(behaviors.list, behaviors.gctx);\n } finally {\n scratch.previews.clear();\n runBehaviorSettled(behaviors.list, behaviors.gctx, committed);\n }\n },\n previewIds: () => scratch.previews.keys(),\n previewPose: (id: string) => scratch.previews.get(id as NodeId) ?? null,\n };\n },\n },\n enabled: () => true,\n}, selectionLayer);\n"],"mappings":"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"}