@weasel-js/core 1.9.2 → 1.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (542) hide show
  1. package/CHANGELOG.md +393 -0
  2. package/README.md +2 -2
  3. package/dist/WeaselProvider.js +5 -5
  4. package/dist/affordances/composeAffordanceLayer.js +10 -20
  5. package/dist/affordances/composeAffordanceLayer.js.map +1 -1
  6. package/dist/affordances/cornerResize.js +25 -17
  7. package/dist/affordances/cornerResize.js.map +1 -1
  8. package/dist/affordances/hitAffordanceRegions.js +27 -20
  9. package/dist/affordances/hitAffordanceRegions.js.map +1 -1
  10. package/dist/affordances/rotationHandle.js +18 -18
  11. package/dist/affordances/rotationHandle.js.map +1 -1
  12. package/dist/animation/behaviors/momentum.js +93 -55
  13. package/dist/animation/behaviors/momentum.js.map +1 -1
  14. package/dist/animation/loop.js +10 -12
  15. package/dist/animation/loop.js.map +1 -1
  16. package/dist/animation/reflow.js +8 -8
  17. package/dist/animation/reflow.js.map +1 -1
  18. package/dist/animation/stagger.js +64 -56
  19. package/dist/animation/stagger.js.map +1 -1
  20. package/dist/animation/timeline/booking.js +62 -54
  21. package/dist/animation/timeline/booking.js.map +1 -1
  22. package/dist/animation/timeline/createTimeline.js +85 -76
  23. package/dist/animation/timeline/createTimeline.js.map +1 -1
  24. package/dist/animation/timeline/sampleTrack.js +13 -5
  25. package/dist/animation/timeline/sampleTrack.js.map +1 -1
  26. package/dist/animation/useAnimator.js +45 -35
  27. package/dist/animation/useAnimator.js.map +1 -1
  28. package/dist/animation/wrappers/animateLifecycle.js +31 -24
  29. package/dist/animation/wrappers/animateLifecycle.js.map +1 -1
  30. package/dist/animation/wrappers/animateOnSetPose.js +28 -26
  31. package/dist/animation/wrappers/animateOnSetPose.js.map +1 -1
  32. package/dist/animation/wrappers/instantPose.js +13 -0
  33. package/dist/animation/wrappers/instantPose.js.map +1 -0
  34. package/dist/{poseDescriptor-D-OH-tu5.d.ts → autoPoseDescriptor-Ct7yqivU.d.ts} +28 -3
  35. package/dist/barrel/chrome.js +16 -0
  36. package/dist/barrel/input.js +19 -0
  37. package/dist/barrel/interactions.js +42 -0
  38. package/dist/barrel/paint.js +18 -0
  39. package/dist/barrel/paths.js +19 -0
  40. package/dist/barrel/renderer.js +20 -0
  41. package/dist/barrel/routing.js +51 -0
  42. package/dist/barrel/text.js +18 -0
  43. package/dist/barrel/viewport.js +52 -0
  44. package/dist/builtins-DPcdghtB.d.ts +1223 -0
  45. package/dist/canvas/Canvas.js +363 -448
  46. package/dist/canvas/Canvas.js.map +1 -1
  47. package/dist/canvas/CanvasView.js +164 -165
  48. package/dist/canvas/CanvasView.js.map +1 -1
  49. package/dist/canvas/CursorCoordsHud.js +37 -32
  50. package/dist/canvas/CursorCoordsHud.js.map +1 -1
  51. package/dist/canvas/DrawCanvas.js +49 -43
  52. package/dist/canvas/DrawCanvas.js.map +1 -1
  53. package/dist/canvas/MinimapCanvas.js +26 -25
  54. package/dist/canvas/MinimapCanvas.js.map +1 -1
  55. package/dist/canvas/NodeShape.js +59 -377
  56. package/dist/canvas/NodeShape.js.map +1 -1
  57. package/dist/canvas/PickHud.js +50 -47
  58. package/dist/canvas/PickHud.js.map +1 -1
  59. package/dist/canvas/SceneCanvas/GestureDispatcherMounter.js +167 -0
  60. package/dist/canvas/SceneCanvas/GestureDispatcherMounter.js.map +1 -0
  61. package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +13 -13
  62. package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js +67 -0
  63. package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js.map +1 -0
  64. package/dist/canvas/SceneCanvas/ToolKeybindingsMounter.js +16 -0
  65. package/dist/canvas/SceneCanvas/ToolKeybindingsMounter.js.map +1 -0
  66. package/dist/canvas/SceneCanvas/coordTrace.js +10 -0
  67. package/dist/canvas/SceneCanvas/coordTrace.js.map +1 -0
  68. package/dist/canvas/SceneCanvas/defaultNodeProperties.js +146 -92
  69. package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
  70. package/dist/canvas/SceneCanvas/defaultNodeRouting.js +9 -8
  71. package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
  72. package/dist/canvas/SceneCanvas/features.js +1 -0
  73. package/dist/canvas/SceneCanvas/features.js.map +1 -1
  74. package/dist/canvas/SceneCanvas/isDev.js +12 -0
  75. package/dist/canvas/SceneCanvas/isDev.js.map +1 -0
  76. package/dist/canvas/SceneCanvas/poseGeometry.js +29 -28
  77. package/dist/canvas/SceneCanvas/poseGeometry.js.map +1 -1
  78. package/dist/canvas/SceneCanvas/poseRegistrars.js +13 -0
  79. package/dist/canvas/SceneCanvas/poseRegistrars.js.map +1 -0
  80. package/dist/canvas/SceneCanvas/props.js +38 -0
  81. package/dist/canvas/SceneCanvas/props.js.map +1 -0
  82. package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js +21 -62
  83. package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js.map +1 -1
  84. package/dist/canvas/SceneCanvas/useDispatcherOverlayLayer.js.map +1 -1
  85. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +46 -48
  86. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
  87. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +33 -33
  88. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  89. package/dist/canvas/SceneCanvas.js +534 -788
  90. package/dist/canvas/SceneCanvas.js.map +1 -1
  91. package/dist/canvas/SceneViewCanvas.js +45 -43
  92. package/dist/canvas/SceneViewCanvas.js.map +1 -1
  93. package/dist/canvas/buildSceneLayer.js +91 -0
  94. package/dist/canvas/buildSceneLayer.js.map +1 -0
  95. package/dist/canvas/buildSceneTree.js +16 -19
  96. package/dist/canvas/buildSceneTree.js.map +1 -1
  97. package/dist/canvas/canvasRenderer.js +68 -52
  98. package/dist/canvas/canvasRenderer.js.map +1 -1
  99. package/dist/canvas/deps/changes.js +13 -0
  100. package/dist/canvas/deps/changes.js.map +1 -0
  101. package/dist/canvas/deps/editAnchors.js +28 -28
  102. package/dist/canvas/deps/insert.js +119 -91
  103. package/dist/canvas/deps/insert.js.map +1 -1
  104. package/dist/canvas/deps/slice.js +46 -45
  105. package/dist/canvas/deps/slice.js.map +1 -1
  106. package/dist/canvas/deps/textEdit.js +10 -9
  107. package/dist/canvas/deps/textEdit.js.map +1 -1
  108. package/dist/canvas/derivedPath.js +46 -23
  109. package/dist/canvas/derivedPath.js.map +1 -1
  110. package/dist/canvas/getNodeAtPoint.js +7 -7
  111. package/dist/canvas/getNodeAtPoint.js.map +1 -1
  112. package/dist/canvas/hud.module.js +1 -1
  113. package/dist/canvas/hud.module.js.map +1 -1
  114. package/dist/canvas/kitRequirements.js +2 -2
  115. package/dist/canvas/kitRequirements.js.map +1 -1
  116. package/dist/canvas/layerOrder.js +13 -6
  117. package/dist/canvas/layerOrder.js.map +1 -1
  118. package/dist/canvas/minimapMath.js +12 -10
  119. package/dist/canvas/minimapMath.js.map +1 -1
  120. package/dist/canvas/nodeShapes/derivedPainter.js +34 -0
  121. package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -0
  122. package/dist/canvas/nodeShapes/imagePainter.js +87 -0
  123. package/dist/canvas/nodeShapes/imagePainter.js.map +1 -0
  124. package/dist/canvas/nodeShapes/memo.js +9 -0
  125. package/dist/canvas/nodeShapes/memo.js.map +1 -0
  126. package/dist/canvas/nodeShapes/nodePaint.js +57 -0
  127. package/dist/canvas/nodeShapes/nodePaint.js.map +1 -0
  128. package/dist/canvas/nodeShapes/pathPainter.js +38 -0
  129. package/dist/canvas/nodeShapes/pathPainter.js.map +1 -0
  130. package/dist/canvas/nodeShapes/poseBox.js +20 -0
  131. package/dist/canvas/nodeShapes/poseBox.js.map +1 -0
  132. package/dist/canvas/nodeShapes/rectFallbackPainter.js +38 -0
  133. package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -0
  134. package/dist/canvas/nodeShapes/shapePainter.js +72 -0
  135. package/dist/canvas/nodeShapes/shapePainter.js.map +1 -0
  136. package/dist/canvas/nodeShapes/textPainter.js +55 -0
  137. package/dist/canvas/nodeShapes/textPainter.js.map +1 -0
  138. package/dist/canvas/overlayRoleStrokes.js.map +1 -1
  139. package/dist/canvas/paintCull.js +10 -11
  140. package/dist/canvas/paintCull.js.map +1 -1
  141. package/dist/canvas/pickWalk.js +52 -63
  142. package/dist/canvas/pickWalk.js.map +1 -1
  143. package/dist/canvas/poseRotation.js +11 -23
  144. package/dist/canvas/poseRotation.js.map +1 -1
  145. package/dist/canvas/renderDebugSnapshot.js +5 -5
  146. package/dist/canvas/renderSceneToPixels.js +3 -1
  147. package/dist/canvas/renderSceneToPixels.js.map +1 -1
  148. package/dist/canvas/sceneAdapter.js +66 -148
  149. package/dist/canvas/sceneAdapter.js.map +1 -1
  150. package/dist/canvas/sceneClipboard.js +112 -0
  151. package/dist/canvas/sceneClipboard.js.map +1 -0
  152. package/dist/canvas/sceneSlotWiring.js +4 -4
  153. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  154. package/dist/canvas/sceneViewRender.js +39 -40
  155. package/dist/canvas/sceneViewRender.js.map +1 -1
  156. package/dist/canvas/useCanvasRenderer.js +14 -6
  157. package/dist/canvas/useCanvasRenderer.js.map +1 -1
  158. package/dist/canvas/useCanvasView.js +58 -0
  159. package/dist/canvas/useCanvasView.js.map +1 -0
  160. package/dist/canvas/useFrameLoop.js +35 -26
  161. package/dist/canvas/useFrameLoop.js.map +1 -1
  162. package/dist/canvas/useSceneLayerList.js +21 -0
  163. package/dist/canvas/useSceneLayerList.js.map +1 -0
  164. package/dist/clipboard.d.ts +10 -6
  165. package/dist/clone.d.ts +4 -3
  166. package/dist/core/adapters/arrayAdapter.js +48 -39
  167. package/dist/core/adapters/arrayAdapter.js.map +1 -1
  168. package/dist/core/device/index.js +5 -4
  169. package/dist/core/device/useDeviceProfile.js +38 -44
  170. package/dist/core/device/useDeviceProfile.js.map +1 -1
  171. package/dist/core/device/watchDevicePixelRatio.js +17 -0
  172. package/dist/core/device/watchDevicePixelRatio.js.map +1 -0
  173. package/dist/core/fillInPoseFrame.js.map +1 -1
  174. package/dist/core/geometry/poseDescriptor.js +21 -19
  175. package/dist/core/geometry/poseDescriptor.js.map +1 -1
  176. package/dist/core/geometry/poseFrame.js +44 -0
  177. package/dist/core/geometry/poseFrame.js.map +1 -0
  178. package/dist/core/geometry/poseRotation.js.map +1 -1
  179. package/dist/core/geometry/regionHit.js +9 -10
  180. package/dist/core/geometry/regionHit.js.map +1 -1
  181. package/dist/core/geometry/unionBounds.js +31 -29
  182. package/dist/core/geometry/unionBounds.js.map +1 -1
  183. package/dist/core/isPlainObject.js +2 -11
  184. package/dist/core/ops/arrange.js +1 -0
  185. package/dist/core/ops/arrange.js.map +1 -1
  186. package/dist/core/ops/create.js +10 -5
  187. package/dist/core/ops/create.js.map +1 -1
  188. package/dist/core/ops/delete.js +12 -24
  189. package/dist/core/ops/delete.js.map +1 -1
  190. package/dist/core/ops/reorder/index.js +43 -40
  191. package/dist/core/ops/reorder/index.js.map +1 -1
  192. package/dist/core/ops/reorder/setChildOrder.js +34 -0
  193. package/dist/core/ops/reorder/setChildOrder.js.map +1 -0
  194. package/dist/core/paintKinds.js +5 -5
  195. package/dist/core/paintKinds.js.map +1 -1
  196. package/dist/core/scene/NodeProperties.js.map +1 -1
  197. package/dist/core/scene/appliedLog.js +37 -0
  198. package/dist/core/scene/appliedLog.js.map +1 -0
  199. package/dist/core/scene/arrivalWindow.js +101 -0
  200. package/dist/core/scene/arrivalWindow.js.map +1 -0
  201. package/dist/core/scene/derivedPath.js.map +1 -1
  202. package/dist/core/scene/historyFacade.js +84 -0
  203. package/dist/core/scene/historyFacade.js.map +1 -0
  204. package/dist/core/scene/kitOps.js +173 -0
  205. package/dist/core/scene/kitOps.js.map +1 -0
  206. package/dist/core/scene/layerStack.js +38 -0
  207. package/dist/core/scene/layerStack.js.map +1 -0
  208. package/dist/core/scene/lockGuard.js +37 -0
  209. package/dist/core/scene/lockGuard.js.map +1 -0
  210. package/dist/core/scene/nodeFnFields.js +5 -6
  211. package/dist/core/scene/nodeFnFields.js.map +1 -1
  212. package/dist/core/scene/nodeMemo.js +45 -19
  213. package/dist/core/scene/nodeMemo.js.map +1 -1
  214. package/dist/core/scene/nodeStore.js +126 -0
  215. package/dist/core/scene/nodeStore.js.map +1 -0
  216. package/dist/core/scene/pickability.js +6 -0
  217. package/dist/core/scene/pickability.js.map +1 -0
  218. package/dist/core/scene/poseFeed.js +38 -38
  219. package/dist/core/scene/poseFeed.js.map +1 -1
  220. package/dist/core/scene/poseOverrides.js +34 -38
  221. package/dist/core/scene/poseOverrides.js.map +1 -1
  222. package/dist/core/scene/renderOrder.js +84 -0
  223. package/dist/core/scene/renderOrder.js.map +1 -0
  224. package/dist/core/scene/scene.js +289 -866
  225. package/dist/core/scene/scene.js.map +1 -1
  226. package/dist/core/scene/serialization.js +68 -0
  227. package/dist/core/scene/serialization.js.map +1 -0
  228. package/dist/core/scene/treeOrder.js +13 -0
  229. package/dist/core/scene/treeOrder.js.map +1 -0
  230. package/dist/core/scene/types.js.map +1 -1
  231. package/dist/core/viewport/meanScale.js +1 -1
  232. package/dist/core/viewport/meanScale.js.map +1 -1
  233. package/dist/core/viewport/view.js +12 -12
  234. package/dist/core/viewport/view.js.map +1 -1
  235. package/dist/core/viewport/zoomBounds.js +10 -8
  236. package/dist/core/viewport/zoomBounds.js.map +1 -1
  237. package/dist/{cornerHandles-BCrDp_mH.d.ts → cornerHandles-CtCb8a3G.d.ts} +1 -1
  238. package/dist/debug/createDebugOverlayLayer.js +14 -14
  239. package/dist/debug/createDebugOverlayLayer.js.map +1 -1
  240. package/dist/debug/flag.js +2 -41
  241. package/dist/features/chrome-caps/useHoverTracking.js +22 -17
  242. package/dist/features/chrome-caps/useHoverTracking.js.map +1 -1
  243. package/dist/features/focus/gateLayer.js +3 -4
  244. package/dist/features/focus/gateLayer.js.map +1 -1
  245. package/dist/features/images/imageCache.js +19 -9
  246. package/dist/features/images/imageCache.js.map +1 -1
  247. package/dist/features/ingestion/contentHandlers.js +14 -11
  248. package/dist/features/ingestion/contentHandlers.js.map +1 -1
  249. package/dist/features/ingestion/index.js +1 -1
  250. package/dist/features/ingestion/registerKitHandlers.js +7 -7
  251. package/dist/features/ingestion/weaselJsonHandler.js +17 -14
  252. package/dist/features/ingestion/weaselJsonHandler.js.map +1 -1
  253. package/dist/features/meshPaint/lazyKind.js +201 -35
  254. package/dist/features/meshPaint/lazyKind.js.map +1 -1
  255. package/dist/features/meshPaint/meshPaint.js +82 -79
  256. package/dist/features/meshPaint/meshPaint.js.map +1 -1
  257. package/dist/features/minimap/createMinimapContribution.js +42 -42
  258. package/dist/features/minimap/layers.js +44 -62
  259. package/dist/features/minimap/layers.js.map +1 -1
  260. package/dist/features/parallax/createParallaxPlane.js +10 -14
  261. package/dist/features/parallax/createParallaxPlane.js.map +1 -1
  262. package/dist/features/paths/anchorEdits.js +19 -8
  263. package/dist/features/paths/anchorEdits.js.map +1 -1
  264. package/dist/features/paths/anchors.js +31 -26
  265. package/dist/features/paths/anchors.js.map +1 -1
  266. package/dist/features/paths/booleans.js +2 -2
  267. package/dist/features/paths/index.js +13 -11
  268. package/dist/features/paths/markerCommands.js +37 -36
  269. package/dist/features/paths/markerCommands.js.map +1 -1
  270. package/dist/features/paths/pathInWorld.js +19 -19
  271. package/dist/features/paths/pathInWorld.js.map +1 -1
  272. package/dist/features/paths/pathLayer.js.map +1 -1
  273. package/dist/features/paths/strokeToPath.js +40 -0
  274. package/dist/features/paths/strokeToPath.js.map +1 -0
  275. package/dist/features/paths/tessellate/stroke.js.map +1 -1
  276. package/dist/features/patterns/commandPaint.js +18 -0
  277. package/dist/features/patterns/commandPaint.js.map +1 -0
  278. package/dist/features/patterns/index.js +5 -17
  279. package/dist/features/patterns/index.js.map +1 -1
  280. package/dist/features/patterns/rasterizeTile.js +17 -0
  281. package/dist/features/patterns/rasterizeTile.js.map +1 -0
  282. package/dist/features/patterns/resolveSpec.js +52 -44
  283. package/dist/features/patterns/resolveSpec.js.map +1 -1
  284. package/dist/features/poseRun/usePoseRun.js +8 -3
  285. package/dist/features/poseRun/usePoseRun.js.map +1 -1
  286. package/dist/features/selection/SelectionContext.js +5 -0
  287. package/dist/features/selection/SelectionContext.js.map +1 -1
  288. package/dist/features/selection/overlay.js +18 -18
  289. package/dist/features/selection/overlay.js.map +1 -1
  290. package/dist/features/text/domOffsets.js +82 -0
  291. package/dist/features/text/domOffsets.js.map +1 -0
  292. package/dist/features/text/domRuns.js +103 -131
  293. package/dist/features/text/domRuns.js.map +1 -1
  294. package/dist/features/text/hitTest.js +5 -2
  295. package/dist/features/text/hitTest.js.map +1 -1
  296. package/dist/features/text/isTextData.js +8 -0
  297. package/dist/features/text/isTextData.js.map +1 -0
  298. package/dist/features/text/overlayDirection.js +15 -0
  299. package/dist/features/text/overlayDirection.js.map +1 -0
  300. package/dist/features/text/runs/rangeStyle.js +32 -34
  301. package/dist/features/text/runs/rangeStyle.js.map +1 -1
  302. package/dist/features/text/textCommand.js +34 -33
  303. package/dist/features/text/textCommand.js.map +1 -1
  304. package/dist/features/text/textToPath.js +40 -40
  305. package/dist/features/text/textToPath.js.map +1 -1
  306. package/dist/features/text/useSceneTextEdit.js +76 -82
  307. package/dist/features/text/useSceneTextEdit.js.map +1 -1
  308. package/dist/features/text/useTextEdit.js +184 -160
  309. package/dist/features/text/useTextEdit.js.map +1 -1
  310. package/dist/features/tiling/createTiledLayer.js +24 -22
  311. package/dist/features/tiling/createTiledLayer.js.map +1 -1
  312. package/dist/index.d.ts +11010 -7700
  313. package/dist/index.js +316 -309
  314. package/dist/input/keyState.js +42 -43
  315. package/dist/input/keyState.js.map +1 -1
  316. package/dist/input/useKeyModifiers.js +11 -0
  317. package/dist/input/useKeyModifiers.js.map +1 -0
  318. package/dist/interactions/actions/align/align.js +8 -8
  319. package/dist/interactions/actions/clipboard/clipboardOps.js +34 -32
  320. package/dist/interactions/actions/clipboard/clipboardOps.js.map +1 -1
  321. package/dist/interactions/actions/clipboard/materializePaste.js +10 -7
  322. package/dist/interactions/actions/clipboard/materializePaste.js.map +1 -1
  323. package/dist/interactions/actions/clipboard/wireFormat.js.map +1 -1
  324. package/dist/interactions/actions/clone/behaviors/cloneByAltDrag.js.map +1 -1
  325. package/dist/interactions/actions/defaultCommitAdapter.js +20 -18
  326. package/dist/interactions/actions/defaultCommitAdapter.js.map +1 -1
  327. package/dist/interactions/actions/defaults/align.js +9 -10
  328. package/dist/interactions/actions/defaults/align.js.map +1 -1
  329. package/dist/interactions/actions/defaults/anchorEditing.js +59 -81
  330. package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
  331. package/dist/interactions/actions/defaults/areaSelect.js +35 -38
  332. package/dist/interactions/actions/defaults/areaSelect.js.map +1 -1
  333. package/dist/interactions/actions/defaults/clipboard.js +26 -10
  334. package/dist/interactions/actions/defaults/clipboard.js.map +1 -1
  335. package/dist/interactions/actions/defaults/clone.js +44 -60
  336. package/dist/interactions/actions/defaults/clone.js.map +1 -1
  337. package/dist/interactions/actions/defaults/copySubtree.js +40 -0
  338. package/dist/interactions/actions/defaults/copySubtree.js.map +1 -0
  339. package/dist/interactions/actions/defaults/distribute.js +14 -13
  340. package/dist/interactions/actions/defaults/distribute.js.map +1 -1
  341. package/dist/interactions/actions/defaults/duplicate.js +17 -57
  342. package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
  343. package/dist/interactions/actions/defaults/editAnchors.js +18 -18
  344. package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
  345. package/dist/interactions/actions/defaults/flip.js +52 -46
  346. package/dist/interactions/actions/defaults/flip.js.map +1 -1
  347. package/dist/interactions/actions/defaults/group.js +43 -48
  348. package/dist/interactions/actions/defaults/group.js.map +1 -1
  349. package/dist/interactions/actions/defaults/ingest.js +35 -33
  350. package/dist/interactions/actions/defaults/ingest.js.map +1 -1
  351. package/dist/interactions/actions/defaults/insert.js +50 -51
  352. package/dist/interactions/actions/defaults/insert.js.map +1 -1
  353. package/dist/interactions/actions/defaults/insertPathAnchor.js +7 -7
  354. package/dist/interactions/actions/defaults/lassoSelect.js +67 -70
  355. package/dist/interactions/actions/defaults/lassoSelect.js.map +1 -1
  356. package/dist/interactions/actions/defaults/move.js +267 -249
  357. package/dist/interactions/actions/defaults/move.js.map +1 -1
  358. package/dist/interactions/actions/defaults/nudge.js +40 -68
  359. package/dist/interactions/actions/defaults/nudge.js.map +1 -1
  360. package/dist/interactions/actions/defaults/nudgeSteps.js +37 -0
  361. package/dist/interactions/actions/defaults/nudgeSteps.js.map +1 -0
  362. package/dist/interactions/actions/defaults/reorder.js +2 -0
  363. package/dist/interactions/actions/defaults/reorder.js.map +1 -1
  364. package/dist/interactions/actions/defaults/resize.js +104 -98
  365. package/dist/interactions/actions/defaults/resize.js.map +1 -1
  366. package/dist/interactions/actions/defaults/rotate.js +111 -110
  367. package/dist/interactions/actions/defaults/rotate.js.map +1 -1
  368. package/dist/interactions/actions/defaults/setFill.js +7 -7
  369. package/dist/interactions/actions/defaults/setFillOpacity.js +4 -4
  370. package/dist/interactions/actions/defaults/setStroke.js +12 -12
  371. package/dist/interactions/actions/defaults/setStrokeOpacity.js +6 -6
  372. package/dist/interactions/actions/defaults/sliceOps.js +19 -19
  373. package/dist/interactions/actions/defaults/viewportDragPan.js +37 -26
  374. package/dist/interactions/actions/defaults/viewportDragPan.js.map +1 -1
  375. package/dist/interactions/actions/edit-anchors/geometry.js +28 -23
  376. package/dist/interactions/actions/edit-anchors/geometry.js.map +1 -1
  377. package/dist/interactions/actions/flip/helpers.js +30 -17
  378. package/dist/interactions/actions/flip/helpers.js.map +1 -1
  379. package/dist/interactions/actions/hitSelection.js +8 -0
  380. package/dist/interactions/actions/hitSelection.js.map +1 -0
  381. package/dist/interactions/actions/lasso-select/behaviors/selectFromLasso.js +14 -18
  382. package/dist/interactions/actions/lasso-select/behaviors/selectFromLasso.js.map +1 -1
  383. package/dist/interactions/actions/move/behaviors/snapBackOrDelete.js +5 -5
  384. package/dist/interactions/actions/move/behaviors/snapBackOrDelete.js.map +1 -1
  385. package/dist/interactions/actions/move/behaviors/snapToContainer.js +32 -21
  386. package/dist/interactions/actions/move/behaviors/snapToContainer.js.map +1 -1
  387. package/dist/interactions/actions/move/gestureAdapter.js +32 -15
  388. package/dist/interactions/actions/move/gestureAdapter.js.map +1 -1
  389. package/dist/interactions/actions/move/landedDescendants.js +33 -0
  390. package/dist/interactions/actions/move/landedDescendants.js.map +1 -0
  391. package/dist/interactions/actions/outlines/createOutlines.js +18 -18
  392. package/dist/interactions/actions/poseDescriptorDep.js +16 -4
  393. package/dist/interactions/actions/poseDescriptorDep.js.map +1 -1
  394. package/dist/interactions/actions/poseFrame.js +11 -6
  395. package/dist/interactions/actions/poseFrame.js.map +1 -1
  396. package/dist/interactions/actions/previewOverrides.js +5 -5
  397. package/dist/interactions/actions/previewOverrides.js.map +1 -1
  398. package/dist/interactions/actions/selectionSubtree.js +69 -0
  399. package/dist/interactions/actions/selectionSubtree.js.map +1 -0
  400. package/dist/interactions/actions/useStandardActions.js +66 -62
  401. package/dist/interactions/actions/useStandardActions.js.map +1 -1
  402. package/dist/interactions/gestures/dragGesture/dragGesture.js +4 -7
  403. package/dist/interactions/gestures/dragGesture/dragGesture.js.map +1 -1
  404. package/dist/interactions/gestures/dragGesture/dragPointOf.js +23 -0
  405. package/dist/interactions/gestures/dragGesture/dragPointOf.js.map +1 -0
  406. package/dist/interactions/gestures/handleDrag/handleDrag.js +21 -26
  407. package/dist/interactions/gestures/handleDrag/handleDrag.js.map +1 -1
  408. package/dist/interactions/gestures/pointerDrag/pointerDrag.js +8 -6
  409. package/dist/interactions/gestures/pointerDrag/pointerDrag.js.map +1 -1
  410. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js +8 -4
  411. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js.map +1 -1
  412. package/dist/{math-Drrw4ewZ.d.ts → math-J-ACHyEg.d.ts} +1 -2
  413. package/dist/math.d.ts +2 -3
  414. package/dist/move.d.ts +12 -11
  415. package/dist/{originProjection-DEklpDc_.d.ts → originProjection-gHnOup4e.d.ts} +1 -1
  416. package/dist/{registerProgram-BMZRDgrh.d.ts → registerProgram-T9S_PfAX.d.ts} +18 -7
  417. package/dist/renderer/DrawCommand.js.map +1 -1
  418. package/dist/renderer/WeaselRenderer.js +75 -73
  419. package/dist/renderer/WeaselRenderer.js.map +1 -1
  420. package/dist/renderer/cache/GLImageCache.js +15 -5
  421. package/dist/renderer/cache/GLImageCache.js.map +1 -1
  422. package/dist/renderer/cache/GLMeshCache.js +23 -23
  423. package/dist/renderer/cache/GLMeshCache.js.map +1 -1
  424. package/dist/renderer/cache/GLTextureCache.js +18 -1
  425. package/dist/renderer/cache/GLTextureCache.js.map +1 -1
  426. package/dist/renderer/cache/cache.js.map +1 -1
  427. package/dist/renderer/cache/markerCommandCache.js.map +1 -1
  428. package/dist/renderer/cache/strokeMeshCache.js.map +1 -1
  429. package/dist/renderer/cullDrawCommands.js +11 -11
  430. package/dist/renderer/draw/batch.js +133 -0
  431. package/dist/renderer/draw/batch.js.map +1 -0
  432. package/dist/renderer/draw/clip.js +89 -0
  433. package/dist/renderer/draw/clip.js.map +1 -0
  434. package/dist/renderer/draw/customShader.js +230 -0
  435. package/dist/renderer/draw/customShader.js.map +1 -0
  436. package/dist/renderer/draw/drawTriangles.js +8 -0
  437. package/dist/renderer/draw/drawTriangles.js.map +1 -0
  438. package/dist/renderer/draw/effects.js +28 -0
  439. package/dist/renderer/draw/effects.js.map +1 -0
  440. package/dist/renderer/draw/flushBatch.js +35 -0
  441. package/dist/renderer/draw/flushBatch.js.map +1 -0
  442. package/dist/renderer/draw/gradient.js +90 -0
  443. package/dist/renderer/draw/gradient.js.map +1 -0
  444. package/dist/renderer/draw/image.js +38 -0
  445. package/dist/renderer/draw/image.js.map +1 -0
  446. package/dist/renderer/draw/mesh.js +16 -0
  447. package/dist/renderer/draw/mesh.js.map +1 -0
  448. package/dist/renderer/draw/paintBind.js +70 -0
  449. package/dist/renderer/draw/paintBind.js.map +1 -0
  450. package/dist/renderer/draw/pathPaint.js +142 -0
  451. package/dist/renderer/draw/pathPaint.js.map +1 -0
  452. package/dist/renderer/draw/scratchPass.js +83 -0
  453. package/dist/renderer/draw/scratchPass.js.map +1 -0
  454. package/dist/renderer/draw/stencil.js +28 -0
  455. package/dist/renderer/draw/stencil.js.map +1 -0
  456. package/dist/renderer/draw/text.js +167 -0
  457. package/dist/renderer/draw/text.js.map +1 -0
  458. package/dist/renderer/draw/uniforms.js +77 -0
  459. package/dist/renderer/draw/uniforms.js.map +1 -0
  460. package/dist/renderer/draw.js +60 -1116
  461. package/dist/renderer/draw.js.map +1 -1
  462. package/dist/renderer/effects/EffectTargets.js +3 -0
  463. package/dist/renderer/effects/EffectTargets.js.map +1 -1
  464. package/dist/renderer/effects/composite.js +7 -5
  465. package/dist/renderer/effects/composite.js.map +1 -1
  466. package/dist/renderer/index.js +18 -17
  467. package/dist/renderer/math/color.js +63 -83
  468. package/dist/renderer/math/color.js.map +1 -1
  469. package/dist/renderer/math/mat3.js +15 -11
  470. package/dist/renderer/math/mat3.js.map +1 -1
  471. package/dist/renderer/math/viewToMat3.js +1 -1
  472. package/dist/renderer/math/viewToMat3.js.map +1 -1
  473. package/dist/renderer/shaders/ShaderProgram.js +13 -13
  474. package/dist/renderer/shaders/ShaderProgram.js.map +1 -1
  475. package/dist/renderer/shaders/glyphPaint.js +3 -3
  476. package/dist/renderer/shaders/glyphPaint.js.map +1 -1
  477. package/dist/renderer/shaders/gradFill.js +2 -1
  478. package/dist/renderer/shaders/gradFill.js.map +1 -1
  479. package/dist/renderer/shaders/pathFill.js +2 -1
  480. package/dist/renderer/shaders/pathFill.js.map +1 -1
  481. package/dist/renderer/shaders/patternFill.js +2 -1
  482. package/dist/renderer/shaders/patternFill.js.map +1 -1
  483. package/dist/renderer/shaders/registerProgram.js.map +1 -1
  484. package/dist/renderer/strokeLengths.js.map +1 -1
  485. package/dist/renderer/textures/registerTexture.js +25 -5
  486. package/dist/renderer/textures/registerTexture.js.map +1 -1
  487. package/dist/renderer.d.ts +16 -15
  488. package/dist/renderer.js +17 -16
  489. package/dist/resize.d.ts +5 -6
  490. package/dist/resize.js +5 -5
  491. package/dist/style.css +1 -1
  492. package/dist/test-hook/install.js +12 -6
  493. package/dist/test-hook/install.js.map +1 -1
  494. package/dist/{test-seams-CuIxe0xY.d.ts → test-seams-DM2B87-g.d.ts} +4 -4
  495. package/dist/test-seams.d.ts +2 -2
  496. package/dist/tools/builtin/ellipse/useEllipseTool.js +1 -0
  497. package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
  498. package/dist/tools/builtin/hand/useHandTool.js +1 -0
  499. package/dist/tools/builtin/hand/useHandTool.js.map +1 -1
  500. package/dist/tools/builtin/lasso/useLassoTool.js +11 -11
  501. package/dist/tools/builtin/lasso/useLassoTool.js.map +1 -1
  502. package/dist/tools/builtin/line/useLineTool.js +1 -0
  503. package/dist/tools/builtin/line/useLineTool.js.map +1 -1
  504. package/dist/tools/builtin/pen/usePenTool.js +153 -103
  505. package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
  506. package/dist/tools/builtin/pencil/usePencilTool.js +1 -0
  507. package/dist/tools/builtin/pencil/usePencilTool.js.map +1 -1
  508. package/dist/tools/builtin/polygon/usePolygonTool.js +13 -8
  509. package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
  510. package/dist/tools/builtin/rect/useRectTool.js +1 -0
  511. package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
  512. package/dist/tools/builtin/select/selectionContributions.js +24 -23
  513. package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
  514. package/dist/tools/builtin/select/useSelectTool.js +51 -48
  515. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  516. package/dist/tools/builtin/shared/pointerFollower.js +17 -0
  517. package/dist/tools/builtin/shared/pointerFollower.js.map +1 -0
  518. package/dist/tools/builtin/slice/useSliceTool.js +49 -54
  519. package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
  520. package/dist/tools/builtin/star/useStarTool.js +22 -16
  521. package/dist/tools/builtin/star/useStarTool.js.map +1 -1
  522. package/dist/tools/builtin/text/useTextTool.js +13 -2
  523. package/dist/tools/builtin/text/useTextTool.js.map +1 -1
  524. package/dist/tools/useKeybindings.js +14 -30
  525. package/dist/tools/useKeybindings.js.map +1 -1
  526. package/dist/{types-B_9g6hMM.d.ts → types-B4iJRHC3.d.ts} +5 -1
  527. package/dist/{types-A37hYkeR.d.ts → types-k-5Oqh-U.d.ts} +31 -7
  528. package/dist/util/color.js +3 -3
  529. package/dist/util/color.js.map +1 -1
  530. package/dist/util/paint.js.map +1 -1
  531. package/dist/version.js +1 -1
  532. package/package.json +20 -18
  533. package/dist/autoPoseDescriptor-vCdt55xm.d.ts +0 -27
  534. package/dist/builtins-C-VgUlWI.d.ts +0 -4514
  535. package/dist/core/isPlainObject.js.map +0 -1
  536. package/dist/debug/flag.js.map +0 -1
  537. package/dist/interactions/actions/defaults/freshNodeId.js +0 -10
  538. package/dist/interactions/actions/defaults/freshNodeId.js.map +0 -1
  539. package/dist/renderer/shaders/imageFill.js +0 -14
  540. package/dist/renderer/shaders/imageFill.js.map +0 -1
  541. package/dist/tools/prefs.js +0 -47
  542. package/dist/tools/prefs.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"defaultCommitAdapter.js","names":["defaultCommitAdapter","scene","asNodeId","id","documentPose","selection","ids","parentId","orderedIds","pose","layer","layout","node","index","fnFieldsOfNode"],"sources":["../../../src/interactions/actions/defaultCommitAdapter.ts"],"sourcesContent":["/**\n * Shared scene-backed commit adapter for default actions.\n *\n * Default actions route data/layer/pose/parent mutations through the\n * consumer's `applyOps` commit hook when one is present; when it isn't, they\n * fall back to applying the committed ops directly against the scene through\n * this adapter. It carries the scene-backed op-apply methods (`setPose` /\n * `setParent` / `setData` / `setLayer` / `setLayout` / `removeNode` /\n * `insertNode` / `setChildOrder`) plus the read-side queries (`getNode` / `getNodes` /\n * `getPose` / `getParent` / `getChildren` / `getSelection`) and\n * `setSelection`. Known exclusions from the op factory roster — surfaces the\n * scene doesn't own: `setPath` and `setText`.\n *\n * Lives in `interactions/actions/` (not `canvas/sceneAdapter.ts`) so default\n * actions never import from `canvas/` — that back-edge would create an\n * `interactions → canvas → interactions` cycle. Mirrors `moveGestureAdapter`,\n * widened to all op-apply methods.\n */\nimport { fnFieldsOfNode } from 'core/scene/nodeFnFields';\nimport { documentPose } from 'core/scene/effectivePose';\nimport type { Node, NodeId, Scene } from 'core/scene/types';\nimport { asNodeId } from 'core/scene/types';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport type { LayoutStrategy } from '../../layout/types';\n\n/** Build the adapter the default actions commit through when the consumer\n * supplies no `applyOps` hook of its own — it applies ops straight to the\n * scene. Path and text ops are not covered; those surfaces belong to their\n * own owners.\n *\n * Pass `selection` (a `SelectionApi`'s `adapterMethods`) and selection reads\n * and writes go through it rather than the scene, so a canvas that gates its\n * selection — `selectable={false}` — gates what these batches select too. */\nexport function defaultCommitAdapter<TPose>(\n scene: Scene<unknown, string, TPose>,\n selection?: SelectionApi['adapterMethods'],\n) {\n return {\n getNode: (id: string) => scene.get(asNodeId(id)),\n getNodes: () => {\n return [...scene.renderOrderNodes()] as Node<unknown, string, TPose>[];\n },\n getPose: (id: string) => documentPose(scene, scene.get(asNodeId(id))!),\n getSelection: (): string[] => [...(selection ? selection.getSelection() : scene.getSelection())],\n setSelection: (ids: string[]) => {\n if (selection) selection.setSelection(ids.map(asNodeId));\n else scene.setSelection(ids.map(asNodeId));\n },\n getParent: (id: string) => (scene.get(asNodeId(id))?.parent ?? null) as string | null,\n getRemovalClosure: (ids: readonly string[]): string[] =>\n [...scene.removalClosure(ids.map(asNodeId))],\n getChildren: (parentId: string | null): string[] =>\n (parentId === null\n ? [...scene.roots]\n : [...scene.childrenOf(asNodeId(parentId))]) as string[],\n // Re-stamp each slot whose occupant changed. `scene.reorder` mutates the\n // live sibling list in place, so re-read it each iteration and skip slots\n // already holding the desired id (an earlier reorder may have shifted a\n // later id into place). One enclosing batch is provided by the caller\n // (`scene.applyBatch` / consumer `applyOps`), so no batch here.\n setChildOrder: (parentId: string | null, orderedIds: string[]): void => {\n for (let i = 0; i < orderedIds.length; i++) {\n const current = parentId === null\n ? (scene.roots as readonly NodeId[])\n : (scene.childrenOf(asNodeId(parentId)) as readonly NodeId[]);\n if ((current[i] as string) === orderedIds[i]) continue;\n scene.reorder(asNodeId(orderedIds[i]), i);\n }\n },\n setPose: (id: string, pose: TPose) => scene.setPose(asNodeId(id), pose),\n setParent: (id: string, parentId: string | null) =>\n scene.move(asNodeId(id), parentId == null ? null : asNodeId(parentId)),\n setData: (id: string, data: unknown) => scene.update(asNodeId(id), { data } as never),\n setLayer: (id: string, layer: string) => scene.setLayer(asNodeId(id), layer as never),\n setLayout: (id: string, layout: LayoutStrategy<TPose> | string | null) =>\n scene.setLayout(asNodeId(id), layout),\n removeNode: (id: string) => scene.remove(asNodeId(id)),\n insertNode: (node: Node<unknown, string, TPose>, index?: number) =>\n scene.add({\n kind: node.kind,\n layer: node.layer,\n pose: node.pose,\n data: node.data,\n id: node.id,\n ...(node.parent != null ? { parent: node.parent } : {}),\n // Sibling-index passthrough: undo of a delete (or a boolean op's\n // source removal) re-inserts at the recorded z-order slot instead\n // of appending at the end.\n ...(index !== undefined ? { index } : {}),\n ...(node.dependsOn !== undefined ? { dependsOn: node.dependsOn } : {}),\n ...fnFieldsOfNode(node),\n }),\n };\n}\n"],"mappings":";;;;AAiCA,SAAgBA,EACd,GACA,GACA;CACA,OAAO;EACL,UAAU,MAAeC,EAAM,IAAIC,EAASC,CAAE,CAAC;EAC/C,gBACS,CAAC,GAAGF,EAAM,iBAAiB,CAAC;EAErC,UAAU,MAAeG,EAAaH,GAAOA,EAAM,IAAIC,EAASC,CAAE,CAAC,CAAE;EACrE,oBAA8B,CAAC,GAAIE,IAAYA,EAAU,aAAa,IAAIJ,EAAM,aAAa,CAAE;EAC/F,eAAe,MAAkB;GAC/B,AAAII,IAAWA,EAAU,aAAaC,EAAI,IAAIJ,CAAQ,CAAC,IAClDD,EAAM,aAAaK,EAAI,IAAIJ,CAAQ,CAAC;EAC3C;EACA,YAAY,MAAgBD,EAAM,IAAIC,EAASC,CAAE,CAAC,CAAC,EAAE,UAAU;EAC/D,oBAAoB,MAClB,CAAC,GAAGF,EAAM,eAAeK,EAAI,IAAIJ,CAAQ,CAAC,CAAC;EAC7C,cAAc,MACXK,MAAa,OACV,CAAC,GAAGN,EAAM,KAAK,IACf,CAAC,GAAGA,EAAM,WAAWC,EAASK,CAAQ,CAAC,CAAC;EAM9C,gBAAgB,GAAyB,MAA+B;GACtE,KAAK,IAAI,IAAI,GAAG,IAAIC,EAAW,QAAQ,KAIrC,CAHgBD,MAAa,OACxBN,EAAM,QACNA,EAAM,WAAWC,EAASK,CAAQ,CAAC,EAAA,CAC3B,OAAkBC,EAAW,MAC1CP,EAAM,QAAQC,EAASM,EAAW,EAAE,GAAG,CAAC;EAE5C;EACA,UAAU,GAAY,MAAgBP,EAAM,QAAQC,EAASC,CAAE,GAAGM,CAAI;EACtE,YAAY,GAAY,MACtBR,EAAM,KAAKC,EAASC,CAAE,GAAGI,KAAY,OAAO,OAAOL,EAASK,CAAQ,CAAC;EACvE,UAAU,GAAY,MAAkBN,EAAM,OAAOC,EAASC,CAAE,GAAG,EAAE,QAAK,CAAU;EACpF,WAAW,GAAY,MAAkBF,EAAM,SAASC,EAASC,CAAE,GAAGO,CAAc;EACpF,YAAY,GAAY,MACtBT,EAAM,UAAUC,EAASC,CAAE,GAAGQ,CAAM;EACtC,aAAa,MAAeV,EAAM,OAAOC,EAASC,CAAE,CAAC;EACrD,aAAa,GAAoC,MAC/CF,EAAM,IAAI;GACR,MAAMW,EAAK;GACX,OAAOA,EAAK;GACZ,MAAMA,EAAK;GACX,MAAMA,EAAK;GACX,IAAIA,EAAK;GACT,GAAIA,EAAK,UAAU,OAAiC,CAAC,IAA3B,EAAE,QAAQA,EAAK,OAAO;GAIhD,GAAIC,MAAU,KAAA,IAAwB,CAAC,IAAb,EAAE,SAAM;GAClC,GAAID,EAAK,cAAc,KAAA,IAA4C,CAAC,IAAjC,EAAE,WAAWA,EAAK,UAAU;GAC/D,GAAGE,EAAeF,CAAI;EACxB,CAAC;CACL;AACF"}
1
+ {"version":3,"file":"defaultCommitAdapter.js","names":["defaultCommitAdapter","scene","asNodeId","id","documentPose","selection","ids","parentId","i","orderedIds","pose","layer","layout","node","index","pickability","fnFieldsOfNode"],"sources":["../../../src/interactions/actions/defaultCommitAdapter.ts"],"sourcesContent":["/**\n * Shared scene-backed commit adapter for default actions.\n *\n * Default actions route data/layer/pose/parent mutations through the\n * consumer's `applyOps` commit hook when one is present; when it isn't, they\n * fall back to applying the committed ops directly against the scene through\n * this adapter. It carries the scene-backed op-apply methods (`setPose` /\n * `setParent` / `setData` / `setLayer` / `setLayout` / `removeNode` /\n * `insertNode` / `setChildOrder`) plus the read-side queries (`getNode` / `getNodes` /\n * `getPose` / `getParent` / `getChildren` / `getSelection`) and\n * `setSelection`. Known exclusions from the op factory roster — surfaces the\n * scene doesn't own: `setPath` and `setText`.\n *\n * Lives in `interactions/actions/` (not `canvas/sceneAdapter.ts`) so default\n * actions never import from `canvas/` — that back-edge would create an\n * `interactions → canvas → interactions` cycle. Mirrors `moveGestureAdapter`,\n * widened to all op-apply methods.\n */\nimport { fnFieldsOfNode } from 'core/scene/nodeFnFields';\nimport { pickability } from 'core/scene/pickability';\nimport { documentPose } from 'core/scene/effectivePose';\nimport type { Node, NodeId, Scene } from 'core/scene/types';\nimport { asNodeId } from 'core/scene/types';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport type { LayoutStrategy } from '../../layout/types';\n\n/** Build the adapter the default actions commit through when the consumer\n * supplies no `applyOps` hook of its own — it applies ops straight to the\n * scene. Path and text ops are not covered; those surfaces belong to their\n * own owners.\n *\n * Pass `selection` (a `SelectionApi`'s `adapterMethods`) and selection reads\n * and writes go through it rather than the scene, so a canvas that gates its\n * selection — `selectable={false}` — gates what these batches select too. */\nexport function defaultCommitAdapter<TPose>(\n scene: Scene<unknown, string, TPose>,\n selection?: SelectionApi['adapterMethods'],\n) {\n return {\n getNode: (id: string) => scene.get(asNodeId(id)),\n getNodes: () => {\n return [...scene.renderOrderNodes()] as Node<unknown, string, TPose>[];\n },\n getPose: (id: string) => documentPose(scene, scene.get(asNodeId(id))!),\n getSelection: (): string[] => [...(selection ? selection.getSelection() : scene.getSelection())],\n setSelection: (ids: string[]) => {\n if (selection) selection.setSelection(ids.map(asNodeId));\n else scene.setSelection(ids.map(asNodeId));\n },\n getParent: (id: string) => (scene.get(asNodeId(id))?.parent ?? null) as string | null,\n getRemovalClosure: (ids: readonly string[]): string[] =>\n [...scene.removalClosure(ids.map(asNodeId))],\n getChildren: (parentId: string | null): string[] =>\n (parentId === null\n ? [...scene.roots]\n : [...scene.childrenOf(asNodeId(parentId))]) as string[],\n // Re-stamp each slot whose occupant changed. `scene.reorder` mutates the\n // live sibling list in place, so re-read it each iteration and skip slots\n // already holding the desired id (an earlier reorder may have shifted a\n // later id into place). One enclosing batch is provided by the caller\n // (`scene.applyBatch` / consumer `applyOps`), so no batch here.\n setChildOrder: (parentId: string | null, orderedIds: string[]): void => {\n for (let i = 0; i < orderedIds.length; i++) {\n const current = parentId === null\n ? (scene.roots as readonly NodeId[])\n : (scene.childrenOf(asNodeId(parentId)) as readonly NodeId[]);\n if ((current[i] as string) === orderedIds[i]) continue;\n scene.reorder(asNodeId(orderedIds[i]), i);\n }\n },\n setPose: (id: string, pose: TPose) => scene.setPose(asNodeId(id), pose),\n setParent: (id: string, parentId: string | null) =>\n scene.move(asNodeId(id), parentId == null ? null : asNodeId(parentId)),\n setData: (id: string, data: unknown) => scene.update(asNodeId(id), { data } as never),\n setLayer: (id: string, layer: string) => scene.setLayer(asNodeId(id), layer as never),\n setLayout: (id: string, layout: LayoutStrategy<TPose> | string | null) =>\n scene.setLayout(asNodeId(id), layout),\n removeNode: (id: string) => scene.remove(asNodeId(id)),\n insertNode: (node: Node<unknown, string, TPose>, index?: number) =>\n scene.add({\n kind: node.kind,\n layer: node.layer,\n pose: node.pose,\n data: node.data,\n id: node.id,\n ...(node.parent != null ? { parent: node.parent } : {}),\n // Sibling-index passthrough: undo of a delete (or a boolean op's\n // source removal) re-inserts at the recorded z-order slot instead\n // of appending at the end.\n ...(index !== undefined ? { index } : {}),\n ...(node.dependsOn !== undefined ? { dependsOn: node.dependsOn } : {}),\n ...pickability(node),\n ...fnFieldsOfNode(node),\n }),\n };\n}\n"],"mappings":";;;;;AAkCA,SAAgBA,EACd,GACA,GACA;CACA,OAAO;EACL,UAAU,MAAeC,EAAM,IAAIC,EAASC,CAAE,CAAC;EAC/C,gBACS,CAAC,GAAGF,EAAM,iBAAiB,CAAC;EAErC,UAAU,MAAeG,EAAaH,GAAOA,EAAM,IAAIC,EAASC,CAAE,CAAC,CAAE;EACrE,oBAA8B,CAAC,GAAIE,IAAYA,EAAU,aAAa,IAAIJ,EAAM,aAAa,CAAE;EAC/F,eAAe,MAAkB;GAC/B,AAAII,IAAWA,EAAU,aAAaC,EAAI,IAAIJ,CAAQ,CAAC,IAClDD,EAAM,aAAaK,EAAI,IAAIJ,CAAQ,CAAC;EAC3C;EACA,YAAY,MAAgBD,EAAM,IAAIC,EAASC,CAAE,CAAC,CAAC,EAAE,UAAU;EAC/D,oBAAoB,MAClB,CAAC,GAAGF,EAAM,eAAeK,EAAI,IAAIJ,CAAQ,CAAC,CAAC;EAC7C,cAAc,MACXK,MAAa,OACV,CAAC,GAAGN,EAAM,KAAK,IACf,CAAC,GAAGA,EAAM,WAAWC,EAASK,CAAQ,CAAC,CAAC;EAM9C,gBAAgB,GAAyB,MAA+B;GACtE,KAAK,IAAIC,IAAI,GAAGA,IAAIC,EAAW,QAAQ,KAIrC,CAHgBF,MAAa,OACxBN,EAAM,QACNA,EAAM,WAAWC,EAASK,CAAQ,CAAC,EAAA,CAC3BC,OAAkBC,EAAWD,MAC1CP,EAAM,QAAQC,EAASO,EAAWD,EAAE,GAAGA,CAAC;EAE5C;EACA,UAAU,GAAY,MAAgBP,EAAM,QAAQC,EAASC,CAAE,GAAGO,CAAI;EACtE,YAAY,GAAY,MACtBT,EAAM,KAAKC,EAASC,CAAE,GAAGI,KAAY,OAAO,OAAOL,EAASK,CAAQ,CAAC;EACvE,UAAU,GAAY,MAAkBN,EAAM,OAAOC,EAASC,CAAE,GAAG,EAAE,QAAK,CAAU;EACpF,WAAW,GAAY,MAAkBF,EAAM,SAASC,EAASC,CAAE,GAAGQ,CAAc;EACpF,YAAY,GAAY,MACtBV,EAAM,UAAUC,EAASC,CAAE,GAAGS,CAAM;EACtC,aAAa,MAAeX,EAAM,OAAOC,EAASC,CAAE,CAAC;EACrD,aAAa,GAAoC,MAC/CF,EAAM,IAAI;GACR,MAAMY,EAAK;GACX,OAAOA,EAAK;GACZ,MAAMA,EAAK;GACX,MAAMA,EAAK;GACX,IAAIA,EAAK;GACT,GAAIA,EAAK,UAAU,OAAiC,CAAC,IAA3B,EAAE,QAAQA,EAAK,OAAO;GAIhD,GAAIC,MAAU,KAAA,IAAwB,CAAC,IAAb,EAAE,SAAM;GAClC,GAAID,EAAK,cAAc,KAAA,IAA4C,CAAC,IAAjC,EAAE,WAAWA,EAAK,UAAU;GAC/D,GAAGE,EAAYF,CAAI;GACnB,GAAGG,EAAeH,CAAI;EACxB,CAAC;CACL;AACF"}
@@ -1,4 +1,5 @@
1
- import { translatePoseViaDescriptor as e, visualBoundsViaDescriptor as t } from "../../../core/geometry/poseDescriptor.js";
1
+ import { visualBoundsViaDescriptor as e } from "../../../core/geometry/poseDescriptor.js";
2
+ import { translateEach as t } from "../selectionSubtree.js";
2
3
  import { poseDescriptorOf as n } from "../poseDescriptorDep.js";
3
4
  import { scenePoseFrame as r } from "../poseFrame.js";
4
5
  import { actionReferenceSources as i } from "../spatialReference.js";
@@ -30,15 +31,13 @@ var h = {
30
31
  "center-y": /* @__PURE__ */ m(l, {})
31
32
  };
32
33
  function v(n, s, c, l, u, d, f) {
33
- let p = n.get(), m = r(s, u), h = p.map((e) => m.world(e)), g = h.map((e) => t(e, l)), _ = o(g, d?.to, i(d, f, s, m, l));
34
- _ !== null && s.batch("Align", () => {
35
- for (let t = 0; t < p.length; t++) {
36
- let { dx: n, dy: r } = a(g[t], _, c);
37
- if (n === 0 && r === 0) continue;
38
- let i = e(h[t], n, r, l);
39
- s.setPose(p[t], m.local(p[t], i));
40
- }
41
- });
34
+ let p = n.get(), m = r(s, u), h = p.map((e) => m.world(e)).map((t) => e(t, l)), g = o(h, d?.to, i(d, f, s, m, l));
35
+ if (g === null) return;
36
+ let _ = p.map((e, t) => ({
37
+ id: e,
38
+ ...a(h[t], g, c)
39
+ }));
40
+ s.batch("Align", () => t(s, m, l, _));
42
41
  }
43
42
  function y(e) {
44
43
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"align.js","names":["ID_FOR","\"left\"","\"right\"","\"top\"","\"bottom\"","LABEL_FOR","ICON_FOR","AlignLeftIcon","AlignRightIcon","AlignTopIcon","AlignBottomIcon","AlignCenterXIcon","AlignCenterYIcon","alignSelection","ids","selection","frame","scenePoseFrame","scene","poseComposition","poses","id","bounds","visualBoundsViaDescriptor","p","geom","target","alignTargetBounds","params","actionReferenceSources","pointer","i","alignDeltaFor","edge","dx","dy","to","translatePoseViaDescriptor","makeAlignAction","deps","poseDescriptorOf","ActionDisabledReason","alignLeftAction","alignRightAction","alignTopAction","alignBottomAction","alignCenterXAction","alignCenterYAction"],"sources":["../../../../src/interactions/actions/defaults/align.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type { Scene } from 'core/scene/types';\nimport type { PoseDescriptor } from '../resize/geometry';\nimport { poseDescriptorOf } from '../poseDescriptorDep';\nimport {\n alignDeltaFor,\n alignTargetBounds,\n translatePoseViaDescriptor,\n visualBoundsViaDescriptor,\n type AlignEdge,\n type AlignReference,\n} from '../align/align';\nimport { actionReferenceSources } from '../spatialReference';\nimport type { PointerContextValue } from 'features/pointer/PointerContext';\nimport { scenePoseFrame } from '../poseFrame';\nimport type { Action } from '@weasel-js/routing';\nimport { ActionDisabledReason } from '@weasel-js/routing';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport type { ImmediateInvoker } from '@weasel-js/routing';\nimport {\n AlignLeftIcon,\n AlignRightIcon,\n AlignTopIcon,\n AlignBottomIcon,\n AlignCenterXIcon,\n AlignCenterYIcon,\n} from './icons/alignIcons';\nconst ID_FOR: Record<AlignEdge, string> = {\n 'left': 'align.left',\n 'right': 'align.right',\n 'top': 'align.top',\n 'bottom': 'align.bottom',\n 'center-x': 'align.centerX',\n 'center-y': 'align.centerY',\n};\nconst LABEL_FOR: Record<AlignEdge, string> = {\n 'left': 'Align Left',\n 'right': 'Align Right',\n 'top': 'Align Top',\n 'bottom': 'Align Bottom',\n 'center-x': 'Align Centers Horizontally',\n 'center-y': 'Align Centers Vertically',\n};\nconst ICON_FOR: Record<AlignEdge, ReactNode> = {\n 'left': <AlignLeftIcon />,\n 'right': <AlignRightIcon />,\n 'top': <AlignTopIcon />,\n 'bottom': <AlignBottomIcon />,\n 'center-x': <AlignCenterXIcon />,\n 'center-y': <AlignCenterYIcon />,\n};\n\n// ---------------------------------------------------------------------------\n// Shared helper for descriptor invokers\n// ---------------------------------------------------------------------------\n\n/**\n * Apply an align operation to the current selection via the Scene API.\n * Reads poses through the `poseDescriptor` dep; what the selection aligns to\n * comes from `params.to` (an `AlignReference`, default `'union'`).\n *\n * The edge every member lines up on is a world edge, so the bounds are read\n * and translated in world and each result is stored back in its own parent's\n * frame.\n */\nfunction alignSelection(\n selection: SelectionApi,\n scene: Scene<unknown, string, unknown>,\n edge: AlignEdge,\n geom: PoseDescriptor<unknown>,\n poseComposition: unknown,\n params: Record<string, unknown> | undefined,\n pointer: PointerContextValue | undefined,\n): void {\n const ids = selection.get();\n const frame = scenePoseFrame(scene, poseComposition);\n const poses = ids.map((id) => frame.world(id));\n const bounds = poses.map((p) => visualBoundsViaDescriptor(p, geom));\n const target = alignTargetBounds(\n bounds,\n params?.to as AlignReference | undefined,\n actionReferenceSources(params, pointer, scene, frame, geom),\n );\n if (target === null) return;\n scene.batch('Align', () => {\n for (let i = 0; i < ids.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 scene.setPose(ids[i], frame.local(ids[i], to));\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// Static descriptors\n// ---------------------------------------------------------------------------\n\nfunction makeAlignAction(edge: AlignEdge): Action {\n return {\n id: ID_FOR[edge],\n label: LABEL_FOR[edge],\n icon: ICON_FOR[edge],\n group: 'align',\n eligible: { capability: 'transforms-selection' },\n requires: ['selection', 'scene', 'poseDescriptor', 'poseComposition', 'pointer'],\n // No default keybindings — six edges/centers don't fit a clean default\n // chord set. Wire bindings explicitly via the actions registry override map.\n invoker: {\n timing: 'immediate',\n run: (deps, params) => {\n const selection = deps.selection;\n const scene = deps.scene;\n if (!selection || !scene) return;\n alignSelection(\n selection, scene, edge, poseDescriptorOf(deps.poseDescriptor), deps.poseComposition,\n params, deps.pointer,\n );\n },\n } satisfies ImmediateInvoker,\n // One item is enough: `enabled` cannot see the binding's `to`, and every\n // reference but `'union'` aligns a single item. Deps-aware, since a\n // constant disabled reason greys the entry out forever (see\n // `requiresSelection`).\n enabled: (deps) => {\n const selection = deps?.selection;\n const count = selection?.get().length ?? 0;\n return count >= 1 ? true : ActionDisabledReason.SelectionRequired;\n },\n };\n}\n\n/** @experimental Static descriptor for align-left. */\nexport const alignLeftAction = makeAlignAction('left');\n/** @experimental Static descriptor for align-right. */\nexport const alignRightAction = makeAlignAction('right');\n/** @experimental Static descriptor for align-top. */\nexport const alignTopAction = makeAlignAction('top');\n/** @experimental Static descriptor for align-bottom. */\nexport const alignBottomAction = makeAlignAction('bottom');\n/** @experimental Static descriptor for align-center-x. */\nexport const alignCenterXAction = makeAlignAction('center-x');\n/** @experimental Static descriptor for align-center-y. */\nexport const alignCenterYAction = makeAlignAction('center-y');\n\n"],"mappings":";;;;;;;;;AA2BA,IAAMA,IAAoC;CACxCC,MAAQ;CACRC,OAAS;CACTC,KAAO;CACPC,QAAU;CACV,YAAY;CACZ,YAAY;AACd,GACMC,IAAuC;CAC3CJ,MAAQ;CACRC,OAAS;CACTC,KAAO;CACPC,QAAU;CACV,YAAY;CACZ,YAAY;AACd,GACME,IAAyC;CAC7CL,MAAQ,kBAACM,GAAD,CAAgB,CAAA;CACxBL,OAAS,kBAACM,GAAD,CAAiB,CAAA;CAC1BL,KAAO,kBAACM,GAAD,CAAe,CAAA;CACtBL,QAAU,kBAACM,GAAD,CAAkB,CAAA;CAC5B,YAAY,kBAACC,GAAD,CAAmB,CAAA;CAC/B,YAAY,kBAACC,GAAD,CAAmB,CAAA;AACjC;AAeA,SAASC,EACP,GACA,GACA,GACA,GACA,GACA,GACA,GACM;CACN,IAAMC,IAAMC,EAAU,IAAI,GACpBC,IAAQC,EAAeC,GAAOC,CAAe,GAC7CC,IAAQN,EAAI,KAAK,MAAOE,EAAM,MAAMK,CAAE,CAAC,GACvCC,IAASF,EAAM,KAAK,MAAMG,EAA0BC,GAAGC,CAAI,CAAC,GAC5DC,IAASC,EACbL,GACAM,GAAQ,IACRC,EAAuBD,GAAQE,GAASZ,GAAOF,GAAOS,CAAI,CAC5D;CACA,AAAIC,MAAW,QACfR,EAAM,MAAM,eAAe;EACzB,KAAK,IAAIa,IAAI,GAAGA,IAAIjB,EAAI,QAAQ,KAAK;GACnC,IAAM,EAAE,OAAI,UAAOkB,EAAcV,EAAOS,IAAIL,GAAQO,CAAI;GACxD,IAAIC,MAAO,KAAKC,MAAO,GAAG;GAC1B,IAAMC,IAAKC,EAA2BjB,EAAMW,IAAIG,GAAIC,GAAIV,CAAI;GAC5D,EAAM,QAAQX,EAAIiB,IAAIf,EAAM,MAAMF,EAAIiB,IAAIK,CAAE,CAAC;EAC/C;CACF,CAAC;AACH;AAMA,SAASE,EAAgB,GAAyB;CAChD,OAAO;EACL,IAAItC,EAAOiC;EACX,OAAO5B,EAAU4B;EACjB,MAAM3B,EAAS2B;EACf,OAAO;EACP,UAAU,EAAE,YAAY,uBAAuB;EAC/C,UAAU;GAAC;GAAa;GAAS;GAAkB;GAAmB;EAAS;EAG/E,SAAS;GACP,QAAQ;GACR,MAAM,GAAM,MAAW;IACrB,IAAMlB,IAAYwB,EAAK,WACjBrB,IAAQqB,EAAK;IACnB,AAAKxB,KAAcG,KACnBL,EACEE,GAAWG,GAAOe,GAAMO,EAAiBD,EAAK,cAAc,GAAGA,EAAK,iBACpEX,GAAQW,EAAK,OACf;GACF;EACF;EAKA,UAAU,QACUA,GAAM,UAAA,EACC,IAAI,CAAC,CAAC,UAAU,MACzB,KAAWE,EAAqB;CAEpD;AACF;AAGA,IAAaC,IAAqBJ,EAAgB,MAAM,GAE3CK,IAAqBL,EAAgB,OAAO,GAE5CM,IAAqBN,EAAgB,KAAK,GAE1CO,IAAqBP,EAAgB,QAAQ,GAE7CQ,IAAqBR,EAAgB,UAAU,GAE/CS,IAAqBT,EAAgB,UAAU"}
1
+ {"version":3,"file":"align.js","names":["ID_FOR","\"left\"","\"right\"","\"top\"","\"bottom\"","LABEL_FOR","ICON_FOR","AlignLeftIcon","AlignRightIcon","AlignTopIcon","AlignBottomIcon","AlignCenterXIcon","AlignCenterYIcon","alignSelection","ids","selection","frame","scenePoseFrame","scene","poseComposition","bounds","id","visualBoundsViaDescriptor","p","geom","target","alignTargetBounds","params","actionReferenceSources","pointer","moves","alignDeltaFor","i","edge","translateEach","makeAlignAction","deps","poseDescriptorOf","ActionDisabledReason","alignLeftAction","alignRightAction","alignTopAction","alignBottomAction","alignCenterXAction","alignCenterYAction"],"sources":["../../../../src/interactions/actions/defaults/align.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type { Scene } from 'core/scene/types';\nimport type { PoseDescriptor } from '../resize/geometry';\nimport { poseDescriptorOf } from '../poseDescriptorDep';\nimport {\n alignDeltaFor,\n alignTargetBounds,\n visualBoundsViaDescriptor,\n type AlignEdge,\n type AlignReference,\n} from '../align/align';\nimport { actionReferenceSources } from '../spatialReference';\nimport type { PointerContextValue } from 'features/pointer/PointerContext';\nimport { scenePoseFrame } from '../poseFrame';\nimport { translateEach } from '../selectionSubtree';\nimport type { Action } from '@weasel-js/routing';\nimport { ActionDisabledReason } from '@weasel-js/routing';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport type { ImmediateInvoker } from '@weasel-js/routing';\nimport {\n AlignLeftIcon,\n AlignRightIcon,\n AlignTopIcon,\n AlignBottomIcon,\n AlignCenterXIcon,\n AlignCenterYIcon,\n} from './icons/alignIcons';\nconst ID_FOR: Record<AlignEdge, string> = {\n 'left': 'align.left',\n 'right': 'align.right',\n 'top': 'align.top',\n 'bottom': 'align.bottom',\n 'center-x': 'align.centerX',\n 'center-y': 'align.centerY',\n};\nconst LABEL_FOR: Record<AlignEdge, string> = {\n 'left': 'Align Left',\n 'right': 'Align Right',\n 'top': 'Align Top',\n 'bottom': 'Align Bottom',\n 'center-x': 'Align Centers Horizontally',\n 'center-y': 'Align Centers Vertically',\n};\nconst ICON_FOR: Record<AlignEdge, ReactNode> = {\n 'left': <AlignLeftIcon />,\n 'right': <AlignRightIcon />,\n 'top': <AlignTopIcon />,\n 'bottom': <AlignBottomIcon />,\n 'center-x': <AlignCenterXIcon />,\n 'center-y': <AlignCenterYIcon />,\n};\n\n// ---------------------------------------------------------------------------\n// Shared helper for descriptor invokers\n// ---------------------------------------------------------------------------\n\n/**\n * Apply an align operation to the current selection via the Scene API.\n * Reads poses through the `poseDescriptor` dep; what the selection aligns to\n * comes from `params.to` (an `AlignReference`, default `'union'`).\n *\n * The edge every member lines up on is a world edge, so the bounds are read\n * and translated in world and each result is stored back in its own parent's\n * frame.\n */\nfunction alignSelection(\n selection: SelectionApi,\n scene: Scene<unknown, string, unknown>,\n edge: AlignEdge,\n geom: PoseDescriptor<unknown>,\n poseComposition: unknown,\n params: Record<string, unknown> | undefined,\n pointer: PointerContextValue | undefined,\n): void {\n const ids = selection.get();\n const frame = scenePoseFrame(scene, poseComposition);\n const poses = ids.map((id) => frame.world(id));\n const bounds = poses.map((p) => visualBoundsViaDescriptor(p, geom));\n const target = alignTargetBounds(\n bounds,\n params?.to as AlignReference | undefined,\n actionReferenceSources(params, pointer, scene, frame, geom),\n );\n if (target === null) return;\n const moves = ids.map((id, i) => ({ id, ...alignDeltaFor(bounds[i], target, edge) }));\n scene.batch('Align', () => translateEach(scene, frame, geom, moves));\n}\n\n// ---------------------------------------------------------------------------\n// Static descriptors\n// ---------------------------------------------------------------------------\n\nfunction makeAlignAction(edge: AlignEdge): Action {\n return {\n id: ID_FOR[edge],\n label: LABEL_FOR[edge],\n icon: ICON_FOR[edge],\n group: 'align',\n eligible: { capability: 'transforms-selection' },\n requires: ['selection', 'scene', 'poseDescriptor', 'poseComposition', 'pointer'],\n // No default keybindings — six edges/centers don't fit a clean default\n // chord set. Wire bindings explicitly via the actions registry override map.\n invoker: {\n timing: 'immediate',\n run: (deps, params) => {\n const selection = deps.selection;\n const scene = deps.scene;\n if (!selection || !scene) return;\n alignSelection(\n selection, scene, edge, poseDescriptorOf(deps.poseDescriptor), deps.poseComposition,\n params, deps.pointer,\n );\n },\n } satisfies ImmediateInvoker,\n // One item is enough: `enabled` cannot see the binding's `to`, and every\n // reference but `'union'` aligns a single item. Deps-aware, since a\n // constant disabled reason greys the entry out forever (see\n // `requiresSelection`).\n enabled: (deps) => {\n const selection = deps?.selection;\n const count = selection?.get().length ?? 0;\n return count >= 1 ? true : ActionDisabledReason.SelectionRequired;\n },\n };\n}\n\n/** @experimental Static descriptor for align-left. */\nexport const alignLeftAction = makeAlignAction('left');\n/** @experimental Static descriptor for align-right. */\nexport const alignRightAction = makeAlignAction('right');\n/** @experimental Static descriptor for align-top. */\nexport const alignTopAction = makeAlignAction('top');\n/** @experimental Static descriptor for align-bottom. */\nexport const alignBottomAction = makeAlignAction('bottom');\n/** @experimental Static descriptor for align-center-x. */\nexport const alignCenterXAction = makeAlignAction('center-x');\n/** @experimental Static descriptor for align-center-y. */\nexport const alignCenterYAction = makeAlignAction('center-y');\n\n"],"mappings":";;;;;;;;;;AA2BA,IAAMA,IAAoC;CACxCC,MAAQ;CACRC,OAAS;CACTC,KAAO;CACPC,QAAU;CACV,YAAY;CACZ,YAAY;AACd,GACMC,IAAuC;CAC3CJ,MAAQ;CACRC,OAAS;CACTC,KAAO;CACPC,QAAU;CACV,YAAY;CACZ,YAAY;AACd,GACME,IAAyC;CAC7CL,MAAQ,kBAACM,GAAD,CAAgB,CAAA;CACxBL,OAAS,kBAACM,GAAD,CAAiB,CAAA;CAC1BL,KAAO,kBAACM,GAAD,CAAe,CAAA;CACtBL,QAAU,kBAACM,GAAD,CAAkB,CAAA;CAC5B,YAAY,kBAACC,GAAD,CAAmB,CAAA;CAC/B,YAAY,kBAACC,GAAD,CAAmB,CAAA;AACjC;AAeA,SAASC,EACP,GACA,GACA,GACA,GACA,GACA,GACA,GACM;CACN,IAAMC,IAAMC,EAAU,IAAI,GACpBC,IAAQC,EAAeC,GAAOC,CAAe,GAE7CC,IADQN,EAAI,KAAK,MAAOE,EAAM,MAAMK,CAAE,CAC7B,CAAA,CAAM,KAAK,MAAMC,EAA0BC,GAAGC,CAAI,CAAC,GAC5DC,IAASC,EACbN,GACAO,GAAQ,IACRC,EAAuBD,GAAQE,GAASX,GAAOF,GAAOQ,CAAI,CAC5D;CACA,IAAIC,MAAW,MAAM;CACrB,IAAMK,IAAQhB,EAAI,KAAK,GAAI,OAAO;EAAE;EAAI,GAAGiB,EAAcX,EAAOY,IAAIP,GAAQQ,CAAI;CAAE,EAAE;CACpF,EAAM,MAAM,eAAeC,EAAchB,GAAOF,GAAOQ,GAAMM,CAAK,CAAC;AACrE;AAMA,SAASK,EAAgB,GAAyB;CAChD,OAAO;EACL,IAAInC,EAAOiC;EACX,OAAO5B,EAAU4B;EACjB,MAAM3B,EAAS2B;EACf,OAAO;EACP,UAAU,EAAE,YAAY,uBAAuB;EAC/C,UAAU;GAAC;GAAa;GAAS;GAAkB;GAAmB;EAAS;EAG/E,SAAS;GACP,QAAQ;GACR,MAAM,GAAM,MAAW;IACrB,IAAMlB,IAAYqB,EAAK,WACjBlB,IAAQkB,EAAK;IACnB,AAAKrB,KAAcG,KACnBL,EACEE,GAAWG,GAAOe,GAAMI,EAAiBD,EAAK,cAAc,GAAGA,EAAK,iBACpET,GAAQS,EAAK,OACf;GACF;EACF;EAKA,UAAU,QACUA,GAAM,UAAA,EACC,IAAI,CAAC,CAAC,UAAU,MACzB,KAAWE,EAAqB;CAEpD;AACF;AAGA,IAAaC,IAAqBJ,EAAgB,MAAM,GAE3CK,IAAqBL,EAAgB,OAAO,GAE5CM,IAAqBN,EAAgB,KAAK,GAE1CO,IAAqBP,EAAgB,QAAQ,GAE7CQ,IAAqBR,EAAgB,UAAU,GAE/CS,IAAqBT,EAAgB,UAAU"}
@@ -1,29 +1,37 @@
1
- import { editingLayer as e, inPlane as t } from "../planeInput.js";
2
- import { pathToAnchors as n } from "../../../features/paths/anchors.js";
3
- import { anchorsInRect as r, deleteAnchorsAt as i, editAnchorSet as a, openSubpathAt as o, translateAnchorBy as s } from "../../../features/paths/anchorEdits.js";
4
- import { selectionAfterAnchorPress as c } from "./editAnchors.js";
5
- import { ActionDisabledReason as l } from "@weasel-js/routing";
1
+ import { pxExtent as e } from "../../../core/viewport/pxExtent.js";
2
+ import { pathToAnchors as t } from "../../../features/paths/anchors.js";
3
+ import { editingLayer as n, inPlane as r } from "../planeInput.js";
4
+ import { anchorsInRect as i, deleteAnchorsAt as a, editAnchorSet as o, openSubpathAt as s, translateAnchorBy as c } from "../../../features/paths/anchorEdits.js";
5
+ import { selectionAfterAnchorPress as l } from "./editAnchors.js";
6
+ import { NUDGE_KEY as u, NUDGE_LABEL as d, nudgeDelta as f } from "./nudgeSteps.js";
7
+ import { ActionDisabledReason as p } from "@weasel-js/routing";
6
8
  //#region src/interactions/actions/defaults/anchorEditing.ts
7
- var u = 1, d = 10, f = 10;
8
- function p(e) {
9
+ var m = 10;
10
+ function h(t) {
11
+ let n = t?.view?.get().scale;
12
+ if (!n) return m;
13
+ let r = e(m, n);
14
+ return Math.max(r.x, r.y);
15
+ }
16
+ function g(e) {
9
17
  let t = e?.editAnchors;
10
18
  return !t || !t.editingId ? null : t;
11
19
  }
12
- function m(e) {
20
+ function _(e) {
13
21
  let t = e.getEditablePath(e.editingId);
14
22
  return !t || t.kind !== "polygon" ? null : t;
15
23
  }
16
- function h(e, t, n) {
17
- let r = m(e);
24
+ function v(e, t, n) {
25
+ let r = _(e);
18
26
  if (!r) return !1;
19
- let i = a(r, n);
27
+ let i = o(r, n);
20
28
  return i ? (e.applyEdit(e.editingId, i, t), !0) : !1;
21
29
  }
22
- function g(e) {
23
- let t = p(e);
24
- return !t || t.selectedAnchors.size === 0 ? l.SelectionRequired : !0;
30
+ function y(e) {
31
+ let t = g(e);
32
+ return !t || t.selectedAnchors.size === 0 ? p.SelectionRequired : !0;
25
33
  }
26
- var _ = t({
34
+ var b = r({
27
35
  id: "selectAnchor",
28
36
  label: "Select anchor",
29
37
  defaultBinding: [{
@@ -40,71 +48,41 @@ var _ = t({
40
48
  opts: { params: { additive: !0 } }
41
49
  }],
42
50
  eligible: { capability: "edits-anchors" },
43
- requires: ["editAnchors"],
51
+ requires: ["editAnchors", "view"],
44
52
  invoker: {
45
53
  timing: "immediate",
46
54
  run: (e, t) => {
47
- let n = p(e);
55
+ let n = g(e);
48
56
  if (!n) return;
49
57
  let r = t?.worldX, i = t?.worldY;
50
58
  if (typeof r != "number" || typeof i != "number") return;
51
- let a = m(n);
59
+ let a = _(n);
52
60
  if (!a) return;
53
- let o = O(a, r, i, f), s = t?.additive === !0;
61
+ let o = O(a, r, i, h(e)), s = t?.additive === !0;
54
62
  if (o === -1) {
55
63
  s || n.setSelectedAnchors([]);
56
64
  return;
57
65
  }
58
- n.setSelectedAnchors(c(n.selectedAnchors, o, s));
66
+ n.setSelectedAnchors(l(n.selectedAnchors, o, s));
59
67
  }
60
68
  },
61
- enabled: (e) => p(e) ? !0 : l.SelectionRequired
62
- }, e), v = {
63
- up: "ArrowUp",
64
- down: "ArrowDown",
65
- left: "ArrowLeft",
66
- right: "ArrowRight"
67
- }, y = {
68
- up: "Up",
69
- down: "Down",
70
- left: "Left",
71
- right: "Right"
72
- };
73
- function b(e, t) {
74
- switch (e) {
75
- case "up": return {
76
- dx: 0,
77
- dy: -t
78
- };
79
- case "down": return {
80
- dx: 0,
81
- dy: t
82
- };
83
- case "left": return {
84
- dx: -t,
85
- dy: 0
86
- };
87
- case "right": return {
88
- dx: t,
89
- dy: 0
90
- };
91
- }
92
- }
69
+ enabled: (e) => g(e) ? !0 : p.SelectionRequired
70
+ }, n);
93
71
  function x(e) {
94
72
  return {
95
73
  id: `nudgeAnchors.${e}`,
96
- label: `Nudge Anchors ${y[e]}`,
74
+ label: `Nudge Anchors ${d[e]}`,
97
75
  group: "nudgeAnchors",
98
76
  defaultBinding: [{
99
77
  spec: {
100
78
  kind: "key",
101
- key: v[e]
79
+ key: u[e]
102
80
  },
103
81
  opts: { params: { magnitude: "small" } }
104
82
  }, {
105
83
  spec: {
106
84
  kind: "key",
107
- key: v[e],
85
+ key: u[e],
108
86
  mods: { shift: !0 }
109
87
  },
110
88
  opts: { params: { magnitude: "big" } }
@@ -114,17 +92,17 @@ function x(e) {
114
92
  invoker: {
115
93
  timing: "immediate",
116
94
  run: (t, n) => {
117
- let r = p(t);
95
+ let r = g(t);
118
96
  if (!r || r.selectedAnchors.size === 0) return;
119
- let { dx: i, dy: a } = b(e, n?.magnitude === "big" ? d : u), o = [...r.selectedAnchors];
120
- h(r, "Nudge anchor", (e) => {
97
+ let { dx: i, dy: a } = f(e, n?.magnitude), o = [...r.selectedAnchors];
98
+ v(r, "Nudge anchor", (e) => {
121
99
  let t = !1;
122
- for (let n of o) t = s(e, n, i, a) || t;
100
+ for (let n of o) t = c(e, n, i, a) || t;
123
101
  return t;
124
102
  });
125
103
  }
126
104
  },
127
- enabled: g
105
+ enabled: y
128
106
  };
129
107
  }
130
108
  var S = x("up"), C = x("down"), w = x("left"), T = x("right"), E = {
@@ -143,14 +121,14 @@ var S = x("up"), C = x("down"), w = x("left"), T = x("right"), E = {
143
121
  invoker: {
144
122
  timing: "immediate",
145
123
  run: (e) => {
146
- let t = p(e);
124
+ let t = g(e);
147
125
  if (!t || t.selectedAnchors.size === 0) return;
148
126
  let n = [...t.selectedAnchors];
149
- h(t, "Delete anchor", (e) => i(e, n)) && t.setSelectedAnchors([]);
127
+ v(t, "Delete anchor", (e) => a(e, n)) && t.setSelectedAnchors([]);
150
128
  }
151
129
  },
152
- enabled: g
153
- }, D = t({
130
+ enabled: y
131
+ }, D = r({
154
132
  id: "cutPathAtAnchor",
155
133
  label: "Cut path at anchor",
156
134
  defaultBinding: {
@@ -161,31 +139,31 @@ var S = x("up"), C = x("down"), w = x("left"), T = x("right"), E = {
161
139
  }
162
140
  },
163
141
  eligible: { capability: "edits-anchors" },
164
- requires: ["editAnchors"],
142
+ requires: ["editAnchors", "view"],
165
143
  invoker: {
166
144
  timing: "immediate",
167
145
  run: (e, t) => {
168
- let n = p(e);
146
+ let n = g(e);
169
147
  if (!n) return;
170
148
  let r = t?.worldX, i = t?.worldY;
171
149
  if (typeof r != "number" || typeof i != "number") return;
172
- let a = m(n);
150
+ let a = _(n);
173
151
  if (!a) return;
174
- let s = O(a, r, i, f);
175
- s !== -1 && h(n, "Cut path", (e) => o(e, s)) && n.setSelectedAnchors([]);
152
+ let o = O(a, r, i, h(e));
153
+ o !== -1 && v(n, "Cut path", (e) => s(e, o)) && n.setSelectedAnchors([]);
176
154
  }
177
155
  },
178
- enabled: (e) => p(e) ? !0 : l.SelectionRequired
179
- }, e);
180
- function O(e, t, r, i) {
156
+ enabled: (e) => g(e) ? !0 : p.SelectionRequired
157
+ }, n);
158
+ function O(e, n, r, i) {
181
159
  let a = -1, o = i * i, s = 0;
182
- for (let i of n(e).anchors) for (let e of i) {
183
- let n = e.x - t, i = e.y - r, c = n * n + i * i;
160
+ for (let i of t(e).anchors) for (let e of i) {
161
+ let t = e.x - n, i = e.y - r, c = t * t + i * i;
184
162
  c <= o && (a === -1 || c < o) && (o = c, a = s), s++;
185
163
  }
186
164
  return a;
187
165
  }
188
- var k = t({
166
+ var k = r({
189
167
  id: "marqueeAnchors",
190
168
  label: "Marquee-select anchors",
191
169
  defaultBinding: {
@@ -197,9 +175,9 @@ var k = t({
197
175
  invoker: {
198
176
  timing: "ongoing",
199
177
  start(e) {
200
- let t = p(e.deps);
178
+ let t = g(e.deps);
201
179
  if (!t) return {};
202
- let n = m(t);
180
+ let n = _(t);
203
181
  if (!n) return {};
204
182
  let r = e.modifiers.shift, i = new Set(t.selectedAnchors), a = {
205
183
  x: e.world.x,
@@ -225,11 +203,11 @@ var k = t({
225
203
  }
226
204
  },
227
205
  enabled: () => !0
228
- }, e);
229
- function A(e, t) {
230
- return r(n(e), t);
206
+ }, n);
207
+ function A(e, n) {
208
+ return i(t(e), n);
231
209
  }
232
210
  //#endregion
233
- export { D as cutPathAtAnchorAction, E as deleteAnchorsAction, k as marqueeAnchorsAction, C as nudgeAnchorsDownAction, w as nudgeAnchorsLeftAction, T as nudgeAnchorsRightAction, S as nudgeAnchorsUpAction, _ as selectAnchorAction };
211
+ export { D as cutPathAtAnchorAction, E as deleteAnchorsAction, k as marqueeAnchorsAction, C as nudgeAnchorsDownAction, w as nudgeAnchorsLeftAction, T as nudgeAnchorsRightAction, S as nudgeAnchorsUpAction, b as selectAnchorAction };
234
212
 
235
213
  //# sourceMappingURL=anchorEditing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"anchorEditing.js","names":["SMALL_STEP","BIG_STEP","ANCHOR_CLICK_SLOP","depOf","dep","deps","worldPathOf","p","commitAnchorEdit","worldPath","next","editAnchorSet","edit","label","requiresAnchorSelection","ActionDisabledReason","selectAnchorAction","inPlane","wx","params","wy","hit","nearestAnchorIndex","additive","selectionAfterAnchorPress","editingLayer","KEY_FOR","LABEL_FOR","delta","dir","step","makeNudgeAnchorsAction","selected","any","flat","translateAnchorBy","set","dx","dy","nudgeAnchorsUpAction","nudgeAnchorsDownAction","nudgeAnchorsLeftAction","nudgeAnchorsRightAction","deleteAnchorsAction","doomed","deleteAnchorsAt","cutPathAtAnchorAction","nearest","openSubpathAt","best","bestD2","slop","sub","pathToAnchors","path","a","d2","marqueeAnchorsAction","ctx","base","start","rectFrom","cx","cy","rect","moveCtx","hits","anchorsOfPathInRect","reason","anchorsInRect"],"sources":["../../../../src/interactions/actions/defaults/anchorEditing.ts"],"sourcesContent":["/**\n * Anchor-editing Actions beyond the drag itself — the keyboard and\n * marquee half of path editing.\n *\n * These were previously only reachable through the pen tool's private\n * edit mode, which no consumer could actually enter (its `getPathObj`\n * hook needed a `pose.kind` that no kit-created node has). Porting them\n * onto the Action layer puts them behind the same `editAnchors` dep,\n * `edits-anchors` capability, and undo path as `editAnchorsAction`.\n *\n * ## Why these don't collide with their node-level counterparts\n *\n * Each has a node-level twin bound to the same gesture: `deleteAction`\n * (Delete/Backspace), `nudge.*` (arrows), `areaSelectAction` (drag on\n * empty). Two mechanisms keep them apart, and both are needed:\n *\n * - **Capability eligibility**, when a mode registry is wired. The\n * node-level twins gate on `edits-page` / `transforms-selection` /\n * `creates-selection`, none of which `path-edit` mode allows, so the\n * dispatcher filters them out before matching.\n * - **`enabled` fall-through**, for the many consumers with no mode\n * registry at all. There the eligibility filter is skipped entirely,\n * so these actions report themselves disabled unless a path is\n * actually being edited with anchors selected, and the dispatcher\n * moves on to the node-level twin.\n *\n * Relying on eligibility alone would break every non-modal consumer;\n * relying on `enabled` alone would let a node-level action fire inside\n * path-edit mode. See `anchorEditing.test.ts`.\n */\n\nimport type { Action } from '@weasel-js/routing';\nimport { ActionDisabledReason } from '@weasel-js/routing';\nimport type { ActionDeps } from '@weasel-js/routing';\nimport type { ImmediateInvoker, InvocationCtx, OngoingHandle } from '@weasel-js/routing';\nimport type { EditAnchorsDep } from '../depSchema';\nimport {\n anchorsInRect,\n deleteAnchorsAt,\n editAnchorSet,\n openSubpathAt,\n translateAnchorBy,\n type AnchorSet,\n} from 'features/paths/anchorEdits';\nimport { pathToAnchors } from 'features/paths/anchors';\nimport type { PolygonPath } from '@weasel-js/geom';\nimport { selectionAfterAnchorPress } from './editAnchors';\nimport { inPlane, editingLayer } from '../planeInput';\n\n// ---------------------------------------------------------------------------\n// Shared plumbing\n// ---------------------------------------------------------------------------\n\n/** Nudge step sizes — mirror `nudge.ts` so anchors and nodes move alike. */\nconst SMALL_STEP = 1;\nconst BIG_STEP = 10;\n\n/** World-unit radius for \"this click landed on an anchor\". Matches the\n * affordance hit radius closely enough that anything the user can see as\n * an anchor marker is cuttable. */\nconst ANCHOR_CLICK_SLOP = 10;\n\nfunction depOf(deps: ActionDeps | undefined): EditAnchorsDep | null {\n const dep = deps?.editAnchors;\n if (!dep || !dep.editingId) return null;\n return dep;\n}\n\n/** The edited node's world-space polygon, or null when unavailable. */\nfunction worldPathOf(dep: EditAnchorsDep): PolygonPath | null {\n const p = dep.getEditablePath(dep.editingId) as PolygonPath | undefined;\n if (!p || p.kind !== 'polygon') return null;\n return p;\n}\n\n/**\n * Decode → mutate → encode → commit, in one history entry. Returns false\n * when the edit changed nothing, so callers can skip the commit rather\n * than push an empty undo step.\n */\nfunction commitAnchorEdit(\n dep: EditAnchorsDep,\n label: string,\n edit: (set: Parameters<Parameters<typeof editAnchorSet>[1]>[0]) => boolean | void,\n): boolean {\n const worldPath = worldPathOf(dep);\n if (!worldPath) return false;\n const next = editAnchorSet(worldPath, edit);\n if (!next) return false;\n dep.applyEdit(dep.editingId, next, label);\n return true;\n}\n\n/** Enabled only while a path is in edit mode with anchors selected. */\nfunction requiresAnchorSelection(deps?: ActionDeps): true | ActionDisabledReason {\n const dep = depOf(deps);\n if (!dep || dep.selectedAnchors.size === 0) return ActionDisabledReason.SelectionRequired;\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// selectAnchor\n// ---------------------------------------------------------------------------\n\n/**\n * Click an anchor to select it; Shift-click to toggle it in or out of the\n * selection. Clicking away from every anchor clears the anchor selection.\n *\n * ## Why this is a click action and not part of the drag\n *\n * The obvious home for \"pressing an anchor selects it\" is\n * `editAnchorsAction.start`, since that's the handler that receives the\n * anchor affordance. It can't live there: the dispatcher only dispatches\n * a drag binding once the pointer crosses the drag threshold, so a press\n * that never moves never reaches `start` at all. Verified in the running\n * app — down/up on an anchor logs a bare `click`, and down/move-3px/up\n * logs the same; only a real drag logs `pointerdown → editAnchors`.\n *\n * Clicks, in turn, carry no affordance (the dispatcher attaches those to\n * `pointerdown` only), so this resolves the anchor from the click's world\n * coords — the same thing `insertPathAnchorAction` does for segments.\n * `editAnchorsAction` still syncs the selection when a drag begins on an\n * unselected anchor, so drag-without-clicking-first behaves too.\n *\n * **Non-modal caveat:** `useSelectTool` binds `clearSelection` to\n * `{ click, target: 'empty', mods: {} }` at active scope, which outranks\n * this ambient binding. In path-edit mode that action is filtered out by\n * capability, so anchor clicks win. In a consumer with no mode registry\n * there's no filtering, so clicking an anchor that happens to sit over\n * empty canvas clears the node selection instead of selecting the anchor.\n * Anchors over the path body are unaffected.\n */\nexport const selectAnchorAction: Action & { requires: string[] } = inPlane({\n id: 'selectAnchor',\n label: 'Select anchor',\n defaultBinding: [\n { spec: { kind: 'click', mods: {} }, opts: { params: { additive: false } } },\n { spec: { kind: 'click', mods: { shift: true } }, opts: { params: { additive: true } } },\n ],\n eligible: { capability: 'edits-anchors' },\n requires: ['editAnchors'],\n invoker: {\n timing: 'immediate',\n run: (deps, params) => {\n const dep = depOf(deps);\n if (!dep) return;\n const wx = params?.worldX as number | undefined;\n const wy = params?.worldY as number | undefined;\n if (typeof wx !== 'number' || typeof wy !== 'number') return;\n const worldPath = worldPathOf(dep);\n if (!worldPath) return;\n\n const hit = nearestAnchorIndex(worldPath, wx, wy, ANCHOR_CLICK_SLOP);\n const additive = params?.additive === true;\n if (hit === -1) {\n // Missed every anchor. A bare click clears; a shift-click leaves\n // the selection alone so a stray miss mid-multi-select doesn't\n // undo the work.\n if (!additive) dep.setSelectedAnchors([]);\n return;\n }\n dep.setSelectedAnchors(selectionAfterAnchorPress(dep.selectedAnchors, hit, additive));\n },\n } as ImmediateInvoker,\n enabled: (deps) => (depOf(deps) ? true : ActionDisabledReason.SelectionRequired),\n}, editingLayer);\n\n// ---------------------------------------------------------------------------\n// nudgeAnchors\n// ---------------------------------------------------------------------------\n\ntype Direction = 'up' | 'down' | 'left' | 'right';\nconst KEY_FOR: Record<Direction, string> = {\n up: 'ArrowUp', down: 'ArrowDown', left: 'ArrowLeft', right: 'ArrowRight',\n};\nconst LABEL_FOR: Record<Direction, string> = {\n up: 'Up', down: 'Down', left: 'Left', right: 'Right',\n};\nfunction delta(dir: Direction, step: number): { dx: number; dy: number } {\n switch (dir) {\n case 'up': return { dx: 0, dy: -step };\n case 'down': return { dx: 0, dy: step };\n case 'left': return { dx: -step, dy: 0 };\n case 'right': return { dx: step, dy: 0 };\n }\n}\n\n/**\n * Move the selected anchors by one step. Shift multiplies the step, same\n * as node nudging.\n *\n * Every keystroke is its own history entry, matching node nudge — hold an\n * arrow down and undo walks back one step at a time rather than\n * collapsing the run.\n */\nfunction makeNudgeAnchorsAction(dir: Direction): Action & { requires: string[] } {\n return {\n id: `nudgeAnchors.${dir}`,\n label: `Nudge Anchors ${LABEL_FOR[dir]}`,\n group: 'nudgeAnchors',\n defaultBinding: [\n { spec: { kind: 'key', key: KEY_FOR[dir] }, opts: { params: { magnitude: 'small' } } },\n { spec: { kind: 'key', key: KEY_FOR[dir], mods: { shift: true } }, opts: { params: { magnitude: 'big' } } },\n ],\n eligible: { capability: 'edits-anchors' },\n requires: ['editAnchors'],\n invoker: {\n timing: 'immediate',\n run: (deps, params) => {\n const dep = depOf(deps);\n if (!dep || dep.selectedAnchors.size === 0) return;\n const step = params?.magnitude === 'big' ? BIG_STEP : SMALL_STEP;\n const { dx, dy } = delta(dir, step);\n const selected = [...dep.selectedAnchors];\n commitAnchorEdit(dep, 'Nudge anchor', (set) => {\n let any = false;\n for (const flat of selected) any = translateAnchorBy(set, flat, dx, dy) || any;\n return any;\n });\n },\n } as ImmediateInvoker,\n enabled: requiresAnchorSelection,\n };\n}\n\nexport const nudgeAnchorsUpAction = makeNudgeAnchorsAction('up');\nexport const nudgeAnchorsDownAction = makeNudgeAnchorsAction('down');\nexport const nudgeAnchorsLeftAction = makeNudgeAnchorsAction('left');\nexport const nudgeAnchorsRightAction = makeNudgeAnchorsAction('right');\n\n// ---------------------------------------------------------------------------\n// deleteAnchors\n// ---------------------------------------------------------------------------\n\n/**\n * Delete the selected anchors, refitting the surrounding curve through\n * each gap so the path keeps its shape.\n *\n * Deleting renumbers every anchor after the lowest deleted index, so the\n * selection is cleared rather than left pointing at whatever slid into\n * those slots.\n */\nexport const deleteAnchorsAction: Action & { requires: string[] } = {\n id: 'deleteAnchors',\n label: 'Delete Anchors',\n defaultBinding: {\n kind: 'key',\n key: ['Delete', 'Backspace'],\n phase: [{ channel: '*', phase: 'initial' }],\n },\n eligible: { capability: 'edits-anchors' },\n requires: ['editAnchors'],\n invoker: {\n timing: 'immediate',\n run: (deps) => {\n const dep = depOf(deps);\n if (!dep || dep.selectedAnchors.size === 0) return;\n const doomed = [...dep.selectedAnchors];\n const changed = commitAnchorEdit(dep, 'Delete anchor', (set) =>\n deleteAnchorsAt(set, doomed),\n );\n if (changed) dep.setSelectedAnchors([]);\n },\n } as ImmediateInvoker,\n enabled: requiresAnchorSelection,\n};\n\n// ---------------------------------------------------------------------------\n// cutPathAtAnchor (scissors)\n// ---------------------------------------------------------------------------\n\n/**\n * Open a closed subpath at the alt+shift-clicked anchor — the scissors\n * operation the pen tool used to own.\n *\n * Bound to Alt+Shift+click rather than plain Alt+click so it doesn't\n * collide with `insertPathAnchorAction`, which is already Alt+click on\n * the same path. Both are \"alt-click surgery on the edited path\"; the\n * extra modifier picks which one, instead of the two actions racing on an\n * identical binding where only one could ever win.\n *\n * No-ops on an open subpath: there's nothing to cut.\n */\nexport const cutPathAtAnchorAction: Action & { requires: string[] } = inPlane({\n id: 'cutPathAtAnchor',\n label: 'Cut path at anchor',\n defaultBinding: { kind: 'click', mods: { alt: true, shift: true } },\n eligible: { capability: 'edits-anchors' },\n requires: ['editAnchors'],\n invoker: {\n timing: 'immediate',\n run: (deps, params) => {\n const dep = depOf(deps);\n if (!dep) return;\n const wx = params?.worldX as number | undefined;\n const wy = params?.worldY as number | undefined;\n if (typeof wx !== 'number' || typeof wy !== 'number') return;\n const worldPath = worldPathOf(dep);\n if (!worldPath) return;\n\n // Find the anchor under the click. Clicks carry no affordance (only\n // pointerdown does), so this resolves the hit itself — the same\n // approach insertPathAnchorAction takes for segments.\n const nearest = nearestAnchorIndex(worldPath, wx, wy, ANCHOR_CLICK_SLOP);\n if (nearest === -1) return;\n\n const changed = commitAnchorEdit(dep, 'Cut path', (set) => openSubpathAt(set, nearest));\n // Cutting rotates the subpath's anchor order, so held indices are stale.\n if (changed) dep.setSelectedAnchors([]);\n },\n } as ImmediateInvoker,\n enabled: (deps) => (depOf(deps) ? true : ActionDisabledReason.SelectionRequired),\n}, editingLayer);\n\n/** Flat index of the anchor nearest `(wx, wy)` within `slop`, else -1. */\nfunction nearestAnchorIndex(\n path: PolygonPath,\n wx: number,\n wy: number,\n slop: number,\n): number {\n let best = -1;\n let bestD2 = slop * slop;\n let flat = 0;\n for (const sub of (pathToAnchors(path) as AnchorSet).anchors) {\n for (const a of sub) {\n const dx = a.x - wx;\n const dy = a.y - wy;\n const d2 = dx * dx + dy * dy;\n // `<=` so an exact-slop hit counts; ties go to the earlier anchor.\n if (d2 <= bestD2 && (best === -1 || d2 < bestD2)) {\n bestD2 = d2;\n best = flat;\n }\n flat++;\n }\n }\n return best;\n}\n\n// ---------------------------------------------------------------------------\n// marqueeAnchors\n// ---------------------------------------------------------------------------\n\n/**\n * Rubber-band select anchors by dragging on empty canvas while a path is\n * in edit mode.\n *\n * Shift adds to the existing anchor selection instead of replacing it.\n * The in-flight rect goes on the `editAnchors` dep so the path-editing\n * overlay can draw it — same \"ongoing action owns the state, chrome\n * draws it\" split the move ghosts and the node marquee use.\n */\nexport const marqueeAnchorsAction: Action & { requires: string[] } = inPlane({\n id: 'marqueeAnchors',\n label: 'Marquee-select anchors',\n defaultBinding: { kind: 'drag', target: 'empty' },\n eligible: { capability: 'edits-anchors' },\n requires: ['editAnchors'],\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx): OngoingHandle {\n const dep = depOf(ctx.deps);\n // Not editing → decline so the dispatcher falls through to the\n // node-level areaSelect. This is what keeps non-modal consumers\n // (no eligibility filtering) behaving normally.\n if (!dep) return {};\n const worldPath = worldPathOf(dep);\n if (!worldPath) return {};\n\n const additive = ctx.modifiers.shift;\n const base = new Set(dep.selectedAnchors);\n const start = { x: ctx.world.x, y: ctx.world.y };\n\n const rectFrom = (cx: number, cy: number) => ({\n x: Math.min(start.x, cx),\n y: Math.min(start.y, cy),\n width: Math.abs(cx - start.x),\n height: Math.abs(cy - start.y),\n });\n\n return {\n kind: 'marquee-anchors',\n onMove(moveCtx: InvocationCtx): void {\n const rect = rectFrom(moveCtx.world.x, moveCtx.world.y);\n dep.setMarquee(rect);\n // Live selection preview: the user sees anchors light up as the\n // band sweeps them, rather than only on release.\n const hits = anchorsOfPathInRect(worldPath, rect);\n dep.setSelectedAnchors(additive ? new Set([...base, ...hits]) : hits);\n },\n onEnd(_endCtx: InvocationCtx, reason: 'commit' | 'cancel'): void {\n dep.setMarquee(null);\n if (reason === 'cancel') dep.setSelectedAnchors(base);\n },\n };\n },\n },\n enabled: () => true,\n}, editingLayer);\n\n// ---------------------------------------------------------------------------\n// Local geometry helper\n// ---------------------------------------------------------------------------\n\n/** Flat indices of `path`'s anchors inside `rect`. */\nfunction anchorsOfPathInRect(\n path: PolygonPath,\n rect: { x: number; y: number; width: number; height: number },\n): number[] {\n return anchorsInRect(pathToAnchors(path) as AnchorSet, rect);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"anchorEditing.js","names":["ANCHOR_CLICK_PX","anchorClickSlop","scale","deps","extent","pxExtent","depOf","dep","worldPathOf","p","commitAnchorEdit","worldPath","next","editAnchorSet","edit","label","requiresAnchorSelection","ActionDisabledReason","selectAnchorAction","inPlane","wx","params","wy","hit","nearestAnchorIndex","additive","selectionAfterAnchorPress","editingLayer","makeNudgeAnchorsAction","dir","NUDGE_LABEL","NUDGE_KEY","nudgeDelta","selected","any","flat","translateAnchorBy","set","dx","dy","nudgeAnchorsUpAction","nudgeAnchorsDownAction","nudgeAnchorsLeftAction","nudgeAnchorsRightAction","deleteAnchorsAction","doomed","deleteAnchorsAt","cutPathAtAnchorAction","nearest","openSubpathAt","best","bestD2","slop","sub","pathToAnchors","path","a","d2","marqueeAnchorsAction","ctx","base","start","rectFrom","cx","cy","rect","moveCtx","hits","anchorsOfPathInRect","reason","anchorsInRect"],"sources":["../../../../src/interactions/actions/defaults/anchorEditing.ts"],"sourcesContent":["/**\n * Anchor-editing Actions beyond the drag itself — the keyboard and\n * marquee half of path editing.\n *\n * These were previously only reachable through the pen tool's private\n * edit mode, which no consumer could actually enter (its `getPathObj`\n * hook needed a `pose.kind` that no kit-created node has). Porting them\n * onto the Action layer puts them behind the same `editAnchors` dep,\n * `edits-anchors` capability, and undo path as `editAnchorsAction`.\n *\n * ## Why these don't collide with their node-level counterparts\n *\n * Each has a node-level twin bound to the same gesture: `deleteAction`\n * (Delete/Backspace), `nudge.*` (arrows), `areaSelectAction` (drag on\n * empty). Two mechanisms keep them apart, and both are needed:\n *\n * - **Capability eligibility**, when a mode registry is wired. The\n * node-level twins gate on `edits-page` / `transforms-selection` /\n * `creates-selection`, none of which `path-edit` mode allows, so the\n * dispatcher filters them out before matching.\n * - **`enabled` fall-through**, for the many consumers with no mode\n * registry at all. There the eligibility filter is skipped entirely,\n * so these actions report themselves disabled unless a path is\n * actually being edited with anchors selected, and the dispatcher\n * moves on to the node-level twin.\n *\n * Relying on eligibility alone would break every non-modal consumer;\n * relying on `enabled` alone would let a node-level action fire inside\n * path-edit mode. See `anchorEditing.test.ts`.\n */\n\nimport type { Action } from '@weasel-js/routing';\nimport { ActionDisabledReason } from '@weasel-js/routing';\nimport type { ActionDeps } from '@weasel-js/routing';\nimport type { ImmediateInvoker, InvocationCtx, OngoingHandle } from '@weasel-js/routing';\nimport type { EditAnchorsDep } from '../depSchema';\nimport {\n anchorsInRect,\n deleteAnchorsAt,\n editAnchorSet,\n openSubpathAt,\n translateAnchorBy,\n type AnchorSet,\n} from 'features/paths/anchorEdits';\nimport { pathToAnchors } from 'features/paths/anchors';\nimport { pxExtent } from 'core/viewport/pxExtent';\nimport type { PolygonPath } from '@weasel-js/geom';\nimport { selectionAfterAnchorPress } from './editAnchors';\nimport { inPlane, editingLayer } from '../planeInput';\nimport { NUDGE_KEY, NUDGE_LABEL, nudgeDelta, type NudgeDirection } from './nudgeSteps';\n\n// ---------------------------------------------------------------------------\n// Shared plumbing\n// ---------------------------------------------------------------------------\n\n/** Screen-pixel radius for \"this click landed on an anchor\". Matches the\n * affordance hit radius closely enough that anything the user can see as\n * an anchor marker is cuttable. */\nconst ANCHOR_CLICK_PX = 10;\n\n/** `ANCHOR_CLICK_PX` in world units through the view; with no view, one\n * pixel is one unit. */\nfunction anchorClickSlop(deps: ActionDeps | undefined): number {\n const scale = deps?.view?.get().scale;\n if (!scale) return ANCHOR_CLICK_PX;\n const extent = pxExtent(ANCHOR_CLICK_PX, scale);\n return Math.max(extent.x, extent.y);\n}\n\nfunction depOf(deps: ActionDeps | undefined): EditAnchorsDep | null {\n const dep = deps?.editAnchors;\n if (!dep || !dep.editingId) return null;\n return dep;\n}\n\n/** The edited node's world-space polygon, or null when unavailable. */\nfunction worldPathOf(dep: EditAnchorsDep): PolygonPath | null {\n const p = dep.getEditablePath(dep.editingId) as PolygonPath | undefined;\n if (!p || p.kind !== 'polygon') return null;\n return p;\n}\n\n/**\n * Decode → mutate → encode → commit, in one history entry. Returns false\n * when the edit changed nothing, so callers can skip the commit rather\n * than push an empty undo step.\n */\nfunction commitAnchorEdit(\n dep: EditAnchorsDep,\n label: string,\n edit: (set: Parameters<Parameters<typeof editAnchorSet>[1]>[0]) => boolean | void,\n): boolean {\n const worldPath = worldPathOf(dep);\n if (!worldPath) return false;\n const next = editAnchorSet(worldPath, edit);\n if (!next) return false;\n dep.applyEdit(dep.editingId, next, label);\n return true;\n}\n\n/** Enabled only while a path is in edit mode with anchors selected. */\nfunction requiresAnchorSelection(deps?: ActionDeps): true | ActionDisabledReason {\n const dep = depOf(deps);\n if (!dep || dep.selectedAnchors.size === 0) return ActionDisabledReason.SelectionRequired;\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// selectAnchor\n// ---------------------------------------------------------------------------\n\n/**\n * Click an anchor to select it; Shift-click to toggle it in or out of the\n * selection. Clicking away from every anchor clears the anchor selection.\n *\n * ## Why this is a click action and not part of the drag\n *\n * The obvious home for \"pressing an anchor selects it\" is\n * `editAnchorsAction.start`, since that's the handler that receives the\n * anchor affordance. It can't live there: the dispatcher only dispatches\n * a drag binding once the pointer crosses the drag threshold, so a press\n * that never moves never reaches `start` at all. Verified in the running\n * app — down/up on an anchor logs a bare `click`, and down/move-3px/up\n * logs the same; only a real drag logs `pointerdown → editAnchors`.\n *\n * Clicks, in turn, carry no affordance (the dispatcher attaches those to\n * `pointerdown` only), so this resolves the anchor from the click's world\n * coords — the same thing `insertPathAnchorAction` does for segments.\n * `editAnchorsAction` still syncs the selection when a drag begins on an\n * unselected anchor, so drag-without-clicking-first behaves too.\n *\n * **Non-modal caveat:** `useSelectTool` binds `clearSelection` to\n * `{ click, target: 'empty', mods: {} }` at active scope, which outranks\n * this ambient binding. In path-edit mode that action is filtered out by\n * capability, so anchor clicks win. In a consumer with no mode registry\n * there's no filtering, so clicking an anchor that happens to sit over\n * empty canvas clears the node selection instead of selecting the anchor.\n * Anchors over the path body are unaffected.\n */\nexport const selectAnchorAction: Action & { requires: string[] } = inPlane({\n id: 'selectAnchor',\n label: 'Select anchor',\n defaultBinding: [\n { spec: { kind: 'click', mods: {} }, opts: { params: { additive: false } } },\n { spec: { kind: 'click', mods: { shift: true } }, opts: { params: { additive: true } } },\n ],\n eligible: { capability: 'edits-anchors' },\n requires: ['editAnchors', 'view'],\n invoker: {\n timing: 'immediate',\n run: (deps, params) => {\n const dep = depOf(deps);\n if (!dep) return;\n const wx = params?.worldX as number | undefined;\n const wy = params?.worldY as number | undefined;\n if (typeof wx !== 'number' || typeof wy !== 'number') return;\n const worldPath = worldPathOf(dep);\n if (!worldPath) return;\n\n const hit = nearestAnchorIndex(worldPath, wx, wy, anchorClickSlop(deps));\n const additive = params?.additive === true;\n if (hit === -1) {\n // Missed every anchor. A bare click clears; a shift-click leaves\n // the selection alone so a stray miss mid-multi-select doesn't\n // undo the work.\n if (!additive) dep.setSelectedAnchors([]);\n return;\n }\n dep.setSelectedAnchors(selectionAfterAnchorPress(dep.selectedAnchors, hit, additive));\n },\n } as ImmediateInvoker,\n enabled: (deps) => (depOf(deps) ? true : ActionDisabledReason.SelectionRequired),\n}, editingLayer);\n\n// ---------------------------------------------------------------------------\n// nudgeAnchors\n// ---------------------------------------------------------------------------\n\n/**\n * Move the selected anchors by one step. Shift multiplies the step, same\n * as node nudging.\n *\n * Every keystroke is its own history entry, matching node nudge — hold an\n * arrow down and undo walks back one step at a time rather than\n * collapsing the run.\n */\nfunction makeNudgeAnchorsAction(dir: NudgeDirection): Action & { requires: string[] } {\n return {\n id: `nudgeAnchors.${dir}`,\n label: `Nudge Anchors ${NUDGE_LABEL[dir]}`,\n group: 'nudgeAnchors',\n defaultBinding: [\n { spec: { kind: 'key', key: NUDGE_KEY[dir] }, opts: { params: { magnitude: 'small' } } },\n { spec: { kind: 'key', key: NUDGE_KEY[dir], mods: { shift: true } }, opts: { params: { magnitude: 'big' } } },\n ],\n eligible: { capability: 'edits-anchors' },\n requires: ['editAnchors'],\n invoker: {\n timing: 'immediate',\n run: (deps, params) => {\n const dep = depOf(deps);\n if (!dep || dep.selectedAnchors.size === 0) return;\n const { dx, dy } = nudgeDelta(dir, params?.magnitude);\n const selected = [...dep.selectedAnchors];\n commitAnchorEdit(dep, 'Nudge anchor', (set) => {\n let any = false;\n for (const flat of selected) any = translateAnchorBy(set, flat, dx, dy) || any;\n return any;\n });\n },\n } as ImmediateInvoker,\n enabled: requiresAnchorSelection,\n };\n}\n\nexport const nudgeAnchorsUpAction = makeNudgeAnchorsAction('up');\nexport const nudgeAnchorsDownAction = makeNudgeAnchorsAction('down');\nexport const nudgeAnchorsLeftAction = makeNudgeAnchorsAction('left');\nexport const nudgeAnchorsRightAction = makeNudgeAnchorsAction('right');\n\n// ---------------------------------------------------------------------------\n// deleteAnchors\n// ---------------------------------------------------------------------------\n\n/**\n * Delete the selected anchors, refitting the surrounding curve through\n * each gap so the path keeps its shape.\n *\n * Deleting renumbers every anchor after the lowest deleted index, so the\n * selection is cleared rather than left pointing at whatever slid into\n * those slots.\n */\nexport const deleteAnchorsAction: Action & { requires: string[] } = {\n id: 'deleteAnchors',\n label: 'Delete Anchors',\n defaultBinding: {\n kind: 'key',\n key: ['Delete', 'Backspace'],\n phase: [{ channel: '*', phase: 'initial' }],\n },\n eligible: { capability: 'edits-anchors' },\n requires: ['editAnchors'],\n invoker: {\n timing: 'immediate',\n run: (deps) => {\n const dep = depOf(deps);\n if (!dep || dep.selectedAnchors.size === 0) return;\n const doomed = [...dep.selectedAnchors];\n const changed = commitAnchorEdit(dep, 'Delete anchor', (set) =>\n deleteAnchorsAt(set, doomed),\n );\n if (changed) dep.setSelectedAnchors([]);\n },\n } as ImmediateInvoker,\n enabled: requiresAnchorSelection,\n};\n\n// ---------------------------------------------------------------------------\n// cutPathAtAnchor (scissors)\n// ---------------------------------------------------------------------------\n\n/**\n * Open a closed subpath at the alt+shift-clicked anchor — the scissors\n * operation the pen tool used to own.\n *\n * Bound to Alt+Shift+click rather than plain Alt+click so it doesn't\n * collide with `insertPathAnchorAction`, which is already Alt+click on\n * the same path. Both are \"alt-click surgery on the edited path\"; the\n * extra modifier picks which one, instead of the two actions racing on an\n * identical binding where only one could ever win.\n *\n * No-ops on an open subpath: there's nothing to cut.\n */\nexport const cutPathAtAnchorAction: Action & { requires: string[] } = inPlane({\n id: 'cutPathAtAnchor',\n label: 'Cut path at anchor',\n defaultBinding: { kind: 'click', mods: { alt: true, shift: true } },\n eligible: { capability: 'edits-anchors' },\n requires: ['editAnchors', 'view'],\n invoker: {\n timing: 'immediate',\n run: (deps, params) => {\n const dep = depOf(deps);\n if (!dep) return;\n const wx = params?.worldX as number | undefined;\n const wy = params?.worldY as number | undefined;\n if (typeof wx !== 'number' || typeof wy !== 'number') return;\n const worldPath = worldPathOf(dep);\n if (!worldPath) return;\n\n // Find the anchor under the click. Clicks carry no affordance (only\n // pointerdown does), so this resolves the hit itself — the same\n // approach insertPathAnchorAction takes for segments.\n const nearest = nearestAnchorIndex(worldPath, wx, wy, anchorClickSlop(deps));\n if (nearest === -1) return;\n\n const changed = commitAnchorEdit(dep, 'Cut path', (set) => openSubpathAt(set, nearest));\n // Cutting rotates the subpath's anchor order, so held indices are stale.\n if (changed) dep.setSelectedAnchors([]);\n },\n } as ImmediateInvoker,\n enabled: (deps) => (depOf(deps) ? true : ActionDisabledReason.SelectionRequired),\n}, editingLayer);\n\n/** Flat index of the anchor nearest `(wx, wy)` within `slop`, else -1. */\nfunction nearestAnchorIndex(\n path: PolygonPath,\n wx: number,\n wy: number,\n slop: number,\n): number {\n let best = -1;\n let bestD2 = slop * slop;\n let flat = 0;\n for (const sub of (pathToAnchors(path) as AnchorSet).anchors) {\n for (const a of sub) {\n const dx = a.x - wx;\n const dy = a.y - wy;\n const d2 = dx * dx + dy * dy;\n // `<=` so an exact-slop hit counts; ties go to the earlier anchor.\n if (d2 <= bestD2 && (best === -1 || d2 < bestD2)) {\n bestD2 = d2;\n best = flat;\n }\n flat++;\n }\n }\n return best;\n}\n\n// ---------------------------------------------------------------------------\n// marqueeAnchors\n// ---------------------------------------------------------------------------\n\n/**\n * Rubber-band select anchors by dragging on empty canvas while a path is\n * in edit mode.\n *\n * Shift adds to the existing anchor selection instead of replacing it.\n * The in-flight rect goes on the `editAnchors` dep so the path-editing\n * overlay can draw it — same \"ongoing action owns the state, chrome\n * draws it\" split the move ghosts and the node marquee use.\n */\nexport const marqueeAnchorsAction: Action & { requires: string[] } = inPlane({\n id: 'marqueeAnchors',\n label: 'Marquee-select anchors',\n defaultBinding: { kind: 'drag', target: 'empty' },\n eligible: { capability: 'edits-anchors' },\n requires: ['editAnchors'],\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx): OngoingHandle {\n const dep = depOf(ctx.deps);\n // Not editing → decline so the dispatcher falls through to the\n // node-level areaSelect. This is what keeps non-modal consumers\n // (no eligibility filtering) behaving normally.\n if (!dep) return {};\n const worldPath = worldPathOf(dep);\n if (!worldPath) return {};\n\n const additive = ctx.modifiers.shift;\n const base = new Set(dep.selectedAnchors);\n const start = { x: ctx.world.x, y: ctx.world.y };\n\n const rectFrom = (cx: number, cy: number) => ({\n x: Math.min(start.x, cx),\n y: Math.min(start.y, cy),\n width: Math.abs(cx - start.x),\n height: Math.abs(cy - start.y),\n });\n\n return {\n kind: 'marquee-anchors',\n onMove(moveCtx: InvocationCtx): void {\n const rect = rectFrom(moveCtx.world.x, moveCtx.world.y);\n dep.setMarquee(rect);\n // Live selection preview: the user sees anchors light up as the\n // band sweeps them, rather than only on release.\n const hits = anchorsOfPathInRect(worldPath, rect);\n dep.setSelectedAnchors(additive ? new Set([...base, ...hits]) : hits);\n },\n onEnd(_endCtx: InvocationCtx, reason: 'commit' | 'cancel'): void {\n dep.setMarquee(null);\n if (reason === 'cancel') dep.setSelectedAnchors(base);\n },\n };\n },\n },\n enabled: () => true,\n}, editingLayer);\n\n// ---------------------------------------------------------------------------\n// Local geometry helper\n// ---------------------------------------------------------------------------\n\n/** Flat indices of `path`'s anchors inside `rect`. */\nfunction anchorsOfPathInRect(\n path: PolygonPath,\n rect: { x: number; y: number; width: number; height: number },\n): number[] {\n return anchorsInRect(pathToAnchors(path) as AnchorSet, rect);\n}\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
+ import { selectionFromHits as e } from "../hitSelection.js";
1
2
  //#region src/interactions/actions/defaults/areaSelect.ts
2
- var e = {
3
+ var t = {
3
4
  id: "areaSelect",
4
5
  label: "Area Select",
5
6
  activeCursor: "crosshair",
@@ -11,58 +12,54 @@ var e = {
11
12
  ],
12
13
  invoker: {
13
14
  timing: "ongoing",
14
- start(e, t) {
15
- let n = e.deps.areaSelect;
16
- if (!n) return {};
17
- let r = e.drag?.affordance?.kind;
18
- if (typeof r == "string" && /^(anchor|controlIn|controlOut):/.test(r) || e.deps.editAnchors?.editingId) return {};
19
- let i = {
20
- dep: n,
21
- view: e.deps.view,
22
- startX: e.world.x,
23
- startY: e.world.y,
24
- shiftHeld: e.modifiers.shift,
25
- currentX: e.world.x,
26
- currentY: e.world.y,
15
+ start(t, n) {
16
+ let r = t.deps.areaSelect;
17
+ if (!r) return {};
18
+ let i = t.drag?.affordance?.kind;
19
+ if (typeof i == "string" && /^(anchor|controlIn|controlOut):/.test(i) || t.deps.editAnchors?.editingId) return {};
20
+ let a = {
21
+ dep: r,
22
+ view: t.deps.view,
23
+ startX: t.world.x,
24
+ startY: t.world.y,
25
+ shiftHeld: t.modifiers.shift,
26
+ currentX: t.world.x,
27
+ currentY: t.world.y,
27
28
  open: !0
28
29
  };
29
30
  return {
30
31
  kind: "marquee",
31
32
  onMove(e) {
32
- i.currentX = e.world.x, i.currentY = e.world.y;
33
+ a.currentX = e.world.x, a.currentY = e.world.y;
33
34
  },
34
35
  overlay() {
35
- return i.open ? {
36
+ return a.open ? {
36
37
  kind: "marquee",
37
38
  start: {
38
- x: i.startX,
39
- y: i.startY
39
+ x: a.startX,
40
+ y: a.startY
40
41
  },
41
42
  current: {
42
- x: i.currentX,
43
- y: i.currentY
43
+ x: a.currentX,
44
+ y: a.currentY
44
45
  },
45
- shiftHeld: i.shiftHeld
46
+ shiftHeld: a.shiftHeld
46
47
  } : null;
47
48
  },
48
- onEnd(e, t) {
49
- if (i.open = !1, t === "cancel") return;
50
- let { dep: n, view: r, startX: a, startY: o, currentX: s, currentY: c, shiftHeld: l } = i, u = Math.min(a, s), d = Math.min(o, c), f = Math.abs(s - a), p = Math.abs(c - o);
51
- if (f === 0 || p === 0) {
52
- l || n.setSelection([]);
49
+ onEnd(t, n) {
50
+ if (a.open = !1, n === "cancel") return;
51
+ let { dep: r, view: i, startX: o, startY: s, currentX: c, currentY: l, shiftHeld: u } = a, d = Math.min(o, c), f = Math.min(s, l), p = Math.abs(c - o), m = Math.abs(l - s);
52
+ if (p === 0 || m === 0) {
53
+ u || r.setSelection([]);
53
54
  return;
54
55
  }
55
- let m = n.hitTestArea({
56
- x: u,
57
- y: d,
58
- width: f,
59
- height: p
60
- }, r);
61
- if (l) {
62
- let e = [...n.getSelection()];
63
- for (let t of m) e.includes(t) || e.push(t);
64
- n.setSelection(e);
65
- } else n.setSelection(m);
56
+ let h = r.hitTestArea({
57
+ x: d,
58
+ y: f,
59
+ width: p,
60
+ height: m
61
+ }, i);
62
+ r.setSelection(e(r.getSelection(), h, u));
66
63
  }
67
64
  };
68
65
  }
@@ -70,6 +67,6 @@ var e = {
70
67
  enabled: () => !0
71
68
  };
72
69
  //#endregion
73
- export { e as areaSelectAction };
70
+ export { t as areaSelectAction };
74
71
 
75
72
  //# sourceMappingURL=areaSelect.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"areaSelect.js","names":["areaSelectAction","dep","ctx","affordanceKind","scratch","moveCtx","reason","d","x","startX","currentX","y","startY","currentY","width","height","shiftHeld","hits","view","merged","id"],"sources":["../../../../src/interactions/actions/defaults/areaSelect.ts"],"sourcesContent":["/**\n * `areaSelectAction` — ongoing Action descriptor for marquee area selection.\n *\n * ## Status: REAL\n *\n * Implements the marquee selection logic from `useAreaSelect`:\n * - `start`: records the drag start world point and shift state.\n * - `onMove`: tracks the current drag corner (no scene writes).\n * - `onEnd('commit')`: derives the marquee bounds from start + current;\n * calls `deps.areaSelect.hitTestArea(bounds)` to find overlapping nodes;\n * calls `deps.areaSelect.setSelection(ids)` to update selection (extending\n * with shift held). This is a **transient** write (no undo entry) matching\n * `useAreaSelect`'s `defaultTransient: true` convention from\n * `selectFromMarquee`.\n * - `onEnd('cancel')`: no-op.\n *\n * ## Dependencies\n *\n * Requires `areaSelect` dep from DepSchema:\n * `{ hitTestArea(bounds): NodeId[], getSelection(): NodeId[], setSelection(ids): void }`\n *\n * `<SceneCanvas>` / `<StandardActionsRegistrar>` should source this dep by\n * iterating scene nodes and checking AABB overlap. The dep is intentionally\n * simple — consumers override it for custom hit-testing (contain-mode,\n * layer-aware filtering, etc.).\n *\n * ## What this does NOT wire (vs `useAreaSelect`)\n *\n * - Live marquee overlay rendering — deferred to Phase 7 overlay surface.\n * - `AreaSelectBehavior` pipeline — omitted; only the default replace/extend\n * semantics are implemented.\n * - Debug sink recording — not available in the descriptor model.\n */\n\nimport type { Action } from '@weasel-js/routing';\nimport type { InvocationCtx, OngoingHandle, OngoingOverlay } from '@weasel-js/routing';\nimport type { NodeId } from 'core/scene/types';\nimport type { AreaSelectDep, ViewApi } from '../depSchema';\n\n// ---------------------------------------------------------------------------\n// Internal scratch\n// ---------------------------------------------------------------------------\n\ninterface AreaSelectScratch {\n dep: AreaSelectDep;\n /** The view the marquee started in: what it does not paint is not taken. */\n view: ViewApi | undefined;\n startX: number;\n startY: number;\n shiftHeld: boolean;\n currentX: number;\n currentY: number;\n /** Cleared once `onEnd` runs so subsequent `overlay()` calls report no\n * in-flight marquee (the dispatcher releases the handle after onEnd so\n * this is belt-and-suspenders for any consumer that retains a handle). */\n open: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Descriptor\n// ---------------------------------------------------------------------------\n\n/**\n * @experimental\n * Static descriptor for the `areaSelect` Action.\n *\n * Requires dep-schema entry: `areaSelect`.\n *\n * The invoker implements replace/extend marquee selection via the `areaSelect`\n * dep. Hit-testing and selection writes are delegated to the dep; the invoker\n * contains only the bounds-derivation and shift-extend logic.\n *\n * @see useAreaSelect — the React hook this descriptor mirrors for the default case.\n */\nexport const areaSelectAction: Action & { requires: string[] } = {\n id: 'areaSelect',\n label: 'Area Select',\n // Crosshair while the marquee is being pulled — the select tool used to\n // read this off its own gesture scratch.\n activeCursor: 'crosshair',\n eligible: { capability: 'creates-selection' },\n requires: ['areaSelect', 'editAnchors', 'view'],\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx, _opts): OngoingHandle {\n const dep = ctx.deps.areaSelect;\n if (!dep) return {};\n\n // Decline on anchor / control-handle affordances: the select tool's\n // active-scope binding for this action is `{ kind: 'drag', target: 'empty' }`,\n // which matches whenever `bodyTarget === 'empty'` — including drags\n // where an anchor was hit on top of empty body. Without this opt-out\n // areaSelect would beat editAnchors via scope priority. The\n // dispatcher's empty-handle fallthrough then forwards the gesture.\n const affordanceKind = ctx.drag?.affordance?.kind;\n if (\n typeof affordanceKind === 'string'\n && /^(anchor|controlIn|controlOut):/.test(affordanceKind)\n ) {\n return {};\n }\n\n // Decline for the same reason while a path is in anchor-edit mode:\n // an empty-canvas drag there means \"rubber-band some anchors\", which\n // is `marqueeAnchorsAction`'s job. Capability eligibility already\n // filters this action out in path-edit mode, but only for consumers\n // that wired a mode registry — without one the dispatcher skips\n // eligibility entirely and areaSelect's active scope would beat\n // marqueeAnchors' ambient scope. Declining here covers both.\n const editAnchors = ctx.deps.editAnchors;\n if (editAnchors?.editingId) return {};\n\n // Drag start is the world point at the moment start() is called.\n const scratch: AreaSelectScratch = {\n dep,\n view: ctx.deps.view,\n startX: ctx.world.x,\n startY: ctx.world.y,\n shiftHeld: ctx.modifiers.shift,\n currentX: ctx.world.x,\n currentY: ctx.world.y,\n open: true,\n };\n\n return {\n kind: 'marquee',\n onMove(moveCtx: InvocationCtx): void {\n scratch.currentX = moveCtx.world.x;\n scratch.currentY = moveCtx.world.y;\n },\n overlay(): OngoingOverlay | null {\n if (!scratch.open) return null;\n return {\n kind: 'marquee',\n start: { x: scratch.startX, y: scratch.startY },\n current: { x: scratch.currentX, y: scratch.currentY },\n shiftHeld: scratch.shiftHeld,\n };\n },\n onEnd(_endCtx: InvocationCtx, reason: 'commit' | 'cancel'): void {\n scratch.open = false;\n if (reason === 'cancel') return;\n\n const { dep: d, view, startX, startY, currentX, currentY, shiftHeld } = scratch;\n\n // Derive the marquee AABB from the two world-space corners.\n const x = Math.min(startX, currentX);\n const y = Math.min(startY, currentY);\n const width = Math.abs(currentX - startX);\n const height = Math.abs(currentY - startY);\n\n // Zero-size marquee (click without drag) → clear selection (or no-op with shift).\n if (width === 0 || height === 0) {\n if (!shiftHeld) d.setSelection([]);\n return;\n }\n\n const hits = d.hitTestArea({ x, y, width, height }, view) as NodeId[];\n\n if (shiftHeld) {\n const current = d.getSelection();\n // Extend: merge current + hits (deduplicated).\n const merged = [...current];\n for (const id of hits) {\n if (!merged.includes(id)) merged.push(id);\n }\n d.setSelection(merged);\n } else {\n d.setSelection(hits);\n }\n },\n };\n },\n },\n /**\n * Always enabled at the dispatcher level. The dispatcher's\n * specificity-fallthrough loop treats anything `!== true` as \"skip this\n * match\" — returning a disabled reason would let lower-specificity\n * ambient bindings win. The invoker has no preconditions to guard.\n */\n enabled: () => true,\n};\n"],"mappings":";AA0EA,IAAaA,IAAoD;CAC/D,IAAI;CACJ,OAAO;CAGP,cAAc;CACd,UAAU,EAAE,YAAY,oBAAoB;CAC5C,UAAU;EAAC;EAAc;EAAe;CAAM;CAC9C,SAAS;EACP,QAAQ;EACR,MAAM,GAAoB,GAAsB;GAC9C,IAAMC,IAAMC,EAAI,KAAK;GACrB,IAAI,CAACD,GAAK,OAAO,CAAC;GAQlB,IAAME,IAAiBD,EAAI,MAAM,YAAY;GAgB7C,IAdE,OAAOC,KAAmB,YACvB,kCAAkC,KAAKA,CAAc,KAYtCD,EAAI,KAAK,aACZ,WAAW,OAAO,CAAC;GAGpC,IAAME,IAA6B;IACjC;IACA,MAAMF,EAAI,KAAK;IACf,QAAQA,EAAI,MAAM;IAClB,QAAQA,EAAI,MAAM;IAClB,WAAWA,EAAI,UAAU;IACzB,UAAUA,EAAI,MAAM;IACpB,UAAUA,EAAI,MAAM;IACpB,MAAM;GACR;GAEA,OAAO;IACL,MAAM;IACN,OAAO,GAA8B;KAEnC,AADA,EAAQ,WAAWG,EAAQ,MAAM,GACjC,EAAQ,WAAWA,EAAQ,MAAM;IACnC;IACA,UAAiC;KAE/B,OADKD,EAAQ,OACN;MACL,MAAM;MACN,OAAO;OAAE,GAAGA,EAAQ;OAAQ,GAAGA,EAAQ;MAAO;MAC9C,SAAS;OAAE,GAAGA,EAAQ;OAAU,GAAGA,EAAQ;MAAS;MACpD,WAAWA,EAAQ;KACrB,IAN0B;IAO5B;IACA,MAAM,GAAwB,GAAmC;KAE/D,IADA,EAAQ,OAAO,IACXE,MAAW,UAAU;KAEzB,IAAM,EAAE,KAAKC,GAAG,SAAM,WAAQ,WAAQ,aAAU,aAAU,iBAAcH,GAGlEI,IAAI,KAAK,IAAIC,GAAQC,CAAQ,GAC7BC,IAAI,KAAK,IAAIC,GAAQC,CAAQ,GAC7BC,IAAQ,KAAK,IAAIJ,IAAWD,CAAM,GAClCM,IAAS,KAAK,IAAIF,IAAWD,CAAM;KAGzC,IAAIE,MAAU,KAAKC,MAAW,GAAG;MAC/B,AAAKC,KAAWT,EAAE,aAAa,CAAC,CAAC;MACjC;KACF;KAEA,IAAMU,IAAOV,EAAE,YAAY;MAAE;MAAG;MAAG;MAAO;KAAO,GAAGW,CAAI;KAExD,IAAIF,GAAW;MAGb,IAAMG,IAAS,CAAC,GAFAZ,EAAE,aAEC,CAAO;MAC1B,KAAK,IAAMa,KAAMH,GACf,AAAKE,EAAO,SAASC,CAAE,KAAGD,EAAO,KAAKC,CAAE;MAE1C,EAAE,aAAaD,CAAM;KACvB,OACE,EAAE,aAAaF,CAAI;IAEvB;GACF;EACF;CACF;CAOA,eAAe;AACjB"}
1
+ {"version":3,"file":"areaSelect.js","names":["areaSelectAction","dep","ctx","affordanceKind","scratch","moveCtx","reason","d","x","startX","currentX","y","startY","currentY","width","height","shiftHeld","hits","view","selectionFromHits"],"sources":["../../../../src/interactions/actions/defaults/areaSelect.ts"],"sourcesContent":["/**\n * `areaSelectAction` — ongoing Action descriptor for marquee area selection.\n *\n * The marquee selection:\n * - `start`: records the drag start world point and shift state.\n * - `onMove`: tracks the current drag corner (no scene writes).\n * - `onEnd('commit')`: derives the marquee bounds from start + current;\n * calls `deps.areaSelect.hitTestArea(bounds)` to find overlapping nodes;\n * calls `deps.areaSelect.setSelection(ids)` to update selection (extending\n * with shift held). This is a **transient** write (no undo entry).\n * - `onEnd('cancel')`: no-op.\n *\n * ## Dependencies\n *\n * Requires `areaSelect` dep from DepSchema:\n * `{ hitTestArea(bounds): NodeId[], getSelection(): NodeId[], setSelection(ids): void }`\n *\n * `<SceneCanvas>` / `<StandardActionsRegistrar>` should source this dep by\n * iterating scene nodes and checking AABB overlap. The dep is intentionally\n * simple — consumers override it for custom hit-testing (contain-mode,\n * layer-aware filtering, etc.).\n *\n * ## What this does not wire\n *\n * - `AreaSelectBehavior` pipeline — omitted; only the default replace/extend\n * semantics are implemented.\n * - Debug sink recording — not available in the descriptor model.\n */\n\nimport type { Action } from '@weasel-js/routing';\nimport type { InvocationCtx, OngoingHandle, OngoingOverlay } from '@weasel-js/routing';\nimport type { NodeId } from 'core/scene/types';\nimport type { AreaSelectDep, ViewApi } from '../depSchema';\nimport { selectionFromHits } from '../hitSelection';\n\n// ---------------------------------------------------------------------------\n// Internal scratch\n// ---------------------------------------------------------------------------\n\ninterface AreaSelectScratch {\n dep: AreaSelectDep;\n /** The view the marquee started in: what it does not paint is not taken. */\n view: ViewApi | undefined;\n startX: number;\n startY: number;\n shiftHeld: boolean;\n currentX: number;\n currentY: number;\n /** Cleared once `onEnd` runs so subsequent `overlay()` calls report no\n * in-flight marquee (the dispatcher releases the handle after onEnd so\n * this is belt-and-suspenders for any consumer that retains a handle). */\n open: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Descriptor\n// ---------------------------------------------------------------------------\n\n/**\n * @experimental\n * Static descriptor for the `areaSelect` Action.\n *\n * Requires dep-schema entry: `areaSelect`.\n *\n * The invoker implements replace/extend marquee selection via the `areaSelect`\n * dep. Hit-testing and selection writes are delegated to the dep; the invoker\n * contains only the bounds-derivation and shift-extend logic.\n */\nexport const areaSelectAction: Action & { requires: string[] } = {\n id: 'areaSelect',\n label: 'Area Select',\n // Crosshair while the marquee is being pulled — the select tool used to\n // read this off its own gesture scratch.\n activeCursor: 'crosshair',\n eligible: { capability: 'creates-selection' },\n requires: ['areaSelect', 'editAnchors', 'view'],\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx, _opts): OngoingHandle {\n const dep = ctx.deps.areaSelect;\n if (!dep) return {};\n\n // Decline on anchor / control-handle affordances: the select tool's\n // active-scope binding for this action is `{ kind: 'drag', target: 'empty' }`,\n // which matches whenever `bodyTarget === 'empty'` — including drags\n // where an anchor was hit on top of empty body. Without this opt-out\n // areaSelect would beat editAnchors via scope priority. The\n // dispatcher's empty-handle fallthrough then forwards the gesture.\n const affordanceKind = ctx.drag?.affordance?.kind;\n if (\n typeof affordanceKind === 'string'\n && /^(anchor|controlIn|controlOut):/.test(affordanceKind)\n ) {\n return {};\n }\n\n // Decline for the same reason while a path is in anchor-edit mode:\n // an empty-canvas drag there means \"rubber-band some anchors\", which\n // is `marqueeAnchorsAction`'s job. Capability eligibility already\n // filters this action out in path-edit mode, but only for consumers\n // that wired a mode registry — without one the dispatcher skips\n // eligibility entirely and areaSelect's active scope would beat\n // marqueeAnchors' ambient scope. Declining here covers both.\n const editAnchors = ctx.deps.editAnchors;\n if (editAnchors?.editingId) return {};\n\n // Drag start is the world point at the moment start() is called.\n const scratch: AreaSelectScratch = {\n dep,\n view: ctx.deps.view,\n startX: ctx.world.x,\n startY: ctx.world.y,\n shiftHeld: ctx.modifiers.shift,\n currentX: ctx.world.x,\n currentY: ctx.world.y,\n open: true,\n };\n\n return {\n kind: 'marquee',\n onMove(moveCtx: InvocationCtx): void {\n scratch.currentX = moveCtx.world.x;\n scratch.currentY = moveCtx.world.y;\n },\n overlay(): OngoingOverlay | null {\n if (!scratch.open) return null;\n return {\n kind: 'marquee',\n start: { x: scratch.startX, y: scratch.startY },\n current: { x: scratch.currentX, y: scratch.currentY },\n shiftHeld: scratch.shiftHeld,\n };\n },\n onEnd(_endCtx: InvocationCtx, reason: 'commit' | 'cancel'): void {\n scratch.open = false;\n if (reason === 'cancel') return;\n\n const { dep: d, view, startX, startY, currentX, currentY, shiftHeld } = scratch;\n\n // Derive the marquee AABB from the two world-space corners.\n const x = Math.min(startX, currentX);\n const y = Math.min(startY, currentY);\n const width = Math.abs(currentX - startX);\n const height = Math.abs(currentY - startY);\n\n // Zero-size marquee (click without drag) → clear selection (or no-op with shift).\n if (width === 0 || height === 0) {\n if (!shiftHeld) d.setSelection([]);\n return;\n }\n\n const hits = d.hitTestArea({ x, y, width, height }, view) as NodeId[];\n\n d.setSelection(selectionFromHits(d.getSelection(), hits, shiftHeld));\n },\n };\n },\n },\n /**\n * Always enabled at the dispatcher level. The dispatcher's\n * specificity-fallthrough loop treats anything `!== true` as \"skip this\n * match\" — returning a disabled reason would let lower-specificity\n * ambient bindings win. The invoker has no preconditions to guard.\n */\n enabled: () => true,\n};\n"],"mappings":";;AAoEA,IAAaA,IAAoD;CAC/D,IAAI;CACJ,OAAO;CAGP,cAAc;CACd,UAAU,EAAE,YAAY,oBAAoB;CAC5C,UAAU;EAAC;EAAc;EAAe;CAAM;CAC9C,SAAS;EACP,QAAQ;EACR,MAAM,GAAoB,GAAsB;GAC9C,IAAMC,IAAMC,EAAI,KAAK;GACrB,IAAI,CAACD,GAAK,OAAO,CAAC;GAQlB,IAAME,IAAiBD,EAAI,MAAM,YAAY;GAgB7C,IAdE,OAAOC,KAAmB,YACvB,kCAAkC,KAAKA,CAAc,KAYtCD,EAAI,KAAK,aACZ,WAAW,OAAO,CAAC;GAGpC,IAAME,IAA6B;IACjC;IACA,MAAMF,EAAI,KAAK;IACf,QAAQA,EAAI,MAAM;IAClB,QAAQA,EAAI,MAAM;IAClB,WAAWA,EAAI,UAAU;IACzB,UAAUA,EAAI,MAAM;IACpB,UAAUA,EAAI,MAAM;IACpB,MAAM;GACR;GAEA,OAAO;IACL,MAAM;IACN,OAAO,GAA8B;KAEnC,AADA,EAAQ,WAAWG,EAAQ,MAAM,GACjC,EAAQ,WAAWA,EAAQ,MAAM;IACnC;IACA,UAAiC;KAE/B,OADKD,EAAQ,OACN;MACL,MAAM;MACN,OAAO;OAAE,GAAGA,EAAQ;OAAQ,GAAGA,EAAQ;MAAO;MAC9C,SAAS;OAAE,GAAGA,EAAQ;OAAU,GAAGA,EAAQ;MAAS;MACpD,WAAWA,EAAQ;KACrB,IAN0B;IAO5B;IACA,MAAM,GAAwB,GAAmC;KAE/D,IADA,EAAQ,OAAO,IACXE,MAAW,UAAU;KAEzB,IAAM,EAAE,KAAKC,GAAG,SAAM,WAAQ,WAAQ,aAAU,aAAU,iBAAcH,GAGlEI,IAAI,KAAK,IAAIC,GAAQC,CAAQ,GAC7BC,IAAI,KAAK,IAAIC,GAAQC,CAAQ,GAC7BC,IAAQ,KAAK,IAAIJ,IAAWD,CAAM,GAClCM,IAAS,KAAK,IAAIF,IAAWD,CAAM;KAGzC,IAAIE,MAAU,KAAKC,MAAW,GAAG;MAC/B,AAAKC,KAAWT,EAAE,aAAa,CAAC,CAAC;MACjC;KACF;KAEA,IAAMU,IAAOV,EAAE,YAAY;MAAE;MAAG;MAAG;MAAO;KAAO,GAAGW,CAAI;KAExD,EAAE,aAAaC,EAAkBZ,EAAE,aAAa,GAAGU,GAAMD,CAAS,CAAC;IACrE;GACF;EACF;CACF;CAOA,eAAe;AACjB"}