@weasel-js/core 1.10.0 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/CHANGELOG.md +168 -0
  2. package/README.md +33 -0
  3. package/dist/affordances/composeAffordanceLayer.js +21 -21
  4. package/dist/affordances/composeAffordanceLayer.js.map +1 -1
  5. package/dist/affordances/hitAffordanceRegions.js +48 -32
  6. package/dist/affordances/hitAffordanceRegions.js.map +1 -1
  7. package/dist/affordances/pathAnchors.js +50 -28
  8. package/dist/affordances/pathAnchors.js.map +1 -1
  9. package/dist/affordances/rotateCursor.js +12 -0
  10. package/dist/affordances/rotateCursor.js.map +1 -0
  11. package/dist/affordances/rotationHandle.js +40 -43
  12. package/dist/affordances/rotationHandle.js.map +1 -1
  13. package/dist/animation/rig/rigNode.js +9 -3
  14. package/dist/animation/rig/rigNode.js.map +1 -1
  15. package/dist/animation/rig/rigSeat.js +46 -13
  16. package/dist/animation/rig/rigSeat.js.map +1 -1
  17. package/dist/animation/rig/skinBinding.js +22 -14
  18. package/dist/animation/rig/skinBinding.js.map +1 -1
  19. package/dist/barrel/canvas.js +29 -28
  20. package/dist/barrel/interactions.js +33 -32
  21. package/dist/barrel/paths.js +11 -10
  22. package/dist/barrel/routing.js +49 -48
  23. package/dist/barrel/scene.js +10 -10
  24. package/dist/barrel/viewport.js +24 -21
  25. package/dist/canvas/Canvas.js +6 -17
  26. package/dist/canvas/Canvas.js.map +1 -1
  27. package/dist/canvas/CanvasView.js +34 -37
  28. package/dist/canvas/CanvasView.js.map +1 -1
  29. package/dist/canvas/CursorCoordsHud.js +3 -3
  30. package/dist/canvas/CursorCoordsHud.js.map +1 -1
  31. package/dist/canvas/DrawCanvas.js +3 -3
  32. package/dist/canvas/DrawCanvas.js.map +1 -1
  33. package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +24 -23
  34. package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js.map +1 -1
  35. package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js +40 -49
  36. package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js.map +1 -1
  37. package/dist/canvas/SceneCanvas/defaultNodeProperties.js +297 -259
  38. package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
  39. package/dist/canvas/SceneCanvas/features.js +3 -24
  40. package/dist/canvas/SceneCanvas/features.js.map +1 -1
  41. package/dist/canvas/SceneCanvas/insertPreviewCommands.js +97 -0
  42. package/dist/canvas/SceneCanvas/insertPreviewCommands.js.map +1 -0
  43. package/dist/canvas/SceneCanvas/poseGeometry.js.map +1 -1
  44. package/dist/canvas/SceneCanvas/props.js.map +1 -1
  45. package/dist/canvas/SceneCanvas/scenePointQuery.js +50 -0
  46. package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -0
  47. package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js +52 -0
  48. package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js.map +1 -0
  49. package/dist/canvas/SceneCanvas/useActionTunings.js +17 -0
  50. package/dist/canvas/SceneCanvas/useActionTunings.js.map +1 -0
  51. package/dist/canvas/SceneCanvas/useCanvasCamera.js +37 -29
  52. package/dist/canvas/SceneCanvas/useCanvasCamera.js.map +1 -1
  53. package/dist/canvas/SceneCanvas/useCanvasDispatcher.js +11 -11
  54. package/dist/canvas/SceneCanvas/useCanvasDispatcher.js.map +1 -1
  55. package/dist/canvas/SceneCanvas/useDispatcherOverlayLayer.js +45 -109
  56. package/dist/canvas/SceneCanvas/useDispatcherOverlayLayer.js.map +1 -1
  57. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +49 -41
  58. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
  59. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +64 -96
  60. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  61. package/dist/canvas/SceneCanvas/worldPointAt.js +3 -4
  62. package/dist/canvas/SceneCanvas/worldPointAt.js.map +1 -1
  63. package/dist/canvas/SceneCanvas.js +77 -80
  64. package/dist/canvas/SceneCanvas.js.map +1 -1
  65. package/dist/canvas/SceneViewCanvas.js +3 -3
  66. package/dist/canvas/SceneViewCanvas.js.map +1 -1
  67. package/dist/canvas/SurfaceCanvas.js +40 -0
  68. package/dist/canvas/SurfaceCanvas.js.map +1 -0
  69. package/dist/canvas/adapterDerivedPath.js +13 -13
  70. package/dist/canvas/adapterDerivedPath.js.map +1 -1
  71. package/dist/canvas/adapterPickSource.js +34 -0
  72. package/dist/canvas/adapterPickSource.js.map +1 -0
  73. package/dist/canvas/affordanceAt.js +1 -1
  74. package/dist/canvas/affordanceAt.js.map +1 -1
  75. package/dist/canvas/buildSceneLayer.js.map +1 -1
  76. package/dist/canvas/buildSceneTree.js +56 -34
  77. package/dist/canvas/buildSceneTree.js.map +1 -1
  78. package/dist/canvas/copiesAbove.js +27 -0
  79. package/dist/canvas/copiesAbove.js.map +1 -0
  80. package/dist/canvas/defaultDrawOne.js +24 -23
  81. package/dist/canvas/defaultDrawOne.js.map +1 -1
  82. package/dist/canvas/deps/editAnchors.js +45 -50
  83. package/dist/canvas/deps/editAnchors.js.map +1 -1
  84. package/dist/canvas/deps/hitTestArea.js +30 -15
  85. package/dist/canvas/deps/hitTestArea.js.map +1 -1
  86. package/dist/canvas/deps/insert.js +134 -100
  87. package/dist/canvas/deps/insert.js.map +1 -1
  88. package/dist/canvas/deps/layout.js +9 -15
  89. package/dist/canvas/deps/layout.js.map +1 -1
  90. package/dist/canvas/derivedPath.js +53 -29
  91. package/dist/canvas/derivedPath.js.map +1 -1
  92. package/dist/canvas/insertPreviewExtent.js +33 -31
  93. package/dist/canvas/insertPreviewExtent.js.map +1 -1
  94. package/dist/canvas/overChildren.js +29 -0
  95. package/dist/canvas/overChildren.js.map +1 -0
  96. package/dist/canvas/paintedCursorLayer.js.map +1 -1
  97. package/dist/canvas/pickReach.js +23 -0
  98. package/dist/canvas/pickReach.js.map +1 -0
  99. package/dist/canvas/pickWalk.js +51 -80
  100. package/dist/canvas/pickWalk.js.map +1 -1
  101. package/dist/canvas/sceneAdapter.js +14 -35
  102. package/dist/canvas/sceneAdapter.js.map +1 -1
  103. package/dist/canvas/sceneSlotWiring.js +11 -10
  104. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  105. package/dist/canvas/sceneViewRender.js +38 -44
  106. package/dist/canvas/sceneViewRender.js.map +1 -1
  107. package/dist/canvas/useCanvasView.js +42 -44
  108. package/dist/canvas/useCanvasView.js.map +1 -1
  109. package/dist/canvas/useSceneAdapter.js +22 -0
  110. package/dist/canvas/useSceneAdapter.js.map +1 -0
  111. package/dist/canvas/useSurfaceInput.js +13 -16
  112. package/dist/canvas/useSurfaceInput.js.map +1 -1
  113. package/dist/canvas/viewRegistry.js +31 -23
  114. package/dist/canvas/viewRegistry.js.map +1 -1
  115. package/dist/canvas/wrapNodeOutput.js +7 -2
  116. package/dist/canvas/wrapNodeOutput.js.map +1 -1
  117. package/dist/clipboard.d.ts +1 -1
  118. package/dist/{clone-2a5hVbBd.d.ts → clone-C0eADtS0.d.ts} +13 -13
  119. package/dist/clone.d.ts +2 -2
  120. package/dist/core/geometry/poseDescriptor.js +49 -23
  121. package/dist/core/geometry/poseDescriptor.js.map +1 -1
  122. package/dist/core/geometry/regionHit.js +2 -2
  123. package/dist/core/geometry/regionHit.js.map +1 -1
  124. package/dist/core/geometry/targetFrame.js.map +1 -1
  125. package/dist/core/layers/children.js +1 -3
  126. package/dist/core/layers/children.js.map +1 -1
  127. package/dist/core/ops/create.js +2 -2
  128. package/dist/core/ops/create.js.map +1 -1
  129. package/dist/core/ops/delete.js +19 -23
  130. package/dist/core/ops/delete.js.map +1 -1
  131. package/dist/core/ops/reorder/index.js +3 -7
  132. package/dist/core/ops/reorder/index.js.map +1 -1
  133. package/dist/core/ops/reorder/setChildOrder.js +2 -4
  134. package/dist/core/ops/reorder/setChildOrder.js.map +1 -1
  135. package/dist/core/ops/reparent.js +0 -1
  136. package/dist/core/ops/reparent.js.map +1 -1
  137. package/dist/core/ops/slot.js +3 -6
  138. package/dist/core/ops/slot.js.map +1 -1
  139. package/dist/core/pages/fitViewToPage.js +15 -0
  140. package/dist/core/pages/fitViewToPage.js.map +1 -0
  141. package/dist/core/pages/pages.js +65 -0
  142. package/dist/core/pages/pages.js.map +1 -0
  143. package/dist/core/pages/pagesLayer.js +27 -0
  144. package/dist/core/pages/pagesLayer.js.map +1 -0
  145. package/dist/core/paint/meshKindLazy.js +2 -2
  146. package/dist/core/paint/meshKindLazy.js.map +1 -1
  147. package/dist/core/scene/NodeProperties.js.map +1 -1
  148. package/dist/core/scene/composeFrame.js.map +1 -1
  149. package/dist/core/scene/composePose.js +58 -20
  150. package/dist/core/scene/composePose.js.map +1 -1
  151. package/dist/core/scene/effectivePose.js +5 -1
  152. package/dist/core/scene/effectivePose.js.map +1 -1
  153. package/dist/core/scene/historyFacade.js +19 -13
  154. package/dist/core/scene/historyFacade.js.map +1 -1
  155. package/dist/core/scene/nodeFnFields.js +7 -0
  156. package/dist/core/scene/nodeFnFields.js.map +1 -1
  157. package/dist/core/scene/outerOps.js +2 -1
  158. package/dist/core/scene/outerOps.js.map +1 -1
  159. package/dist/core/scene/reconcileSpecs.js +1 -1
  160. package/dist/core/scene/reconcileSpecs.js.map +1 -1
  161. package/dist/core/scene/sceneRecording.js +2 -1
  162. package/dist/core/scene/sceneRecording.js.map +1 -1
  163. package/dist/core/scene/types.js.map +1 -1
  164. package/dist/core/viewport/screenSlop.js.map +1 -1
  165. package/dist/core/viewport/useDecayLoop.js +28 -30
  166. package/dist/core/viewport/useDecayLoop.js.map +1 -1
  167. package/dist/core/viewport/useNodeOverlayFrame.js.map +1 -1
  168. package/dist/core/viewport/useViewAnimation.js +70 -69
  169. package/dist/core/viewport/useViewAnimation.js.map +1 -1
  170. package/dist/core/viewport/view.js +8 -5
  171. package/dist/core/viewport/view.js.map +1 -1
  172. package/dist/core/viewport/zoomAt.js +11 -2
  173. package/dist/core/viewport/zoomAt.js.map +1 -1
  174. package/dist/{cornerHandles-BEshc4z3.d.ts → cornerHandles-BSKbyuvQ.d.ts} +1 -1
  175. package/dist/features/paths/editablePath.js +24 -8
  176. package/dist/features/paths/editablePath.js.map +1 -1
  177. package/dist/features/paths/index.js +4 -4
  178. package/dist/features/paths/shapeGenerator.js +89 -0
  179. package/dist/features/paths/shapeGenerator.js.map +1 -0
  180. package/dist/features/paths/usePathEdit.js +13 -13
  181. package/dist/features/poseRun/usePoseRun.js +25 -25
  182. package/dist/features/poseRun/usePoseRun.js.map +1 -1
  183. package/dist/features/text/useSceneTextEdit.js +82 -64
  184. package/dist/features/text/useSceneTextEdit.js.map +1 -1
  185. package/dist/features/viewports/viewResolver.js +28 -24
  186. package/dist/features/viewports/viewResolver.js.map +1 -1
  187. package/dist/icons/SliceIcon.js +24 -0
  188. package/dist/icons/SliceIcon.js.map +1 -0
  189. package/dist/index.d.ts +956 -581
  190. package/dist/index.js +260 -251
  191. package/dist/interactions/actions/align/align.js +9 -9
  192. package/dist/interactions/actions/align/align.js.map +1 -1
  193. package/dist/interactions/actions/booleans/booleans.js +18 -18
  194. package/dist/interactions/actions/booleans/booleans.js.map +1 -1
  195. package/dist/interactions/actions/booleans/operands.js.map +1 -1
  196. package/dist/interactions/actions/clone/action.js +35 -30
  197. package/dist/interactions/actions/clone/action.js.map +1 -1
  198. package/dist/interactions/actions/defaults/anchorEditing.js +56 -59
  199. package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
  200. package/dist/interactions/actions/defaults/anchorHandles.js +42 -0
  201. package/dist/interactions/actions/defaults/anchorHandles.js.map +1 -0
  202. package/dist/interactions/actions/defaults/anchorJoin.js +92 -0
  203. package/dist/interactions/actions/defaults/anchorJoin.js.map +1 -0
  204. package/dist/interactions/actions/defaults/bendSegment.js +46 -0
  205. package/dist/interactions/actions/defaults/bendSegment.js.map +1 -0
  206. package/dist/interactions/actions/defaults/copySubtree.js +1 -1
  207. package/dist/interactions/actions/defaults/copySubtree.js.map +1 -1
  208. package/dist/interactions/actions/defaults/duplicate.js +32 -17
  209. package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
  210. package/dist/interactions/actions/defaults/enterPathEdit.js +19 -7
  211. package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
  212. package/dist/interactions/actions/defaults/icons/anchorIcons.js +75 -0
  213. package/dist/interactions/actions/defaults/icons/anchorIcons.js.map +1 -0
  214. package/dist/interactions/actions/defaults/icons/editIcons.js +35 -8
  215. package/dist/interactions/actions/defaults/icons/editIcons.js.map +1 -1
  216. package/dist/interactions/actions/defaults/insertPathAnchor.js +16 -31
  217. package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
  218. package/dist/interactions/actions/defaults/nudge.js +28 -23
  219. package/dist/interactions/actions/defaults/nudge.js.map +1 -1
  220. package/dist/interactions/actions/defaults/pinchZoom.js +29 -29
  221. package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
  222. package/dist/interactions/actions/defaults/segmentUnder.js +22 -0
  223. package/dist/interactions/actions/defaults/segmentUnder.js.map +1 -0
  224. package/dist/interactions/actions/defaults/viewportZoom.js +82 -79
  225. package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
  226. package/dist/interactions/actions/distribute/distribute.js +8 -8
  227. package/dist/interactions/actions/distribute/distribute.js.map +1 -1
  228. package/dist/interactions/actions/edit-anchors/action.js +58 -55
  229. package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
  230. package/dist/interactions/actions/edit-anchors/preview.js +26 -0
  231. package/dist/interactions/actions/edit-anchors/preview.js.map +1 -0
  232. package/dist/interactions/actions/edit-anchors/symmetry.js +11 -0
  233. package/dist/interactions/actions/edit-anchors/symmetry.js.map +1 -0
  234. package/dist/interactions/actions/flip/action.js.map +1 -1
  235. package/dist/interactions/actions/geometryProjection.js.map +1 -1
  236. package/dist/interactions/actions/insert/action.js +37 -30
  237. package/dist/interactions/actions/insert/action.js.map +1 -1
  238. package/dist/interactions/actions/insert/frame.js +9 -2
  239. package/dist/interactions/actions/insert/frame.js.map +1 -1
  240. package/dist/interactions/actions/insert/repositionAction.js +31 -0
  241. package/dist/interactions/actions/insert/repositionAction.js.map +1 -0
  242. package/dist/interactions/actions/lasso-select/action.js +32 -32
  243. package/dist/interactions/actions/move/action.js +29 -25
  244. package/dist/interactions/actions/move/action.js.map +1 -1
  245. package/dist/interactions/actions/move/behaviors/snapToContainer.js +12 -6
  246. package/dist/interactions/actions/move/behaviors/snapToContainer.js.map +1 -1
  247. package/dist/interactions/actions/move/capture.js +32 -23
  248. package/dist/interactions/actions/move/capture.js.map +1 -1
  249. package/dist/interactions/actions/move/frame.js +2 -14
  250. package/dist/interactions/actions/move/frame.js.map +1 -1
  251. package/dist/interactions/actions/move/session.js +1 -1
  252. package/dist/interactions/actions/move/session.js.map +1 -1
  253. package/dist/interactions/actions/move/translate.js.map +1 -1
  254. package/dist/interactions/actions/outlines/createOutlines.js.map +1 -1
  255. package/dist/interactions/actions/outlines/replaceWithPaths.js +13 -13
  256. package/dist/interactions/actions/outlines/replaceWithPaths.js.map +1 -1
  257. package/dist/interactions/actions/outlines/strokeRegion.js.map +1 -1
  258. package/dist/interactions/actions/pathCommands/cutJoin.js +102 -0
  259. package/dist/interactions/actions/pathCommands/cutJoin.js.map +1 -0
  260. package/dist/interactions/actions/pathCommands/livePathEdit.js +64 -0
  261. package/dist/interactions/actions/pathCommands/livePathEdit.js.map +1 -0
  262. package/dist/interactions/actions/pathCommands/pathNodes.js +99 -0
  263. package/dist/interactions/actions/pathCommands/pathNodes.js.map +1 -0
  264. package/dist/interactions/actions/pathCommands/simplifyOffset.js +42 -0
  265. package/dist/interactions/actions/pathCommands/simplifyOffset.js.map +1 -0
  266. package/dist/interactions/actions/resize/action.js +31 -20
  267. package/dist/interactions/actions/resize/action.js.map +1 -1
  268. package/dist/interactions/actions/resize/commit.js.map +1 -1
  269. package/dist/interactions/actions/resize/frame.js +29 -15
  270. package/dist/interactions/actions/resize/frame.js.map +1 -1
  271. package/dist/interactions/actions/resize/replay.js +47 -0
  272. package/dist/interactions/actions/resize/replay.js.map +1 -0
  273. package/dist/interactions/actions/resize/session.js +49 -35
  274. package/dist/interactions/actions/resize/session.js.map +1 -1
  275. package/dist/interactions/actions/resolveOverlays.js +2 -1
  276. package/dist/interactions/actions/resolveOverlays.js.map +1 -1
  277. package/dist/interactions/actions/rotate/action.js +40 -25
  278. package/dist/interactions/actions/rotate/action.js.map +1 -1
  279. package/dist/interactions/actions/rotate/frame.js +1 -1
  280. package/dist/interactions/actions/rotate/frame.js.map +1 -1
  281. package/dist/interactions/actions/rotate/replay.js +22 -0
  282. package/dist/interactions/actions/rotate/replay.js.map +1 -0
  283. package/dist/interactions/actions/rotate/session.js +42 -32
  284. package/dist/interactions/actions/rotate/session.js.map +1 -1
  285. package/dist/interactions/actions/transform-again/action.js +63 -0
  286. package/dist/interactions/actions/transform-again/action.js.map +1 -0
  287. package/dist/interactions/actions/transform-again/memory.js +35 -0
  288. package/dist/interactions/actions/transform-again/memory.js.map +1 -0
  289. package/dist/interactions/actions/useStandardActions.js +101 -83
  290. package/dist/interactions/actions/useStandardActions.js.map +1 -1
  291. package/dist/move.d.ts +4 -4
  292. package/dist/{originProjection-PPT88jZv.d.ts → originProjection-CoNVpITV.d.ts} +1 -1
  293. package/dist/resize.d.ts +3 -3
  294. package/dist/style.css +1 -1
  295. package/dist/tools/builtin/ellipse/useEllipseTool.js +16 -15
  296. package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
  297. package/dist/tools/builtin/line/useLineTool.js +16 -15
  298. package/dist/tools/builtin/line/useLineTool.js.map +1 -1
  299. package/dist/tools/builtin/pen/usePenTool.js +111 -111
  300. package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
  301. package/dist/tools/builtin/pickTopMostHit.js +11 -14
  302. package/dist/tools/builtin/pickTopMostHit.js.map +1 -1
  303. package/dist/tools/builtin/polygon/usePolygonTool.js +17 -15
  304. package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
  305. package/dist/tools/builtin/rect/useRectTool.js +15 -14
  306. package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
  307. package/dist/tools/builtin/select/adapterPointQuery.js +22 -18
  308. package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
  309. package/dist/tools/builtin/select/selectionContributions.js +1 -1
  310. package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
  311. package/dist/tools/builtin/select/useSelectTool.js +2 -1
  312. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  313. package/dist/tools/builtin/slice/useSliceTool.js +42 -40
  314. package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
  315. package/dist/tools/builtin/star/useStarTool.js +18 -16
  316. package/dist/tools/builtin/star/useStarTool.js.map +1 -1
  317. package/dist/tools/builtin/text/useTextTool.js +2 -2
  318. package/dist/tools/builtin/text/useTextTool.js.map +1 -1
  319. package/dist/{types-CIKDwlsP.d.ts → types-DBbbCy0i.d.ts} +22 -15
  320. package/dist/version.js +1 -1
  321. package/package.json +22 -22
  322. package/dist/canvas/surface.module.js +0 -6
  323. package/dist/canvas/surface.module.js.map +0 -1
  324. package/dist/canvas/surfaceBox.js +0 -16
  325. package/dist/canvas/surfaceBox.js.map +0 -1
  326. package/dist/layout/arrivals.js +0 -33
  327. package/dist/layout/arrivals.js.map +0 -1
@@ -14,8 +14,11 @@ function r(e) {
14
14
  function i(e) {
15
15
  return n(e.scale);
16
16
  }
17
- function a(e) {
18
- let t = o(e.scale.x), n = o(e.scale.y), r = s(e.x), i = s(e.y);
17
+ function a(e, t) {
18
+ return e.x === t.x && e.y === t.y && e.scale.x === t.scale.x && e.scale.y === t.scale.y;
19
+ }
20
+ function o(e) {
21
+ let t = s(e.scale.x), n = s(e.scale.y), r = c(e.x), i = c(e.y);
19
22
  return t === e.scale.x && n === e.scale.y && r === e.x && i === e.y ? e : {
20
23
  x: r,
21
24
  y: i,
@@ -25,13 +28,13 @@ function a(e) {
25
28
  }
26
29
  };
27
30
  }
28
- function o(t) {
31
+ function s(t) {
29
32
  return t < 0 && t > -Infinity ? -e(-t) : e(t);
30
33
  }
31
- function s(e) {
34
+ function c(e) {
32
35
  return Number.isFinite(e) ? e : (t(`a position of ${e}`), 0);
33
36
  }
34
37
  //#endregion
35
- export { a as normalizeView, r as viewToTransform, i as viewZoom };
38
+ export { o as normalizeView, a as sameView, r as viewToTransform, i as viewZoom };
36
39
 
37
40
  //# sourceMappingURL=view.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"view.js","names":["normalizeZoom","normalizeZoom$1","viewToTransform","view","viewZoom","meanScale","normalizeView","sx","normalizeAxis","sy","x","finiteTranslation","y","s","v","warnInvalidView"],"sources":["../../../src/core/viewport/view.ts"],"sourcesContent":["import type { ViewTransform } from './viewTransform';\nimport type { View } from '@weasel-js/routing';\nimport { meanScale } from '@weasel-js/geom';\nimport { normalizeZoom, warnInvalidView } from './zoomBounds';\n\n/**\n * Every view the kit keeps is total: each `scale` axis is finite with a\n * magnitude of at least `ZOOM_FLOOR` and `x` / `y` are finite. A negative\n * axis is a flipped one (y-up), not a degenerate one, and keeps its sign.\n * Canvases, views, and animations pass what they are given through\n * {@link normalizeView}, so screen/world conversion never divides by zero.\n */\nexport type { View };\n\n/** A zoom factor: a `number` applies to both axes, a `{x, y}` vector per axis. */\nexport type ZoomFactor = number | { x: number; y: number };\n\n/** A zoom-clamp bound: a `number` applies to both axes, a `{x, y}` vector per axis. */\nexport type ZoomBound = number | { x: number; y: number };\n\n/**\n * Bridge `View` into the legacy `ViewTransform` shape. The two use opposite\n * sign conventions for the translation half: `view.x` is camera position;\n * `panX` is canvas translation.\n */\nexport function viewToTransform(view: View): ViewTransform {\n // `+ 0` coerces `-0 → 0` without swallowing NaN (matches viewToMat3).\n return {\n panX: -view.x * view.scale.x + 0,\n panY: -view.y * view.scale.y + 0,\n zoom: { x: view.scale.x, y: view.scale.y },\n };\n}\n\n/**\n * How far `view` is zoomed in, as one number — the geometric mean of its two\n * axes, so a uniform view reports its own scale and a stretched one reports\n * the scale that covers the same area.\n */\nexport function viewZoom(view: View): number {\n return meanScale(view.scale);\n}\n\n/**\n * `view` inside the invariant {@link View} documents: a zero or non-finite\n * scale axis becomes `ZOOM_FLOOR`, a negative one keeps its sign, and a\n * non-finite translation becomes 0. Returns `view` itself when it already\n * holds, so a valid view keeps its identity.\n */\nexport function normalizeView(view: View): View {\n const sx = normalizeAxis(view.scale.x);\n const sy = normalizeAxis(view.scale.y);\n const x = finiteTranslation(view.x);\n const y = finiteTranslation(view.y);\n if (sx === view.scale.x && sy === view.scale.y && x === view.x && y === view.y) return view;\n return { x, y, scale: { x: sx, y: sy } };\n}\n\nfunction normalizeAxis(s: number): number {\n return s < 0 && s > -Infinity ? -normalizeZoom(-s) : normalizeZoom(s);\n}\n\nfunction finiteTranslation(v: number): number {\n if (Number.isFinite(v)) return v;\n warnInvalidView(`a position of ${v}`);\n return 0;\n}\n"],"mappings":";;;AAyBA,SAAgBE,EAAgB,GAA2B;CAEzD,OAAO;EACL,MAAM,CAACC,EAAK,IAAIA,EAAK,MAAM,IAAI;EAC/B,MAAM,CAACA,EAAK,IAAIA,EAAK,MAAM,IAAI;EAC/B,MAAM;GAAE,GAAGA,EAAK,MAAM;GAAG,GAAGA,EAAK,MAAM;EAAE;CAC3C;AACF;AAOA,SAAgBC,EAAS,GAAoB;CAC3C,OAAOC,EAAUF,EAAK,KAAK;AAC7B;AAQA,SAAgBG,EAAc,GAAkB;CAC9C,IAAMC,IAAKC,EAAcL,EAAK,MAAM,CAAC,GAC/BM,IAAKD,EAAcL,EAAK,MAAM,CAAC,GAC/BO,IAAIC,EAAkBR,EAAK,CAAC,GAC5BS,IAAID,EAAkBR,EAAK,CAAC;CAElC,OADII,MAAOJ,EAAK,MAAM,KAAKM,MAAON,EAAK,MAAM,KAAKO,MAAMP,EAAK,KAAKS,MAAMT,EAAK,IAAUA,IAChF;EAAE;EAAG;EAAG,OAAO;GAAE,GAAGI;GAAI,GAAGE;EAAG;CAAE;AACzC;AAEA,SAASD,EAAc,GAAmB;CACxC,OAAOK,IAAI,KAAKA,IAAI,YAAY,CAACb,EAAc,CAACa,CAAC,IAAIb,EAAca,CAAC;AACtE;AAEA,SAASF,EAAkB,GAAmB;CAG5C,OAFI,OAAO,SAASG,CAAC,IAAUA,KAC/BC,EAAgB,iBAAiBD,GAAG,GAC7B;AACT"}
1
+ {"version":3,"file":"view.js","names":["normalizeZoom","normalizeZoom$1","viewToTransform","view","viewZoom","meanScale","sameView","a","b","normalizeView","sx","normalizeAxis","sy","x","finiteTranslation","y","s","v","warnInvalidView"],"sources":["../../../src/core/viewport/view.ts"],"sourcesContent":["import type { ViewTransform } from './viewTransform';\nimport type { View } from '@weasel-js/routing';\nimport { meanScale } from '@weasel-js/geom';\nimport { normalizeZoom, warnInvalidView } from './zoomBounds';\n\n/**\n * Every view the kit keeps is total: each `scale` axis is finite with a\n * magnitude of at least `ZOOM_FLOOR` and `x` / `y` are finite. A negative\n * axis is a flipped one (y-up), not a degenerate one, and keeps its sign.\n * Canvases, views, and animations pass what they are given through\n * {@link normalizeView}, so screen/world conversion never divides by zero.\n */\nexport type { View };\n\n/** A zoom factor: a `number` applies to both axes, a `{x, y}` vector per axis. */\nexport type ZoomFactor = number | { x: number; y: number };\n\n/** A zoom-clamp bound: a `number` applies to both axes, a `{x, y}` vector per axis. */\nexport type ZoomBound = number | { x: number; y: number };\n\n/**\n * Bridge `View` into the legacy `ViewTransform` shape. The two use opposite\n * sign conventions for the translation half: `view.x` is camera position;\n * `panX` is canvas translation.\n */\nexport function viewToTransform(view: View): ViewTransform {\n // `+ 0` coerces `-0 → 0` without swallowing NaN (matches viewToMat3).\n return {\n panX: -view.x * view.scale.x + 0,\n panY: -view.y * view.scale.y + 0,\n zoom: { x: view.scale.x, y: view.scale.y },\n };\n}\n\n/**\n * How far `view` is zoomed in, as one number — the geometric mean of its two\n * axes, so a uniform view reports its own scale and a stretched one reports\n * the scale that covers the same area.\n */\nexport function viewZoom(view: View): number {\n return meanScale(view.scale);\n}\n\n/** Whether two views are the same camera, whatever objects hold them. */\nexport function sameView(a: View, b: View): boolean {\n return a.x === b.x && a.y === b.y && a.scale.x === b.scale.x && a.scale.y === b.scale.y;\n}\n\n/**\n * `view` inside the invariant {@link View} documents: a zero or non-finite\n * scale axis becomes `ZOOM_FLOOR`, a negative one keeps its sign, and a\n * non-finite translation becomes 0. Returns `view` itself when it already\n * holds, so a valid view keeps its identity.\n */\nexport function normalizeView(view: View): View {\n const sx = normalizeAxis(view.scale.x);\n const sy = normalizeAxis(view.scale.y);\n const x = finiteTranslation(view.x);\n const y = finiteTranslation(view.y);\n if (sx === view.scale.x && sy === view.scale.y && x === view.x && y === view.y) return view;\n return { x, y, scale: { x: sx, y: sy } };\n}\n\nfunction normalizeAxis(s: number): number {\n return s < 0 && s > -Infinity ? -normalizeZoom(-s) : normalizeZoom(s);\n}\n\nfunction finiteTranslation(v: number): number {\n if (Number.isFinite(v)) return v;\n warnInvalidView(`a position of ${v}`);\n return 0;\n}\n"],"mappings":";;;AAyBA,SAAgBE,EAAgB,GAA2B;CAEzD,OAAO;EACL,MAAM,CAACC,EAAK,IAAIA,EAAK,MAAM,IAAI;EAC/B,MAAM,CAACA,EAAK,IAAIA,EAAK,MAAM,IAAI;EAC/B,MAAM;GAAE,GAAGA,EAAK,MAAM;GAAG,GAAGA,EAAK,MAAM;EAAE;CAC3C;AACF;AAOA,SAAgBC,EAAS,GAAoB;CAC3C,OAAOC,EAAUF,EAAK,KAAK;AAC7B;AAGA,SAAgBG,EAAS,GAAS,GAAkB;CAClD,OAAOC,EAAE,MAAMC,EAAE,KAAKD,EAAE,MAAMC,EAAE,KAAKD,EAAE,MAAM,MAAMC,EAAE,MAAM,KAAKD,EAAE,MAAM,MAAMC,EAAE,MAAM;AACxF;AAQA,SAAgBC,EAAc,GAAkB;CAC9C,IAAMC,IAAKC,EAAcR,EAAK,MAAM,CAAC,GAC/BS,IAAKD,EAAcR,EAAK,MAAM,CAAC,GAC/BU,IAAIC,EAAkBX,EAAK,CAAC,GAC5BY,IAAID,EAAkBX,EAAK,CAAC;CAElC,OADIO,MAAOP,EAAK,MAAM,KAAKS,MAAOT,EAAK,MAAM,KAAKU,MAAMV,EAAK,KAAKY,MAAMZ,EAAK,IAAUA,IAChF;EAAE;EAAG;EAAG,OAAO;GAAE,GAAGO;GAAI,GAAGE;EAAG;CAAE;AACzC;AAEA,SAASD,EAAc,GAAmB;CACxC,OAAOK,IAAI,KAAKA,IAAI,YAAY,CAAChB,EAAc,CAACgB,CAAC,IAAIhB,EAAcgB,CAAC;AACtE;AAEA,SAASF,EAAkB,GAAmB;CAG5C,OAFI,OAAO,SAASG,CAAC,IAAUA,KAC/BC,EAAgB,iBAAiBD,GAAG,GAC7B;AACT"}
@@ -5,7 +5,16 @@ function i(e, t, r) {
5
5
  let i = n(Math.min(r, Math.max(t, Math.abs(e))));
6
6
  return e < 0 ? -i : i;
7
7
  }
8
- function a(n, a, o, s) {
8
+ function a(e, t) {
9
+ return t === "x" ? {
10
+ x: e,
11
+ y: 1
12
+ } : t === "y" ? {
13
+ x: 1,
14
+ y: e
15
+ } : e;
16
+ }
17
+ function o(n, a, o, s) {
9
18
  let c = r(n), l = typeof o == "number" ? o : o.x, u = typeof o == "number" ? o : o.y, d = typeof s?.min == "number" ? s.min : s?.min?.x ?? t, f = typeof s?.min == "number" ? s.min : s?.min?.y ?? t, p = typeof s?.max == "number" ? s.max : s?.max?.x ?? e, m = typeof s?.max == "number" ? s.max : s?.max?.y ?? e, h = i(c.scale.x * l, d, p), g = i(c.scale.y * u, f, m), _ = a.x / c.scale.x + c.x, v = a.y / c.scale.y + c.y;
10
19
  return {
11
20
  scale: {
@@ -17,6 +26,6 @@ function a(n, a, o, s) {
17
26
  };
18
27
  }
19
28
  //#endregion
20
- export { a as zoomAt };
29
+ export { a as axisZoomFactor, o as zoomAt };
21
30
 
22
31
  //# sourceMappingURL=zoomAt.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"zoomAt.js","names":["clampScale","magnitude","normalizeZoom","max","min","scale","zoomAt","view","normalizeView","input","fx","factor","fy","minX","opts","DEFAULT_MIN_ZOOM","minY","maxX","DEFAULT_MAX_ZOOM","maxY","nextX","nextY","worldX","anchor","worldY"],"sources":["../../../src/core/viewport/zoomAt.ts"],"sourcesContent":["import { normalizeView, type View, type ZoomFactor, type ZoomBound } from './view';\nimport { DEFAULT_MIN_ZOOM, DEFAULT_MAX_ZOOM, normalizeZoom } from './zoomBounds';\n\n/** Optional clamp bounds for `zoomAt`. Defaults: {@link DEFAULT_MIN_ZOOM} /\n * {@link DEFAULT_MAX_ZOOM} (per axis). */\nexport interface ZoomClampOpts {\n min?: ZoomBound;\n max?: ZoomBound;\n}\n\n/** Bound a scale's *magnitude*, keeping its direction. `scale.y < 0` is how a\n * `View` spells a y-up camera, so clamping the signed value against positive\n * bounds would flip the axis and collapse the zoom rather than limit it. */\nfunction clampScale(scale: number, min: number, max: number): number {\n const magnitude = normalizeZoom(Math.min(max, Math.max(min, Math.abs(scale))));\n return scale < 0 ? -magnitude : magnitude;\n}\n\n/**\n * Pure zoom primitive. Returns a new `View` whose `scale` is multiplied\n * by `factor` (per-axis, clamped) and whose translation is adjusted so\n * that the world point currently under `anchor` (screen coords relative\n * to the canvas top-left) stays under the same screen pixel after the\n * zoom — independently on each axis.\n *\n * `factor: number` means \"apply uniformly to both axes\". `factor: {x, y}`\n * applies per-axis factors. Likewise for `opts.min` / `opts.max`.\n */\nexport function zoomAt(\n input: View,\n anchor: { x: number; y: number },\n factor: ZoomFactor,\n opts?: ZoomClampOpts,\n): View {\n const view = normalizeView(input);\n const fx = typeof factor === 'number' ? factor : factor.x;\n const fy = typeof factor === 'number' ? factor : factor.y;\n const minX = typeof opts?.min === 'number' ? opts.min : opts?.min?.x ?? DEFAULT_MIN_ZOOM;\n const minY = typeof opts?.min === 'number' ? opts.min : opts?.min?.y ?? DEFAULT_MIN_ZOOM;\n const maxX = typeof opts?.max === 'number' ? opts.max : opts?.max?.x ?? DEFAULT_MAX_ZOOM;\n const maxY = typeof opts?.max === 'number' ? opts.max : opts?.max?.y ?? DEFAULT_MAX_ZOOM;\n\n const nextX = clampScale(view.scale.x * fx, minX, maxX);\n const nextY = clampScale(view.scale.y * fy, minY, maxY);\n\n const worldX = anchor.x / view.scale.x + view.x;\n const worldY = anchor.y / view.scale.y + view.y;\n return {\n scale: { x: nextX, y: nextY },\n x: worldX - anchor.x / nextX,\n y: worldY - anchor.y / nextY,\n };\n}\n"],"mappings":";;;AAaA,SAASA,EAAW,GAAe,GAAa,GAAqB;CACnE,IAAMC,IAAYC,EAAc,KAAK,IAAIC,GAAK,KAAK,IAAIC,GAAK,KAAK,IAAIC,CAAK,CAAC,CAAC,CAAC;CAC7E,OAAOA,IAAQ,IAAI,CAACJ,IAAYA;AAClC;AAYA,SAAgBK,EACd,GACA,GACA,GACA,GACM;CACN,IAAMC,IAAOC,EAAcC,CAAK,GAC1BC,IAAK,OAAOC,KAAW,WAAWA,IAASA,EAAO,GAClDC,IAAK,OAAOD,KAAW,WAAWA,IAASA,EAAO,GAClDE,IAAO,OAAOC,GAAM,OAAQ,WAAWA,EAAK,MAAMA,GAAM,KAAK,KAAKC,GAClEC,IAAO,OAAOF,GAAM,OAAQ,WAAWA,EAAK,MAAMA,GAAM,KAAK,KAAKC,GAClEE,IAAO,OAAOH,GAAM,OAAQ,WAAWA,EAAK,MAAMA,GAAM,KAAK,KAAKI,GAClEC,IAAO,OAAOL,GAAM,OAAQ,WAAWA,EAAK,MAAMA,GAAM,KAAK,KAAKI,GAElEE,IAAQpB,EAAWO,EAAK,MAAM,IAAIG,GAAIG,GAAMI,CAAI,GAChDI,IAAQrB,EAAWO,EAAK,MAAM,IAAIK,GAAII,GAAMG,CAAI,GAEhDG,IAASC,EAAO,IAAIhB,EAAK,MAAM,IAAIA,EAAK,GACxCiB,IAASD,EAAO,IAAIhB,EAAK,MAAM,IAAIA,EAAK;CAC9C,OAAO;EACL,OAAO;GAAE,GAAGa;GAAO,GAAGC;EAAM;EAC5B,GAAGC,IAASC,EAAO,IAAIH;EACvB,GAAGI,IAASD,EAAO,IAAIF;CACzB;AACF"}
1
+ {"version":3,"file":"zoomAt.js","names":["clampScale","magnitude","normalizeZoom","max","min","scale","axisZoomFactor","axis","factor","zoomAt","view","normalizeView","input","fx","fy","minX","opts","DEFAULT_MIN_ZOOM","minY","maxX","DEFAULT_MAX_ZOOM","maxY","nextX","nextY","worldX","anchor","worldY"],"sources":["../../../src/core/viewport/zoomAt.ts"],"sourcesContent":["import { normalizeView, type View, type ZoomFactor, type ZoomBound } from './view';\nimport { DEFAULT_MIN_ZOOM, DEFAULT_MAX_ZOOM, normalizeZoom } from './zoomBounds';\n\n/** Optional clamp bounds for `zoomAt`. Defaults: {@link DEFAULT_MIN_ZOOM} /\n * {@link DEFAULT_MAX_ZOOM} (per axis). */\nexport interface ZoomClampOpts {\n min?: ZoomBound;\n max?: ZoomBound;\n}\n\n/** Bound a scale's *magnitude*, keeping its direction. `scale.y < 0` is how a\n * `View` spells a y-up camera, so clamping the signed value against positive\n * bounds would flip the axis and collapse the zoom rather than limit it. */\nfunction clampScale(scale: number, min: number, max: number): number {\n const magnitude = normalizeZoom(Math.min(max, Math.max(min, Math.abs(scale))));\n return scale < 0 ? -magnitude : magnitude;\n}\n\n/** Which axes a zoom scales. */\nexport type ZoomAxis = 'both' | 'x' | 'y';\n\n/** `factor` held to one axis: the other axis's factor is 1, so `zoomAt` leaves\n * its scale and its translation where they were. */\nexport function axisZoomFactor(factor: number, axis: ZoomAxis): ZoomFactor {\n if (axis === 'x') return { x: factor, y: 1 };\n if (axis === 'y') return { x: 1, y: factor };\n return factor;\n}\n\n/**\n * Pure zoom primitive. Returns a new `View` whose `scale` is multiplied\n * by `factor` (per-axis, clamped) and whose translation is adjusted so\n * that the world point currently under `anchor` (screen coords relative\n * to the canvas top-left) stays under the same screen pixel after the\n * zoom — independently on each axis.\n *\n * `factor: number` means \"apply uniformly to both axes\". `factor: {x, y}`\n * applies per-axis factors. Likewise for `opts.min` / `opts.max`.\n */\nexport function zoomAt(\n input: View,\n anchor: { x: number; y: number },\n factor: ZoomFactor,\n opts?: ZoomClampOpts,\n): View {\n const view = normalizeView(input);\n const fx = typeof factor === 'number' ? factor : factor.x;\n const fy = typeof factor === 'number' ? factor : factor.y;\n const minX = typeof opts?.min === 'number' ? opts.min : opts?.min?.x ?? DEFAULT_MIN_ZOOM;\n const minY = typeof opts?.min === 'number' ? opts.min : opts?.min?.y ?? DEFAULT_MIN_ZOOM;\n const maxX = typeof opts?.max === 'number' ? opts.max : opts?.max?.x ?? DEFAULT_MAX_ZOOM;\n const maxY = typeof opts?.max === 'number' ? opts.max : opts?.max?.y ?? DEFAULT_MAX_ZOOM;\n\n const nextX = clampScale(view.scale.x * fx, minX, maxX);\n const nextY = clampScale(view.scale.y * fy, minY, maxY);\n\n const worldX = anchor.x / view.scale.x + view.x;\n const worldY = anchor.y / view.scale.y + view.y;\n return {\n scale: { x: nextX, y: nextY },\n x: worldX - anchor.x / nextX,\n y: worldY - anchor.y / nextY,\n };\n}\n"],"mappings":";;;AAaA,SAASA,EAAW,GAAe,GAAa,GAAqB;CACnE,IAAMC,IAAYC,EAAc,KAAK,IAAIC,GAAK,KAAK,IAAIC,GAAK,KAAK,IAAIC,CAAK,CAAC,CAAC,CAAC;CAC7E,OAAOA,IAAQ,IAAI,CAACJ,IAAYA;AAClC;AAOA,SAAgBK,EAAe,GAAgB,GAA4B;CAGzE,OAFIC,MAAS,MAAY;EAAE,GAAGC;EAAQ,GAAG;CAAE,IACvCD,MAAS,MAAY;EAAE,GAAG;EAAG,GAAGC;CAAO,IACpCA;AACT;AAYA,SAAgBC,EACd,GACA,GACA,GACA,GACM;CACN,IAAMC,IAAOC,EAAcC,CAAK,GAC1BC,IAAK,OAAOL,KAAW,WAAWA,IAASA,EAAO,GAClDM,IAAK,OAAON,KAAW,WAAWA,IAASA,EAAO,GAClDO,IAAO,OAAOC,GAAM,OAAQ,WAAWA,EAAK,MAAMA,GAAM,KAAK,KAAKC,GAClEC,IAAO,OAAOF,GAAM,OAAQ,WAAWA,EAAK,MAAMA,GAAM,KAAK,KAAKC,GAClEE,IAAO,OAAOH,GAAM,OAAQ,WAAWA,EAAK,MAAMA,GAAM,KAAK,KAAKI,GAClEC,IAAO,OAAOL,GAAM,OAAQ,WAAWA,EAAK,MAAMA,GAAM,KAAK,KAAKI,GAElEE,IAAQtB,EAAWU,EAAK,MAAM,IAAIG,GAAIE,GAAMI,CAAI,GAChDI,IAAQvB,EAAWU,EAAK,MAAM,IAAII,GAAII,GAAMG,CAAI,GAEhDG,IAASC,EAAO,IAAIf,EAAK,MAAM,IAAIA,EAAK,GACxCgB,IAASD,EAAO,IAAIf,EAAK,MAAM,IAAIA,EAAK;CAC9C,OAAO;EACL,OAAO;GAAE,GAAGY;GAAO,GAAGC;EAAM;EAC5B,GAAGC,IAASC,EAAO,IAAIH;EACvB,GAAGI,IAASD,EAAO,IAAIF;CACzB;AACF"}
@@ -1,4 +1,4 @@
1
- import { B as BoundsConstraint, P as PointSnapBehavior } from './clone-2a5hVbBd.js';
1
+ import { B as BoundsConstraint, P as PointSnapBehavior } from './clone-C0eADtS0.js';
2
2
  import { Bounds, ResizeAnchor } from '@weasel-js/routing';
3
3
 
4
4
  /** Option surface for the `resize` action. */
@@ -1,8 +1,8 @@
1
1
  import { isRectPose as e } from "../../core/geometry/autoPoseDescriptor.js";
2
2
  import { pathFromPlane as t } from "../../canvas/planeClips.js";
3
- import { pathInWorld as n } from "@weasel-js/geom";
3
+ import { pathInWorld as n, worldEditToStorage as r } from "@weasel-js/geom";
4
4
  //#region src/features/paths/editablePath.ts
5
- function r(t) {
5
+ function i(t) {
6
6
  if (!t) return null;
7
7
  if (t.pose?.kind === "polygon") return { kind: "pose" };
8
8
  let n = t.data;
@@ -12,12 +12,28 @@ function r(t) {
12
12
  data: n
13
13
  } : null;
14
14
  }
15
- function i(e) {
15
+ function a(e) {
16
16
  if (!e) return null;
17
- let t = r(e);
17
+ let t = i(e);
18
18
  return t ? t.kind === "pose" ? e.pose : n(t.data.path, t.pose) : null;
19
19
  }
20
- function a(e, t, n) {
20
+ function o(e, t) {
21
+ let n = i(e);
22
+ if (!n) return null;
23
+ if (n.kind === "pose") return {
24
+ pose: t,
25
+ data: e.data
26
+ };
27
+ let { pose: a, path: o } = r(n.pose, t);
28
+ return {
29
+ pose: a,
30
+ data: {
31
+ ...n.data,
32
+ path: o
33
+ }
34
+ };
35
+ }
36
+ function s(e, t, n) {
21
37
  let r = t.pose, i = t.data, a = !1;
22
38
  for (let t of n) {
23
39
  let n = t.previewIds?.();
@@ -38,10 +54,10 @@ function a(e, t, n) {
38
54
  data: i
39
55
  };
40
56
  }
41
- function o(e, n, r, o) {
42
- return n ? t(i(a(e, n, r)), o) : null;
57
+ function c(e, n, r, i) {
58
+ return n ? t(a(s(e, n, r)), i) : null;
43
59
  }
44
60
  //#endregion
45
- export { r as classifyPathStorage, o as liveEditablePath, i as resolveEditablePathOf };
61
+ export { i as classifyPathStorage, c as liveEditablePath, a as resolveEditablePathOf, o as storeEditablePath };
46
62
 
47
63
  //# sourceMappingURL=editablePath.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"editablePath.js","names":["classifyPathStorage","node","data","isRectPose","resolveEditablePathOf","storage","pathInWorld","previewedShape","pose","touched","source","previews","previewIds","owns","pid","id","p","d","liveEditablePath","pathFromPlane","plane"],"sources":["../../../src/features/paths/editablePath.ts"],"sourcesContent":["/**\n * A node's editable polygon. It lives in one of two places: the pose itself\n * (`pose.kind === 'polygon'`), or `data.path` with a rect pose, stored pose-local.\n */\nimport { pathInWorld, type Path, type PolygonPath } from '@weasel-js/geom';\nimport type { GesturePreviewSource } from 'canvas/gestureBounds';\nimport { pathFromPlane } from 'canvas/planeClips';\nimport { isRectPose } from 'core/geometry/autoPoseDescriptor';\nimport type { RectPose } from 'core/scene/types';\nimport type { PlaneMap } from 'core/viewport/parallax';\n\n/** The two fields of a node the editable path is read from. */\nexport interface EditableShape {\n pose: unknown;\n data: unknown;\n}\n\n/** Resolve where a node's editable polygon lives. Returns null when the\n * node has neither a polygon pose nor a polygon on `data.path`. */\nexport function classifyPathStorage(\n node: EditableShape | undefined | null,\n): { kind: 'pose' } | { kind: 'data'; pose: RectPose; data: { path: PolygonPath } } | null {\n if (!node) return null;\n const pose = node.pose as { kind?: string } | undefined;\n if (pose?.kind === 'polygon') return { kind: 'pose' };\n const data = node.data as { path?: Path } | null;\n if (data?.path && (data.path as { kind?: string }).kind === 'polygon' && isRectPose(node.pose)) {\n return {\n kind: 'data',\n pose: node.pose,\n data: data as { path: PolygonPath },\n };\n }\n return null;\n}\n\n/** A node's editable polygon in world coords. Does NOT consult any preview state —\n * callers that want live state read in-flight handles themselves. */\nexport function resolveEditablePathOf(\n node: EditableShape | undefined | null,\n): PolygonPath | null {\n if (!node) return null;\n const storage = classifyPathStorage(node);\n if (!storage) return null;\n // Pose-as-polygon: the pose IS the polygon (polygon poses can't carry\n // rotation), so it's already world.\n if (storage.kind === 'pose') return (node as { pose: PolygonPath }).pose;\n // data.path: translate to the pose origin AND bake `pose.rotation`, so anchors render on\n // the rotated contour the renderer draws.\n return pathInWorld(storage.data.path, storage.pose) as PolygonPath;\n}\n\n/** `node` as the in-flight gestures in `previews` would leave it: a handle\n * previewing `id` contributes its pose and its data. */\nfunction previewedShape(\n id: string,\n node: EditableShape,\n previews: readonly GesturePreviewSource[],\n): EditableShape {\n let pose = node.pose;\n let data = node.data;\n let touched = false;\n for (const source of previews) {\n const previewIds = source.previewIds?.();\n if (!previewIds) continue;\n let owns = false;\n for (const pid of previewIds) {\n if (pid === id) { owns = true; break; }\n }\n if (!owns) continue;\n const p = source.previewPose?.(id);\n if (p != null && !touched) { pose = p; touched = true; }\n const d = source.previewData?.(id);\n if (d != null) { data = d; touched = true; }\n }\n return { pose, data };\n}\n\n/**\n * The editable polygon of node `id` as it is drawn right now: following any\n * in-flight gesture in `previews`, and in the world of the camera whose\n * `plane` this is — a path on a parallax plane is stored in the plane's.\n */\nexport function liveEditablePath(\n id: string,\n node: EditableShape | undefined | null,\n previews: readonly GesturePreviewSource[],\n plane: PlaneMap | null,\n): PolygonPath | null {\n if (!node) return null;\n return pathFromPlane(resolveEditablePathOf(previewedShape(id, node, previews)), plane);\n}\n"],"mappings":";;;;AAmBA,SAAgBA,EACd,GACyF;CACzF,IAAI,CAACC,GAAM,OAAO;CAElB,IADaA,EAAK,MACR,SAAS,WAAW,OAAO,EAAE,MAAM,OAAO;CACpD,IAAMC,IAAOD,EAAK;CAQlB,OAPIC,GAAM,QAASA,EAAK,KAA2B,SAAS,aAAaC,EAAWF,EAAK,IAAI,IACpF;EACL,MAAM;EACN,MAAMA,EAAK;EACL;CACR,IAEK;AACT;AAIA,SAAgBG,EACd,GACoB;CACpB,IAAI,CAACH,GAAM,OAAO;CAClB,IAAMI,IAAUL,EAAoBC,CAAI;CAOxC,OANKI,IAGDA,EAAQ,SAAS,SAAgBJ,EAA+B,OAG7DK,EAAYD,EAAQ,KAAK,MAAMA,EAAQ,IAAI,IAN7B;AAOvB;AAIA,SAASE,EACP,GACA,GACA,GACe;CACf,IAAIC,IAAOP,EAAK,MACZC,IAAOD,EAAK,MACZQ,IAAU;CACd,KAAK,IAAMC,KAAUC,GAAU;EAC7B,IAAMC,IAAaF,EAAO,aAAa;EACvC,IAAI,CAACE,GAAY;EACjB,IAAIC,IAAO;EACX,KAAK,IAAMC,KAAOF,GAChB,IAAIE,MAAQC,GAAI;GAAE,IAAO;GAAM;EAAO;EAExC,IAAI,CAACF,GAAM;EACX,IAAMG,IAAIN,EAAO,cAAcK,CAAE;EACjC,AAAIC,KAAK,QAAQ,CAACP,MAAW,IAAOO,GAAG,IAAU;EACjD,IAAMC,IAAIP,EAAO,cAAcK,CAAE;EACjC,AAAIE,KAAK,SAAQ,IAAOA,GAAG,IAAU;CACvC;CACA,OAAO;EAAE;EAAM;CAAK;AACtB;AAOA,SAAgBC,EACd,GACA,GACA,GACA,GACoB;CAEpB,OADKjB,IACEkB,EAAcf,EAAsBG,EAAeQ,GAAId,GAAMU,CAAQ,CAAC,GAAGS,CAAK,IADnE;AAEpB"}
1
+ {"version":3,"file":"editablePath.js","names":["classifyPathStorage","node","data","isRectPose","resolveEditablePathOf","storage","pathInWorld","storeEditablePath","worldPath","worldEditToStorage","previewedShape","pose","touched","source","previews","previewIds","owns","pid","id","p","d","liveEditablePath","pathFromPlane","plane"],"sources":["../../../src/features/paths/editablePath.ts"],"sourcesContent":["/**\n * A node's editable polygon. It lives in one of two places: the pose itself\n * (`pose.kind === 'polygon'`), or `data.path` with a rect pose, stored pose-local.\n */\nimport { pathInWorld, worldEditToStorage, type Path, type PolygonPath } from '@weasel-js/geom';\nimport type { GesturePreviewSource } from 'canvas/gestureBounds';\nimport { pathFromPlane } from 'canvas/planeClips';\nimport { isRectPose } from 'core/geometry/autoPoseDescriptor';\nimport type { RectPose } from 'core/scene/types';\nimport type { PlaneMap } from 'core/viewport/parallax';\n\n/** The two fields of a node the editable path is read from. */\nexport interface EditableShape {\n pose: unknown;\n data: unknown;\n}\n\n/** Resolve where a node's editable polygon lives. Returns null when the\n * node has neither a polygon pose nor a polygon on `data.path`. */\nexport function classifyPathStorage(\n node: EditableShape | undefined | null,\n): { kind: 'pose' } | { kind: 'data'; pose: RectPose; data: { path: PolygonPath } } | null {\n if (!node) return null;\n const pose = node.pose as { kind?: string } | undefined;\n if (pose?.kind === 'polygon') return { kind: 'pose' };\n const data = node.data as { path?: Path } | null;\n if (data?.path && (data.path as { kind?: string }).kind === 'polygon' && isRectPose(node.pose)) {\n return {\n kind: 'data',\n pose: node.pose,\n data: data as { path: PolygonPath },\n };\n }\n return null;\n}\n\n/** A node's editable polygon in world coords. Does NOT consult any preview state —\n * callers that want live state read in-flight handles themselves. */\nexport function resolveEditablePathOf(\n node: EditableShape | undefined | null,\n): PolygonPath | null {\n if (!node) return null;\n const storage = classifyPathStorage(node);\n if (!storage) return null;\n // Pose-as-polygon: the pose IS the polygon (polygon poses can't carry\n // rotation), so it's already world.\n if (storage.kind === 'pose') return (node as { pose: PolygonPath }).pose;\n // data.path: translate to the pose origin AND bake `pose.rotation`, so anchors render on\n // the rotated contour the renderer draws.\n return pathInWorld(storage.data.path, storage.pose) as PolygonPath;\n}\n\n/**\n * The pose and data that store `worldPath` where `node` stores its editable\n * polygon — the inverse of {@link resolveEditablePathOf}, so a `data.path`\n * node keeps its rotation. Null when `node` has no editable polygon.\n */\nexport function storeEditablePath(\n node: EditableShape | undefined | null,\n worldPath: PolygonPath,\n): EditableShape | null {\n const storage = classifyPathStorage(node);\n if (!storage) return null;\n if (storage.kind === 'pose') return { pose: worldPath, data: node!.data };\n const { pose, path } = worldEditToStorage(storage.pose, worldPath);\n return { pose, data: { ...storage.data, path } };\n}\n\n/** `node` as the in-flight gestures in `previews` would leave it: a handle\n * previewing `id` contributes its pose and its data. */\nfunction previewedShape(\n id: string,\n node: EditableShape,\n previews: readonly GesturePreviewSource[],\n): EditableShape {\n let pose = node.pose;\n let data = node.data;\n let touched = false;\n for (const source of previews) {\n const previewIds = source.previewIds?.();\n if (!previewIds) continue;\n let owns = false;\n for (const pid of previewIds) {\n if (pid === id) { owns = true; break; }\n }\n if (!owns) continue;\n const p = source.previewPose?.(id);\n if (p != null && !touched) { pose = p; touched = true; }\n const d = source.previewData?.(id);\n if (d != null) { data = d; touched = true; }\n }\n return { pose, data };\n}\n\n/**\n * The editable polygon of node `id` as it is drawn right now: following any\n * in-flight gesture in `previews`, and in the world of the camera whose\n * `plane` this is — a path on a parallax plane is stored in the plane's.\n */\nexport function liveEditablePath(\n id: string,\n node: EditableShape | undefined | null,\n previews: readonly GesturePreviewSource[],\n plane: PlaneMap | null,\n): PolygonPath | null {\n if (!node) return null;\n return pathFromPlane(resolveEditablePathOf(previewedShape(id, node, previews)), plane);\n}\n"],"mappings":";;;;AAmBA,SAAgBA,EACd,GACyF;CACzF,IAAI,CAACC,GAAM,OAAO;CAElB,IADaA,EAAK,MACR,SAAS,WAAW,OAAO,EAAE,MAAM,OAAO;CACpD,IAAMC,IAAOD,EAAK;CAQlB,OAPIC,GAAM,QAASA,EAAK,KAA2B,SAAS,aAAaC,EAAWF,EAAK,IAAI,IACpF;EACL,MAAM;EACN,MAAMA,EAAK;EACL;CACR,IAEK;AACT;AAIA,SAAgBG,EACd,GACoB;CACpB,IAAI,CAACH,GAAM,OAAO;CAClB,IAAMI,IAAUL,EAAoBC,CAAI;CAOxC,OANKI,IAGDA,EAAQ,SAAS,SAAgBJ,EAA+B,OAG7DK,EAAYD,EAAQ,KAAK,MAAMA,EAAQ,IAAI,IAN7B;AAOvB;AAOA,SAAgBE,EACd,GACA,GACsB;CACtB,IAAMF,IAAUL,EAAoBC,CAAI;CACxC,IAAI,CAACI,GAAS,OAAO;CACrB,IAAIA,EAAQ,SAAS,QAAQ,OAAO;EAAE,MAAMG;EAAW,MAAMP,EAAM;CAAK;CACxE,IAAM,EAAE,SAAM,YAASQ,EAAmBJ,EAAQ,MAAMG,CAAS;CACjE,OAAO;EAAE;EAAM,MAAM;GAAE,GAAGH,EAAQ;GAAM;EAAK;CAAE;AACjD;AAIA,SAASK,EACP,GACA,GACA,GACe;CACf,IAAIC,IAAOV,EAAK,MACZC,IAAOD,EAAK,MACZW,IAAU;CACd,KAAK,IAAMC,KAAUC,GAAU;EAC7B,IAAMC,IAAaF,EAAO,aAAa;EACvC,IAAI,CAACE,GAAY;EACjB,IAAIC,IAAO;EACX,KAAK,IAAMC,KAAOF,GAChB,IAAIE,MAAQC,GAAI;GAAE,IAAO;GAAM;EAAO;EAExC,IAAI,CAACF,GAAM;EACX,IAAMG,IAAIN,EAAO,cAAcK,CAAE;EACjC,AAAIC,KAAK,QAAQ,CAACP,MAAW,IAAOO,GAAG,IAAU;EACjD,IAAMC,IAAIP,EAAO,cAAcK,CAAE;EACjC,AAAIE,KAAK,SAAQ,IAAOA,GAAG,IAAU;CACvC;CACA,OAAO;EAAE;EAAM;CAAK;AACtB;AAOA,SAAgBC,EACd,GACA,GACA,GACA,GACoB;CAEpB,OADKpB,IACEqB,EAAclB,EAAsBM,EAAeQ,GAAIjB,GAAMa,CAAQ,CAAC,GAAGS,CAAK,IADnE;AAEpB"}
@@ -3,8 +3,8 @@ import { createPenPreviewLayer as t } from "./penPreviewLayer.js";
3
3
  import { createPathEditingOverlayLayer as n } from "./pathEditingOverlayLayer.js";
4
4
  import { createPathLayer as r } from "./pathLayer.js";
5
5
  import { pathOriginProjection as i } from "./originProjection.js";
6
- import { DEFAULT_FLATTEN_TOLERANCE as a, PATH_C as o, PATH_CMD_LENGTHS as s, PATH_L as c, PATH_M as l, PATH_Q as u, PATH_Z as d, PathBuilder as f, boundsOfPath as p, circlePath as m, composePath as h, countPathAnchors as g, decomposePath as _, ellipsePath as v, flattenCubic as y, flattenCubicWithArcLen as b, flattenQuadratic as x, flattenQuadraticWithArcLen as S, hasOpenSubpath as C, linePath as w, pathCommandCoordCount as T, pathContainsPoint as E, pathContainsPolygon as D, pathContainsRect as O, pathDistanceToPoint as k, pathFromD as A, pathInPoseFrame as j, pathInWorld as M, pathIntersectsPolygon as N, pathIntersectsRect as P, pathSignedArea as F, pathThroughPoseFrame as I, pathToAnchors as L, pointAlongPath as R, pointInPath as z, polygonFromPoints as B, polylineFromPoints as V, poseFrameInverseOf as H, poseFrameOf as U, poseLinearOf as W, poseRotationOf as G, rectMarkerPath as K, rectPath as q, regularPolygonPath as J, reversePath as Y, rotatePathAround as X, roundRectPath as Z, scalePathToBounds as Q, splitByClosure as $, splitSubpaths as ee, squarePath as te, starPath as ne, transformPath as re, translatePath as ie, unionBoundsPath as ae, worldEditToStorage as oe } from "@weasel-js/geom";
7
- import { strokeToPath as se } from "@weasel-js/stroke";
8
- import { pathDivide as ce, pathExclude as le, pathIntersect as ue, pathSubtract as de, pathUnion as fe, snipPathByPolyline as pe, splitPathByPolyline as me, splitPathBySegment as he, trianglesToPath as ge } from "@weasel-js/geom/booleans";
6
+ import { DEFAULT_FLATTEN_TOLERANCE as a, PATH_C as o, PATH_CMD_LENGTHS as s, PATH_L as c, PATH_M as l, PATH_Q as u, PATH_Z as d, PathBuilder as f, averageAnchorsAt as p, boundsOfPath as m, circlePath as h, composePath as g, countPathAnchors as _, decomposePath as v, ellipsePath as y, flattenCubic as b, flattenCubicWithArcLen as x, flattenQuadratic as S, flattenQuadraticWithArcLen as C, hasOpenSubpath as w, joinAnchorsAt as T, linePath as E, pathCommandCoordCount as D, pathContainsPoint as O, pathContainsPolygon as k, pathContainsRect as A, pathDistanceToPoint as j, pathFromD as M, pathInPoseFrame as N, pathInWorld as P, pathIntersectsPolygon as F, pathIntersectsRect as I, pathSignedArea as L, pathThroughPoseFrame as R, pathToAnchors as z, pointAlongPath as B, pointInPath as V, polygonFromPoints as H, polylineFromPoints as U, poseFrameInverseOf as W, poseFrameOf as G, poseLinearOf as K, poseRotationOf as q, rectMarkerPath as J, rectPath as Y, regularPolygonPath as X, reversePath as Z, rotatePathAround as Q, roundRectPath as $, scalePathToBounds as ee, simplifyPath as te, splitByClosure as ne, splitSubpaths as re, squarePath as ie, starPath as ae, transformPath as oe, translatePath as se, unionBoundsPath as ce, worldEditToStorage as le } from "@weasel-js/geom";
7
+ import { offsetPath as ue, strokeToPath as de } from "@weasel-js/stroke";
8
+ import { pathDivide as fe, pathExclude as pe, pathIntersect as me, pathSubtract as he, pathUnion as ge, snipPathByPolyline as _e, snipPathsAtCrossings as ve, splitPathByPolyline as ye, splitPathBySegment as be, trianglesToPath as xe } from "@weasel-js/geom/booleans";
9
9
  import "@weasel-js/geom/curves";
10
- export { a as DEFAULT_FLATTEN_TOLERANCE, o as PATH_C, s as PATH_CMD_LENGTHS, c as PATH_L, l as PATH_M, u as PATH_Q, d as PATH_Z, f as PathBuilder, p as boundsOfPath, m as circlePath, h as composePath, g as countPathAnchors, n as createPathEditingOverlayLayer, r as createPathLayer, t as createPenPreviewLayer, _ as decomposePath, v as ellipsePath, y as flattenCubic, b as flattenCubicWithArcLen, x as flattenQuadratic, S as flattenQuadraticWithArcLen, C as hasOpenSubpath, w as linePath, T as pathCommandCoordCount, E as pathContainsPoint, D as pathContainsPolygon, O as pathContainsRect, k as pathDistanceToPoint, ce as pathDivide, le as pathExclude, A as pathFromD, j as pathInPoseFrame, M as pathInWorld, ue as pathIntersect, N as pathIntersectsPolygon, P as pathIntersectsRect, i as pathOriginProjection, e as pathPoseDescriptor, F as pathSignedArea, de as pathSubtract, I as pathThroughPoseFrame, L as pathToAnchors, fe as pathUnion, R as pointAlongPath, z as pointInPath, B as polygonFromPoints, V as polylineFromPoints, H as poseFrameInverseOf, U as poseFrameOf, W as poseLinearOf, G as poseRotationOf, K as rectMarkerPath, q as rectPath, J as regularPolygonPath, Y as reversePath, X as rotatePathAround, Z as roundRectPath, Q as scalePathToBounds, pe as snipPathByPolyline, $ as splitByClosure, me as splitPathByPolyline, he as splitPathBySegment, ee as splitSubpaths, te as squarePath, ne as starPath, se as strokeToPath, re as transformPath, ie as translatePath, ge as trianglesToPath, ae as unionBoundsPath, oe as worldEditToStorage };
10
+ export { a as DEFAULT_FLATTEN_TOLERANCE, o as PATH_C, s as PATH_CMD_LENGTHS, c as PATH_L, l as PATH_M, u as PATH_Q, d as PATH_Z, f as PathBuilder, p as averageAnchorsAt, m as boundsOfPath, h as circlePath, g as composePath, _ as countPathAnchors, n as createPathEditingOverlayLayer, r as createPathLayer, t as createPenPreviewLayer, v as decomposePath, y as ellipsePath, b as flattenCubic, x as flattenCubicWithArcLen, S as flattenQuadratic, C as flattenQuadraticWithArcLen, w as hasOpenSubpath, T as joinAnchorsAt, E as linePath, ue as offsetPath, D as pathCommandCoordCount, O as pathContainsPoint, k as pathContainsPolygon, A as pathContainsRect, j as pathDistanceToPoint, fe as pathDivide, pe as pathExclude, M as pathFromD, N as pathInPoseFrame, P as pathInWorld, me as pathIntersect, F as pathIntersectsPolygon, I as pathIntersectsRect, i as pathOriginProjection, e as pathPoseDescriptor, L as pathSignedArea, he as pathSubtract, R as pathThroughPoseFrame, z as pathToAnchors, ge as pathUnion, B as pointAlongPath, V as pointInPath, H as polygonFromPoints, U as polylineFromPoints, W as poseFrameInverseOf, G as poseFrameOf, K as poseLinearOf, q as poseRotationOf, J as rectMarkerPath, Y as rectPath, X as regularPolygonPath, Z as reversePath, Q as rotatePathAround, $ as roundRectPath, ee as scalePathToBounds, te as simplifyPath, _e as snipPathByPolyline, ve as snipPathsAtCrossings, ne as splitByClosure, ye as splitPathByPolyline, be as splitPathBySegment, re as splitSubpaths, ie as squarePath, ae as starPath, de as strokeToPath, oe as transformPath, se as translatePath, xe as trianglesToPath, ce as unionBoundsPath, le as worldEditToStorage };
@@ -0,0 +1,89 @@
1
+ import { boundsOfPath as e, boxToBox as t, poseFrameOf as n, regularPolygonPath as r, starPath as i, transformPath as a, worldEditToStorage as o } from "@weasel-js/geom";
2
+ //#region src/features/paths/shapeGenerator.ts
3
+ var s = {
4
+ min: 3,
5
+ max: 32
6
+ }, c = {
7
+ min: .01,
8
+ max: 1
9
+ }, l = (e) => typeof e == "number" && Number.isFinite(e);
10
+ function u(e) {
11
+ if (typeof e != "object" || !e) return !1;
12
+ let t = e;
13
+ return l(t.rotation) ? t.kind === "polygon" ? l(t.sides) : t.kind === "star" && l(t.points) && l(t.innerRadiusRatio) : !1;
14
+ }
15
+ function d(e) {
16
+ let t = e?.generator;
17
+ return u(t) ? t : null;
18
+ }
19
+ var f = (e, t, n) => Math.min(n, Math.max(t, e)), p = (e) => f(Math.round(e), s.min, s.max);
20
+ function m(e) {
21
+ return e.kind === "polygon" ? {
22
+ kind: "polygon",
23
+ sides: p(e.sides),
24
+ rotation: e.rotation
25
+ } : {
26
+ kind: "star",
27
+ points: p(e.points),
28
+ innerRadiusRatio: f(e.innerRadiusRatio, c.min, c.max),
29
+ rotation: e.rotation
30
+ };
31
+ }
32
+ function h(e, t = {
33
+ x: 0,
34
+ y: 0
35
+ }, n = 1) {
36
+ let a = m(e);
37
+ return a.kind === "polygon" ? r(t, n, a.sides, a.rotation) : i(t, n, a.points, n * a.innerRadiusRatio, a.rotation);
38
+ }
39
+ function g(e) {
40
+ if (typeof e != "object" || !e || !("generator" in e)) return e;
41
+ let { generator: t, ...n } = e;
42
+ return n;
43
+ }
44
+ function _(n, r) {
45
+ let i = n;
46
+ if (i?.kind !== "polygon") return !1;
47
+ let a = h(r);
48
+ if (i.coords.length !== a.coords.length) return !1;
49
+ let o = e(a), s = e(i), c = t(o.x, o.y, o.width, o.height, s.x, s.y, s.width, s.height), l = 1e-4 * Math.max(1, Math.abs(s.x) + s.width, Math.abs(s.y) + s.height);
50
+ for (let e = 0; e + 1 < a.coords.length; e += 2) {
51
+ let t = c[0] * a.coords[e] + c[4], n = c[3] * a.coords[e + 1] + c[5];
52
+ if (Math.abs(t - i.coords[e]) > l || Math.abs(n - i.coords[e + 1]) > l) return !1;
53
+ }
54
+ return !0;
55
+ }
56
+ function v(r, i) {
57
+ let s = d(r.data);
58
+ if (!s || !_(r.data.path, s)) return null;
59
+ let c = m(i), l = e(h(s)), { pose: u } = r, f = t(l.x, l.y, l.width, l.height, u.x, u.y, u.width, u.height), p = a(h(c), f), g = n(u), v = g ? a(p, g) : p, y = o(u, v);
60
+ return {
61
+ pose: y.pose,
62
+ data: {
63
+ ...r.data,
64
+ path: y.path,
65
+ generator: c
66
+ }
67
+ };
68
+ }
69
+ function y(e, t) {
70
+ let n = e.data;
71
+ if (typeof n != "object" || !n || !("generator" in n)) return e;
72
+ let r = n.generator, i = d(t.data);
73
+ if (i !== null && JSON.stringify(r) === JSON.stringify(i)) return e;
74
+ let a = i !== null && u(r) && r.kind === i.kind ? v({
75
+ pose: e.pose,
76
+ data: {
77
+ ...n,
78
+ generator: i
79
+ }
80
+ }, r) : null;
81
+ return {
82
+ ...e,
83
+ ...a ?? { data: g(e.data) }
84
+ };
85
+ }
86
+ //#endregion
87
+ export { s as SHAPE_COUNT_RANGE, c as STAR_RATIO_RANGE, m as clampShapeGenerator, u as isShapeGenerator, v as regenerateShape, y as settleShapeGenerator, d as shapeGeneratorOf, h as shapeGeneratorPath, g as withoutShapeGenerator };
88
+
89
+ //# sourceMappingURL=shapeGenerator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shapeGenerator.js","names":["SHAPE_COUNT_RANGE","STAR_RATIO_RANGE","finite","v","isShapeGenerator","g","shapeGeneratorOf","data","clamp","hi","lo","count","clampShapeGenerator","shapeGeneratorPath","c","regularPolygonPath","center","radius","starPath","withoutShapeGenerator","_dropped","rest","pathMatches","p","path","unit","u","boundsOfPath","b","m","boxToBox","tol","i","x","y","regenerateShape","held","node","next","was","toLocal","pose","local","transformPath","frame","poseFrameOf","world","stored","worldEditToStorage","settleShapeGenerator","raw","prev","regenerated"],"sources":["../../../src/features/paths/shapeGenerator.ts"],"sourcesContent":["/**\n * The parameters a polygon or star was drawn with, kept on the node beside the\n * geometry they made (`data.generator` beside `data.path`), so the shape stays\n * editable as a polygon or a star after it is drawn.\n *\n * `data.path` stays the geometry every reader uses; the generator only says how\n * to make it again. Editing the path itself turns the shape into plain\n * geometry: the generator is dropped, since it could no longer reproduce what\n * is drawn. A generator whose path no longer matches it (a writer that changed\n * the path without dropping it) is treated the same way.\n */\nimport {\n boundsOfPath,\n boxToBox,\n poseFrameOf,\n regularPolygonPath,\n starPath,\n transformPath,\n worldEditToStorage,\n type PathInWorldPose,\n type PolygonPath,\n} from '@weasel-js/geom';\n\n/** A regular polygon's parameters. `rotation` is the angle of its first\n * vertex, in radians, inside the pose's own rotation. */\nexport interface PolygonGenerator {\n kind: 'polygon';\n sides: number;\n rotation: number;\n}\n\n/** A star's parameters. `innerRadiusRatio` is the inner radius over the outer\n * one; `rotation` is the angle of its first point, in radians, inside the\n * pose's own rotation. */\nexport interface StarGenerator {\n kind: 'star';\n points: number;\n innerRadiusRatio: number;\n rotation: number;\n}\n\n/** The parameters a node's `data.path` was generated from. */\nexport type ShapeGenerator = PolygonGenerator | StarGenerator;\n\n/** The range of a polygon's side count and a star's point count. */\nexport const SHAPE_COUNT_RANGE = { min: 3, max: 32 } as const;\n\n/** The range of a star's `innerRadiusRatio`. */\nexport const STAR_RATIO_RANGE = { min: 0.01, max: 1 } as const;\n\nconst finite = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);\n\n/** Whether `v` is a well-formed {@link ShapeGenerator}. */\nexport function isShapeGenerator(v: unknown): v is ShapeGenerator {\n if (v === null || typeof v !== 'object') return false;\n const g = v as Record<string, unknown>;\n if (!finite(g.rotation)) return false;\n if (g.kind === 'polygon') return finite(g.sides);\n if (g.kind === 'star') return finite(g.points) && finite(g.innerRadiusRatio);\n return false;\n}\n\n/** The generator a node's `data` holds, or `null`. */\nexport function shapeGeneratorOf(data: unknown): ShapeGenerator | null {\n const g = (data as { generator?: unknown } | null | undefined)?.generator;\n return isShapeGenerator(g) ? g : null;\n}\n\nconst clamp = (v: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, v));\nconst count = (v: number): number => clamp(Math.round(v), SHAPE_COUNT_RANGE.min, SHAPE_COUNT_RANGE.max);\n\n/** `g` with its counts rounded and every field held in range. */\nexport function clampShapeGenerator(g: ShapeGenerator): ShapeGenerator {\n if (g.kind === 'polygon') return { kind: 'polygon', sides: count(g.sides), rotation: g.rotation };\n return {\n kind: 'star',\n points: count(g.points),\n innerRadiusRatio: clamp(g.innerRadiusRatio, STAR_RATIO_RANGE.min, STAR_RATIO_RANGE.max),\n rotation: g.rotation,\n };\n}\n\n/** The shape `g` makes, on a circle of `radius` around `center`. */\nexport function shapeGeneratorPath(\n g: ShapeGenerator,\n center: { x: number; y: number } = { x: 0, y: 0 },\n radius = 1,\n): PolygonPath {\n const c = clampShapeGenerator(g);\n return c.kind === 'polygon'\n ? regularPolygonPath(center, radius, c.sides, c.rotation)\n : starPath(center, radius, c.points, radius * c.innerRadiusRatio, c.rotation);\n}\n\n/** `data` without its generator: the shape as plain geometry. */\nexport function withoutShapeGenerator<T>(data: T): T {\n if (data === null || typeof data !== 'object' || !('generator' in data)) return data;\n const { generator: _dropped, ...rest } = data as Record<string, unknown>;\n void _dropped;\n return rest as T;\n}\n\n/** Whether `path` is what `g` makes, up to the box-to-box map storage applies. */\nfunction pathMatches(path: unknown, g: ShapeGenerator): boolean {\n const p = path as PolygonPath | undefined;\n if (p?.kind !== 'polygon') return false;\n const unit = shapeGeneratorPath(g);\n if (p.coords.length !== unit.coords.length) return false;\n const u = boundsOfPath(unit);\n const b = boundsOfPath(p);\n const m = boxToBox(u.x, u.y, u.width, u.height, b.x, b.y, b.width, b.height);\n // Paths store float32 coordinates, so the slack scales with how far they are from the origin.\n const tol = 1e-4 * Math.max(1, Math.abs(b.x) + b.width, Math.abs(b.y) + b.height);\n for (let i = 0; i + 1 < unit.coords.length; i += 2) {\n const x = m[0] * unit.coords[i] + m[4];\n const y = m[3] * unit.coords[i + 1] + m[5];\n if (Math.abs(x - p.coords[i]) > tol || Math.abs(y - p.coords[i + 1]) > tol) return false;\n }\n return true;\n}\n\n/** A node's two halves, as a regeneration reads and writes them. */\nexport interface GeneratedShape<P extends PathInWorldPose = PathInWorldPose> {\n pose: P;\n data: unknown;\n}\n\n/**\n * `node` made again from `next`, in place: the new shape takes the box the old\n * one's circle had, under the same rotation and transform, so a star given\n * more points keeps its center and its size. Returns `null` when the node holds\n * no generator, or its path no longer matches the one it holds.\n */\nexport function regenerateShape<P extends PathInWorldPose>(\n node: GeneratedShape<P>,\n next: ShapeGenerator,\n): GeneratedShape<P> | null {\n const held = shapeGeneratorOf(node.data);\n if (!held || !pathMatches((node.data as { path?: unknown }).path, held)) return null;\n const g = clampShapeGenerator(next);\n const was = boundsOfPath(shapeGeneratorPath(held));\n const { pose } = node;\n // Where the unit circle of the held generator lands in the pose's local frame.\n const toLocal = boxToBox(was.x, was.y, was.width, was.height, pose.x, pose.y, pose.width, pose.height);\n const local = transformPath(shapeGeneratorPath(g), toLocal) as PolygonPath;\n const frame = poseFrameOf(pose);\n const world = frame ? (transformPath(local, frame) as PolygonPath) : local;\n const stored = worldEditToStorage(pose, world);\n return { pose: stored.pose, data: { ...(node.data as object), path: stored.path, generator: g } };\n}\n\n/**\n * What a write that left a node as `next`, from `prev`, stores: when the write\n * changed the generator, the shape made again from it. A generator written\n * onto a node that held none, or of another kind, or onto one whose path no\n * longer matches, is dropped, leaving the shape as plain geometry. Any other\n * write is stored as written. Fits `NodePropertiesEntry.settle`.\n */\nexport function settleShapeGenerator<N extends { pose: unknown; data: unknown }>(next: N, prev: N): N {\n const data = next.data as Record<string, unknown> | null;\n if (data === null || typeof data !== 'object' || !('generator' in data)) return next;\n const raw = data.generator;\n const held = shapeGeneratorOf(prev.data);\n if (held !== null && JSON.stringify(raw) === JSON.stringify(held)) return next;\n const regenerated = held !== null && isShapeGenerator(raw) && raw.kind === held.kind\n ? regenerateShape({ pose: next.pose as PathInWorldPose, data: { ...data, generator: held } }, raw)\n : null;\n return { ...next, ...(regenerated ?? { data: withoutShapeGenerator(next.data) }) };\n}\n"],"mappings":";;AA6CA,IAAaA,IAAoB;CAAE,KAAK;CAAG,KAAK;AAAG,GAGtCC,IAAmB;CAAE,KAAK;CAAM,KAAK;AAAE,GAE9CC,KAAU,MAA4B,OAAOC,KAAM,YAAY,OAAO,SAASA,CAAC;AAGtF,SAAgBC,EAAiB,GAAiC;CAChE,IAAkB,OAAOD,KAAM,aAA3BA,GAAqC,OAAO;CAChD,IAAME,IAAIF;CAIV,OAHKD,EAAOG,EAAE,QAAQ,IAClBA,EAAE,SAAS,YAAkBH,EAAOG,EAAE,KAAK,IAC3CA,EAAE,SAAS,UAAeH,EAAOG,EAAE,MAAM,KAAKH,EAAOG,EAAE,gBAAgB,IAF3C;AAIlC;AAGA,SAAgBC,EAAiB,GAAsC;CACrE,IAAMD,IAAKE,GAAqD;CAChE,OAAOH,EAAiBC,CAAC,IAAIA,IAAI;AACnC;AAEA,IAAMG,KAAS,GAAW,GAAY,MAAuB,KAAK,IAAIC,GAAI,KAAK,IAAIC,GAAIP,CAAC,CAAC,GACnFQ,KAAS,MAAsBH,EAAM,KAAK,MAAML,CAAC,GAAGH,EAAkB,KAAKA,EAAkB,GAAG;AAGtG,SAAgBY,EAAoB,GAAmC;CAErE,OADIP,EAAE,SAAS,YAAkB;EAAE,MAAM;EAAW,OAAOM,EAAMN,EAAE,KAAK;EAAG,UAAUA,EAAE;CAAS,IACzF;EACL,MAAM;EACN,QAAQM,EAAMN,EAAE,MAAM;EACtB,kBAAkBG,EAAMH,EAAE,kBAAkBJ,EAAiB,KAAKA,EAAiB,GAAG;EACtF,UAAUI,EAAE;CACd;AACF;AAGA,SAAgBQ,EACd,GACA,IAAmC;CAAE,GAAG;CAAG,GAAG;AAAE,GAChD,IAAS,GACI;CACb,IAAMC,IAAIF,EAAoBP,CAAC;CAC/B,OAAOS,EAAE,SAAS,YACdC,EAAmBC,GAAQC,GAAQH,EAAE,OAAOA,EAAE,QAAQ,IACtDI,EAASF,GAAQC,GAAQH,EAAE,QAAQG,IAASH,EAAE,kBAAkBA,EAAE,QAAQ;AAChF;AAGA,SAAgBK,EAAyB,GAAY;CACnD,IAAqB,OAAOZ,KAAS,aAAjCA,KAA6C,EAAE,eAAeA,IAAO,OAAOA;CAChF,IAAM,EAAE,WAAWa,GAAU,GAAGC,MAASd;CAEzC,OAAOc;AACT;AAGA,SAASC,EAAY,GAAe,GAA4B;CAC9D,IAAMC,IAAIC;CACV,IAAID,GAAG,SAAS,WAAW,OAAO;CAClC,IAAME,IAAOZ,EAAmBR,CAAC;CACjC,IAAIkB,EAAE,OAAO,WAAWE,EAAK,OAAO,QAAQ,OAAO;CACnD,IAAMC,IAAIC,EAAaF,CAAI,GACrBG,IAAID,EAAaJ,CAAC,GAClBM,IAAIC,EAASJ,EAAE,GAAGA,EAAE,GAAGA,EAAE,OAAOA,EAAE,QAAQE,EAAE,GAAGA,EAAE,GAAGA,EAAE,OAAOA,EAAE,MAAM,GAErEG,IAAM,OAAO,KAAK,IAAI,GAAG,KAAK,IAAIH,EAAE,CAAC,IAAIA,EAAE,OAAO,KAAK,IAAIA,EAAE,CAAC,IAAIA,EAAE,MAAM;CAChF,KAAK,IAAII,IAAI,GAAGA,IAAI,IAAIP,EAAK,OAAO,QAAQ,KAAK,GAAG;EAClD,IAAMQ,IAAIJ,EAAE,KAAKJ,EAAK,OAAOO,KAAKH,EAAE,IAC9BK,IAAIL,EAAE,KAAKJ,EAAK,OAAOO,IAAI,KAAKH,EAAE;EACxC,IAAI,KAAK,IAAII,IAAIV,EAAE,OAAOS,EAAE,IAAID,KAAO,KAAK,IAAIG,IAAIX,EAAE,OAAOS,IAAI,EAAE,IAAID,GAAK,OAAO;CACrF;CACA,OAAO;AACT;AAcA,SAAgBI,EACd,GACA,GAC0B;CAC1B,IAAMC,IAAO9B,EAAiB+B,EAAK,IAAI;CACvC,IAAI,CAACD,KAAQ,CAACd,EAAae,EAAK,KAA4B,MAAMD,CAAI,GAAG,OAAO;CAChF,IAAM/B,IAAIO,EAAoB0B,CAAI,GAC5BC,IAAMZ,EAAad,EAAmBuB,CAAI,CAAC,GAC3C,EAAE,YAASC,GAEXG,IAAUV,EAASS,EAAI,GAAGA,EAAI,GAAGA,EAAI,OAAOA,EAAI,QAAQE,EAAK,GAAGA,EAAK,GAAGA,EAAK,OAAOA,EAAK,MAAM,GAC/FC,IAAQC,EAAc9B,EAAmBR,CAAC,GAAGmC,CAAO,GACpDI,IAAQC,EAAYJ,CAAI,GACxBK,IAAQF,IAASD,EAAcD,GAAOE,CAAK,IAAoBF,GAC/DK,IAASC,EAAmBP,GAAMK,CAAK;CAC7C,OAAO;EAAE,MAAMC,EAAO;EAAM,MAAM;GAAE,GAAIV,EAAK;GAAiB,MAAMU,EAAO;GAAM,WAAW1C;EAAE;CAAE;AAClG;AASA,SAAgB4C,EAAiE,GAAS,GAAY;CACpG,IAAM1C,IAAO+B,EAAK;CAClB,IAAqB,OAAO/B,KAAS,aAAjCA,KAA6C,EAAE,eAAeA,IAAO,OAAO+B;CAChF,IAAMY,IAAM3C,EAAK,WACX6B,IAAO9B,EAAiB6C,EAAK,IAAI;CACvC,IAAIf,MAAS,QAAQ,KAAK,UAAUc,CAAG,MAAM,KAAK,UAAUd,CAAI,GAAG,OAAOE;CAC1E,IAAMc,IAAchB,MAAS,QAAQhC,EAAiB8C,CAAG,KAAKA,EAAI,SAASd,EAAK,OAC5ED,EAAgB;EAAE,MAAMG,EAAK;EAAyB,MAAM;GAAE,GAAG/B;GAAM,WAAW6B;EAAK;CAAE,GAAGc,CAAG,IAC/F;CACJ,OAAO;EAAE,GAAGZ;EAAM,GAAIc,KAAe,EAAE,MAAMjC,EAAsBmB,EAAK,IAAI,EAAE;CAAG;AACnF"}
@@ -1,30 +1,30 @@
1
1
  import { rectFromPlane as e } from "../../core/viewport/parallax.js";
2
- import { createPathEditState as t } from "./pathEditState.js";
3
- import { liveEditablePath as n } from "./editablePath.js";
2
+ import { liveEditablePath as t } from "./editablePath.js";
3
+ import { createPathEditState as n } from "./pathEditState.js";
4
4
  import { createPathEditingOverlayLayer as r } from "./pathEditingOverlayLayer.js";
5
5
  import { useState as i } from "react";
6
6
  import { useLatest as a } from "@weasel-js/react";
7
7
  //#region src/features/paths/usePathEdit.ts
8
8
  function o(e) {
9
- let n = a(e), [r] = i(() => t({
10
- host: () => n.current.host,
11
- isAllowed: () => n.current.isAllowed?.() ?? !0,
12
- isSelectedNode: (e) => n.current.isSelectedNode(e)
9
+ let t = a(e), [r] = i(() => n({
10
+ host: () => t.current.host,
11
+ isAllowed: () => t.current.isAllowed?.() ?? !0,
12
+ isSelectedNode: (e) => t.current.isSelectedNode(e)
13
13
  }));
14
14
  return r;
15
15
  }
16
- function s(t, o) {
16
+ function s(n, o) {
17
17
  let s = a(o), [c] = i(() => ({
18
18
  ...r({
19
- getEditingId: () => t.getEditingId() || null,
20
- getPose: (e, t, r) => n(e, s.current.nodeOf(e), t, s.current.planeOfNode(e, r)),
21
- getSelectedAnchors: t.getSelectedAnchors,
22
- getMarquee: (n) => {
23
- let r = t.getMarquee(), i = r ? s.current.planeOfNode(t.getEditingId(), n) : null;
19
+ getEditingId: () => n.getEditingId() || null,
20
+ getPose: (e, n, r) => t(e, s.current.nodeOf(e), n, s.current.planeOfNode(e, r)),
21
+ getSelectedAnchors: n.getSelectedAnchors,
22
+ getMarquee: (t) => {
23
+ let r = n.getMarquee(), i = r ? s.current.planeOfNode(n.getEditingId(), t) : null;
24
24
  return r && i ? e(i, r) : r;
25
25
  }
26
26
  }),
27
- subscribe: t.subscribe
27
+ subscribe: n.subscribe
28
28
  }));
29
29
  return c;
30
30
  }
@@ -2,59 +2,59 @@ import { useCallback as e, useEffect as t, useMemo as n, useRef as r } from "rea
2
2
  import { useLatest as i, useVisibleRaf as a } from "@weasel-js/react";
3
3
  //#region src/features/poseRun/usePoseRun.ts
4
4
  function o(o) {
5
- let s = i(o), c = r(/* @__PURE__ */ new Map()), l = r(null), u = r(!1), d = r(0), f = r(!0), p = a(() => {
6
- v();
7
- }, {
8
- requestFrame: o.requestFrame,
9
- cancelFrame: o.cancelFrame
10
- }), m = e(() => {
5
+ let s = i(o), c = r(/* @__PURE__ */ new Map()), l = r(null), u = r(!1), d = r(0), f = r(!0), p = e(() => {
11
6
  let { scene: e } = s.current;
12
7
  return l.current?.scene !== e && (l.current?.hold.drop(), l.current = {
13
8
  scene: e,
14
9
  hold: e.overrides.hold()
15
10
  }), l.current.hold;
16
- }, [s]), h = e(() => {
11
+ }, [s]), m = e(() => {
17
12
  l.current?.hold.drop(), c.current.clear();
18
- }, []), g = () => {
13
+ }, []), h = () => {
19
14
  let e = s.current.scene, t = 0;
20
15
  try {
21
16
  c.current.size > 0 && e.batch(s.current.label ?? "Layout", () => {
22
17
  for (let [n, r] of c.current) e.get(n) === void 0 || e.isLocked(n) || (e.setPose(n, r), t++);
23
18
  });
24
19
  } finally {
25
- h();
20
+ m();
26
21
  }
27
22
  s.current.onCommit?.(t);
28
- }, _ = () => {
29
- u.current = !1, p.cancel();
30
- }, v = () => {
23
+ }, g = (e) => {
24
+ u.current = !1, e.cancel();
25
+ }, _ = (e) => {
31
26
  if (!f.current || !u.current) return;
32
- let e = m(), t = e.others(), { poses: n, done: r } = s.current.step({
33
- pinned: t,
27
+ let t = p(), n = t.others(), { poses: r, done: i } = s.current.step({
28
+ pinned: n,
34
29
  frame: d.current
35
30
  });
36
31
  d.current++, c.current.clear();
37
- for (let [e, r] of n) t.has(e) || c.current.set(e, r);
38
- if (e.set(c.current), r) {
39
- _(), g();
32
+ for (let [e, t] of r) n.has(e) || c.current.set(e, t);
33
+ if (t.set(c.current), i) {
34
+ g(e), h();
40
35
  return;
41
36
  }
42
- p.request();
43
- };
37
+ e.request();
38
+ }, v = a((e, t) => {
39
+ _(t);
40
+ }, {
41
+ requestFrame: o.requestFrame,
42
+ cancelFrame: o.cancelFrame
43
+ });
44
44
  return t(() => (f.current = !0, () => {
45
- f.current = !1, p.cancel(), h();
46
- }), [p, h]), n(() => ({
45
+ f.current = !1, v.cancel(), m();
46
+ }), [v, m]), n(() => ({
47
47
  start() {
48
- !u.current && f.current && (u.current = !0, d.current = 0, p.request());
48
+ !u.current && f.current && (u.current = !0, d.current = 0, v.request());
49
49
  },
50
50
  stop() {
51
- u.current && (_(), g());
51
+ u.current && (g(v), h());
52
52
  },
53
53
  cancel() {
54
- _(), h();
54
+ g(v), m();
55
55
  },
56
56
  isRunning: () => u.current
57
- }), [p]);
57
+ }), [v]);
58
58
  }
59
59
  //#endregion
60
60
  export { o as usePoseRun };
@@ -1 +1 @@
1
- {"version":3,"file":"usePoseRun.js","names":["usePoseRun","optsRef","useLatest","opts","previews","useRef","held","running","frame","mounted","loop","useVisibleRaf","holdNow","useCallback","scene","release","commit","moved","id","pose","halt","tick","hold","pinned","poses","done","useEffect","useMemo"],"sources":["../../../src/features/poseRun/usePoseRun.ts"],"sourcesContent":["/**\n * A pose run: poses published per frame, committed once.\n *\n * The frames go to `scene.overrides` — the ephemeral table a drag publishes\n * to — so an edge follows a node the run is moving, and the whole run lands as\n * one undo entry when it settles.\n *\n * **A node another gesture owns is pinned, not driven.** The run publishes\n * through a hold of its own, so an override it did not put there belongs to\n * somebody else — a drag, a resize. It never writes that id, never commits it,\n * and hands it to `step` as pinned so the producer can hold its neighbors\n * against it.\n */\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { useLatest, useVisibleRaf } from '@weasel-js/react';\nimport type { NodeId, PoseHold, Scene } from '../../core/scene/types';\n\n/** What a producer is told before it is asked for a frame. */\nexport interface PoseRunCtx<TPose> {\n /** Nodes another gesture is moving, and the pose each is drawn at. The run\n * will not write these, so a producer holds them fixed. */\n pinned: ReadonlyMap<NodeId, TPose>;\n /** Frames this run has produced, starting at 0. */\n frame: number;\n}\n\n/** One frame of a run. */\nexport interface PoseRunStep<TPose> {\n /** Where the nodes it drives are this frame — the whole picture, not a\n * delta. An id the frame omits stops being published, which is how a node\n * leaves the run's hands. */\n poses: Iterable<readonly [NodeId, TPose]>;\n /** Whether the run is finished. The frame is published either way, and a\n * finished run commits. */\n done: boolean;\n}\n\n/** Options for `usePoseRun`. Read fresh each frame, so they may change mid-run. */\nexport interface UsePoseRunOptions<TPose> {\n scene: Scene<unknown, string, TPose>;\n /** Called once per frame while running, for that frame's poses. */\n step: (ctx: PoseRunCtx<TPose>) => PoseRunStep<TPose>;\n /** The undo entry's name. Default `'Layout'`. */\n label?: string;\n /** How many nodes the commit wrote. Fires after the ops land. */\n onCommit?: (moved: number) => void;\n /** Clock injection, for tests. Defaults to `requestAnimationFrame`. */\n requestFrame?: (cb: (t: number) => void) => number;\n cancelFrame?: (handle: number) => void;\n}\n\n/** Controls for a pose run. Stable for the life of the component. */\nexport interface PoseRun {\n /** Begin a new run, counting frames from 0. Idempotent while running. */\n start(): void;\n /** Finish now: commit what is published, then drop the overrides. */\n stop(): void;\n /** Abandon: drop the overrides and write nothing, like a canceled drag. */\n cancel(): void;\n isRunning(): boolean;\n}\n\n/**\n * Drive node poses frame by frame through `scene.overrides`, committing them as\n * one undo entry when `step` reports done or `stop` is called. Idle until\n * `start`; unmounting abandons the run without writing.\n */\nexport function usePoseRun<TPose>(opts: UsePoseRunOptions<TPose>): PoseRun {\n const optsRef = useLatest(opts);\n\n /** This frame's poses. */\n const previews = useRef(new Map<NodeId, TPose>());\n /** The hold the run publishes through, and the scene it was taken from. */\n const held = useRef<{ scene: Scene<unknown, string, TPose>; hold: PoseHold<TPose> } | null>(null);\n const running = useRef(false);\n const frame = useRef(0);\n const mounted = useRef(true);\n\n // The gate defaults an absent clock to `requestAnimationFrame` itself, so\n // these pass straight through rather than being defaulted twice.\n const loop = useVisibleRaf(() => { tick(); }, {\n requestFrame: opts.requestFrame,\n cancelFrame: opts.cancelFrame,\n });\n\n /** The hold on the scene the options name now. A run handed a different\n * scene mid-flight lets go of the old one's nodes. */\n const holdNow = useCallback((): PoseHold<TPose> => {\n const { scene } = optsRef.current;\n if (held.current?.scene !== scene) {\n held.current?.hold.drop();\n held.current = { scene, hold: scene.overrides.hold() };\n }\n return held.current.hold;\n }, [optsRef]);\n\n const release = useCallback((): void => {\n held.current?.hold.drop();\n previews.current.clear();\n }, []);\n\n const commit = (): void => {\n const scene = optsRef.current.scene;\n let moved = 0;\n try {\n if (previews.current.size > 0) {\n scene.batch(optsRef.current.label ?? 'Layout', () => {\n for (const [id, pose] of previews.current) {\n if (scene.get(id) === undefined || scene.isLocked(id)) continue;\n scene.setPose(id, pose);\n moved++;\n }\n });\n }\n } finally {\n release();\n }\n optsRef.current.onCommit?.(moved);\n };\n\n const halt = (): void => {\n running.current = false;\n loop.cancel();\n };\n\n const tick = (): void => {\n if (!mounted.current || !running.current) return;\n const hold = holdNow();\n const pinned = hold.others();\n const { poses, done } = optsRef.current.step({ pinned, frame: frame.current });\n frame.current++;\n previews.current.clear();\n for (const [id, pose] of poses) {\n if (pinned.has(id)) continue;\n previews.current.set(id, pose);\n }\n hold.set(previews.current);\n if (done) {\n halt();\n commit();\n return;\n }\n loop.request();\n };\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n loop.cancel();\n // Teardown is not a decision: an unmounted run abandons its frames\n // rather than writing them.\n release();\n };\n }, [loop, release]);\n\n return useMemo<PoseRun>(() => ({\n start() {\n if (running.current || !mounted.current) return;\n running.current = true;\n frame.current = 0;\n loop.request();\n },\n stop() {\n if (!running.current) return;\n halt();\n commit();\n },\n cancel() {\n halt();\n release();\n },\n isRunning: () => running.current,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }), [loop]);\n}\n"],"mappings":";;;AAmEA,SAAgBA,EAAkB,GAAyC;CACzE,IAAMC,IAAUC,EAAUC,CAAI,GAGxBC,IAAWC,kBAAO,IAAI,IAAmB,CAAC,GAE1CC,IAAOD,EAA+E,IAAI,GAC1FE,IAAUF,EAAO,EAAK,GACtBG,IAAQH,EAAO,CAAC,GAChBI,IAAUJ,EAAO,EAAI,GAIrBK,IAAOC,QAAoB;EAAE,EAAK;CAAG,GAAG;EAC5C,cAAcR,EAAK;EACnB,aAAaA,EAAK;CACpB,CAAC,GAIKS,IAAUC,QAAmC;EACjD,IAAM,EAAE,aAAUZ,EAAQ;EAK1B,OAJIK,EAAK,SAAS,UAAUQ,MAC1BR,EAAK,SAAS,KAAK,KAAK,GACxB,EAAK,UAAU;GAAE;GAAO,MAAMQ,EAAM,UAAU,KAAK;EAAE,IAEhDR,EAAK,QAAQ;CACtB,GAAG,CAACL,CAAO,CAAC,GAENc,IAAUF,QAAwB;EAEtC,AADAP,EAAK,SAAS,KAAK,KAAK,GACxBF,EAAS,QAAQ,MAAM;CACzB,GAAG,CAAC,CAAC,GAECY,UAAqB;EACzB,IAAMF,IAAQb,EAAQ,QAAQ,OAC1BgB,IAAQ;EACZ,IAAI;GACF,AAAIb,EAAS,QAAQ,OAAO,KAC1BU,EAAM,MAAMb,EAAQ,QAAQ,SAAS,gBAAgB;IACnD,KAAK,IAAM,CAACiB,GAAIC,MAASf,EAAS,SAChC,AAAIU,EAAM,IAAII,CAAE,MAAM,KAAA,KAAaJ,EAAM,SAASI,CAAE,MACpDJ,EAAM,QAAQI,GAAIC,CAAI,GACtB;GAEJ,CAAC;EAEL,UAAU;GACR,EAAQ;EACV;EACA,EAAQ,QAAQ,WAAWF,CAAK;CAClC,GAEMG,UAAmB;EAEvB,AADA,EAAQ,UAAU,IAClBV,EAAK,OAAO;CACd,GAEMW,UAAmB;EACvB,IAAI,CAACZ,EAAQ,WAAW,CAACF,EAAQ,SAAS;EAC1C,IAAMe,IAAOV,EAAQ,GACfW,IAASD,EAAK,OAAO,GACrB,EAAE,UAAO,YAASrB,EAAQ,QAAQ,KAAK;GAAE;GAAQ,OAAOO,EAAM;EAAQ,CAAC;EAE7E,AADA,EAAM,WACNJ,EAAS,QAAQ,MAAM;EACvB,KAAK,IAAM,CAACc,GAAIC,MAASK,GACvB,AAAID,EAAO,IAAIL,CAAE,KACjBd,EAAS,QAAQ,IAAIc,GAAIC,CAAI;EAG/B,IADAG,EAAK,IAAIlB,EAAS,OAAO,GACrBqB,GAAM;GAER,AADAL,EAAK,GACLJ,EAAO;GACP;EACF;EACA,EAAK,QAAQ;CACf;CAaA,OAXAU,SACE,EAAQ,UAAU,UACL;EAKX,AAJA,EAAQ,UAAU,IAClBhB,EAAK,OAAO,GAGZK,EAAQ;CACV,IACC,CAACL,GAAMK,CAAO,CAAC,GAEXY,SAAwB;EAC7B,QAAQ;GACN,AAAI,GAAQ,WAAYlB,EAAQ,YAChC,EAAQ,UAAU,IAClB,EAAM,UAAU,GAChBC,EAAK,QAAQ;EACf;EACA,OAAO;GACL,AAAKH,EAAQ,YACba,EAAK,GACLJ,EAAO;EACT;EACA,SAAS;GAEP,AADAI,EAAK,GACLL,EAAQ;EACV;EACA,iBAAiBR,EAAQ;CAE3B,IAAI,CAACG,CAAI,CAAC;AACZ"}
1
+ {"version":3,"file":"usePoseRun.js","names":["usePoseRun","optsRef","useLatest","opts","previews","useRef","held","running","frame","mounted","holdNow","useCallback","scene","release","commit","moved","id","pose","halt","loop","tick","hold","pinned","poses","done","useVisibleRaf","useEffect","useMemo"],"sources":["../../../src/features/poseRun/usePoseRun.ts"],"sourcesContent":["/**\n * A pose run: poses published per frame, committed once.\n *\n * The frames go to `scene.overrides` — the ephemeral table a drag publishes\n * to — so an edge follows a node the run is moving, and the whole run lands as\n * one undo entry when it settles.\n *\n * **A node another gesture owns is pinned, not driven.** The run publishes\n * through a hold of its own, so an override it did not put there belongs to\n * somebody else — a drag, a resize. It never writes that id, never commits it,\n * and hands it to `step` as pinned so the producer can hold its neighbors\n * against it.\n */\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { useLatest, useVisibleRaf, type VisibleRaf } from '@weasel-js/react';\nimport type { NodeId, PoseHold, Scene } from '../../core/scene/types';\n\n/** What a producer is told before it is asked for a frame. */\nexport interface PoseRunCtx<TPose> {\n /** Nodes another gesture is moving, and the pose each is drawn at. The run\n * will not write these, so a producer holds them fixed. */\n pinned: ReadonlyMap<NodeId, TPose>;\n /** Frames this run has produced, starting at 0. */\n frame: number;\n}\n\n/** One frame of a run. */\nexport interface PoseRunStep<TPose> {\n /** Where the nodes it drives are this frame — the whole picture, not a\n * delta. An id the frame omits stops being published, which is how a node\n * leaves the run's hands. */\n poses: Iterable<readonly [NodeId, TPose]>;\n /** Whether the run is finished. The frame is published either way, and a\n * finished run commits. */\n done: boolean;\n}\n\n/** Options for `usePoseRun`. Read fresh each frame, so they may change mid-run. */\nexport interface UsePoseRunOptions<TPose> {\n scene: Scene<unknown, string, TPose>;\n /** Called once per frame while running, for that frame's poses. */\n step: (ctx: PoseRunCtx<TPose>) => PoseRunStep<TPose>;\n /** The undo entry's name. Default `'Layout'`. */\n label?: string;\n /** How many nodes the commit wrote. Fires after the ops land. */\n onCommit?: (moved: number) => void;\n /** Clock injection, for tests. Defaults to `requestAnimationFrame`. */\n requestFrame?: (cb: (t: number) => void) => number;\n cancelFrame?: (handle: number) => void;\n}\n\n/** Controls for a pose run. Stable for the life of the component. */\nexport interface PoseRun {\n /** Begin a new run, counting frames from 0. Idempotent while running. */\n start(): void;\n /** Finish now: commit what is published, then drop the overrides. */\n stop(): void;\n /** Abandon: drop the overrides and write nothing, like a canceled drag. */\n cancel(): void;\n isRunning(): boolean;\n}\n\n/**\n * Drive node poses frame by frame through `scene.overrides`, committing them as\n * one undo entry when `step` reports done or `stop` is called. Idle until\n * `start`; unmounting abandons the run without writing.\n */\nexport function usePoseRun<TPose>(opts: UsePoseRunOptions<TPose>): PoseRun {\n const optsRef = useLatest(opts);\n\n /** This frame's poses. */\n const previews = useRef(new Map<NodeId, TPose>());\n /** The hold the run publishes through, and the scene it was taken from. */\n const held = useRef<{ scene: Scene<unknown, string, TPose>; hold: PoseHold<TPose> } | null>(null);\n const running = useRef(false);\n const frame = useRef(0);\n const mounted = useRef(true);\n\n /** The hold on the scene the options name now. A run handed a different\n * scene mid-flight lets go of the old one's nodes. */\n const holdNow = useCallback((): PoseHold<TPose> => {\n const { scene } = optsRef.current;\n if (held.current?.scene !== scene) {\n held.current?.hold.drop();\n held.current = { scene, hold: scene.overrides.hold() };\n }\n return held.current.hold;\n }, [optsRef]);\n\n const release = useCallback((): void => {\n held.current?.hold.drop();\n previews.current.clear();\n }, []);\n\n const commit = (): void => {\n const scene = optsRef.current.scene;\n let moved = 0;\n try {\n if (previews.current.size > 0) {\n scene.batch(optsRef.current.label ?? 'Layout', () => {\n for (const [id, pose] of previews.current) {\n if (scene.get(id) === undefined || scene.isLocked(id)) continue;\n scene.setPose(id, pose);\n moved++;\n }\n });\n }\n } finally {\n release();\n }\n optsRef.current.onCommit?.(moved);\n };\n\n const halt = (loop: VisibleRaf): void => {\n running.current = false;\n loop.cancel();\n };\n\n const tick = (loop: VisibleRaf): void => {\n if (!mounted.current || !running.current) return;\n const hold = holdNow();\n const pinned = hold.others();\n const { poses, done } = optsRef.current.step({ pinned, frame: frame.current });\n frame.current++;\n previews.current.clear();\n for (const [id, pose] of poses) {\n if (pinned.has(id)) continue;\n previews.current.set(id, pose);\n }\n hold.set(previews.current);\n if (done) {\n halt(loop);\n commit();\n return;\n }\n loop.request();\n };\n\n // The gate defaults an absent clock to `requestAnimationFrame` itself, so\n // these pass straight through rather than being defaulted twice.\n const loop = useVisibleRaf((_time, loop) => { tick(loop); }, {\n requestFrame: opts.requestFrame,\n cancelFrame: opts.cancelFrame,\n });\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n loop.cancel();\n // Teardown is not a decision: an unmounted run abandons its frames\n // rather than writing them.\n release();\n };\n }, [loop, release]);\n\n return useMemo<PoseRun>(() => ({\n start() {\n if (running.current || !mounted.current) return;\n running.current = true;\n frame.current = 0;\n loop.request();\n },\n stop() {\n if (!running.current) return;\n halt(loop);\n commit();\n },\n cancel() {\n halt(loop);\n release();\n },\n isRunning: () => running.current,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }), [loop]);\n}\n"],"mappings":";;;AAmEA,SAAgBA,EAAkB,GAAyC;CACzE,IAAMC,IAAUC,EAAUC,CAAI,GAGxBC,IAAWC,kBAAO,IAAI,IAAmB,CAAC,GAE1CC,IAAOD,EAA+E,IAAI,GAC1FE,IAAUF,EAAO,EAAK,GACtBG,IAAQH,EAAO,CAAC,GAChBI,IAAUJ,EAAO,EAAI,GAIrBK,IAAUC,QAAmC;EACjD,IAAM,EAAE,aAAUV,EAAQ;EAK1B,OAJIK,EAAK,SAAS,UAAUM,MAC1BN,EAAK,SAAS,KAAK,KAAK,GACxB,EAAK,UAAU;GAAE;GAAO,MAAMM,EAAM,UAAU,KAAK;EAAE,IAEhDN,EAAK,QAAQ;CACtB,GAAG,CAACL,CAAO,CAAC,GAENY,IAAUF,QAAwB;EAEtC,AADAL,EAAK,SAAS,KAAK,KAAK,GACxBF,EAAS,QAAQ,MAAM;CACzB,GAAG,CAAC,CAAC,GAECU,UAAqB;EACzB,IAAMF,IAAQX,EAAQ,QAAQ,OAC1Bc,IAAQ;EACZ,IAAI;GACF,AAAIX,EAAS,QAAQ,OAAO,KAC1BQ,EAAM,MAAMX,EAAQ,QAAQ,SAAS,gBAAgB;IACnD,KAAK,IAAM,CAACe,GAAIC,MAASb,EAAS,SAChC,AAAIQ,EAAM,IAAII,CAAE,MAAM,KAAA,KAAaJ,EAAM,SAASI,CAAE,MACpDJ,EAAM,QAAQI,GAAIC,CAAI,GACtB;GAEJ,CAAC;EAEL,UAAU;GACR,EAAQ;EACV;EACA,EAAQ,QAAQ,WAAWF,CAAK;CAClC,GAEMG,KAAQ,MAA2B;EAEvC,AADA,EAAQ,UAAU,IAClBC,EAAK,OAAO;CACd,GAEMC,KAAQ,MAA2B;EACvC,IAAI,CAACX,EAAQ,WAAW,CAACF,EAAQ,SAAS;EAC1C,IAAMc,IAAOX,EAAQ,GACfY,IAASD,EAAK,OAAO,GACrB,EAAE,UAAO,YAASpB,EAAQ,QAAQ,KAAK;GAAE;GAAQ,OAAOO,EAAM;EAAQ,CAAC;EAE7E,AADA,EAAM,WACNJ,EAAS,QAAQ,MAAM;EACvB,KAAK,IAAM,CAACY,GAAIC,MAASM,GACvB,AAAID,EAAO,IAAIN,CAAE,KACjBZ,EAAS,QAAQ,IAAIY,GAAIC,CAAI;EAG/B,IADAI,EAAK,IAAIjB,EAAS,OAAO,GACrBoB,GAAM;GAER,AADAN,EAAKC,CAAI,GACTL,EAAO;GACP;EACF;EACA,EAAK,QAAQ;CACf,GAIMK,IAAOM,GAAe,GAAO,MAAS;EAAE,EAAKN,CAAI;CAAG,GAAG;EAC3D,cAAchB,EAAK;EACnB,aAAaA,EAAK;CACpB,CAAC;CAaD,OAXAuB,SACE,EAAQ,UAAU,UACL;EAKX,AAJA,EAAQ,UAAU,IAClBP,EAAK,OAAO,GAGZN,EAAQ;CACV,IACC,CAACM,GAAMN,CAAO,CAAC,GAEXc,SAAwB;EAC7B,QAAQ;GACN,AAAI,GAAQ,WAAYlB,EAAQ,YAChC,EAAQ,UAAU,IAClB,EAAM,UAAU,GAChBU,EAAK,QAAQ;EACf;EACA,OAAO;GACL,AAAKZ,EAAQ,YACbW,EAAKC,CAAI,GACTL,EAAO;EACT;EACA,SAAS;GAEP,AADAI,EAAKC,CAAI,GACTN,EAAQ;EACV;EACA,iBAAiBN,EAAQ;CAE3B,IAAI,CAACY,CAAI,CAAC;AACZ"}