@weasel-js/core 1.13.0 → 1.14.0

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 (741) hide show
  1. package/CHANGELOG.md +300 -0
  2. package/README.md +8 -12
  3. package/dist/affordances/hitAffordanceRegions.js +1 -1
  4. package/dist/affordances/hitAffordanceRegions.js.map +1 -1
  5. package/dist/affordances/pathAnchors.js.map +1 -1
  6. package/dist/animation/clipTargets.js +1 -1
  7. package/dist/animation/clipTargets.js.map +1 -1
  8. package/dist/animation/poseTarget.js +5 -32
  9. package/dist/animation/poseTarget.js.map +1 -1
  10. package/dist/{autoPoseDescriptor-CZ1eXbzY.d.ts → autoPoseDescriptor-C25QVb0W.d.ts} +1 -1
  11. package/dist/barrel/canvas.js +8 -8
  12. package/dist/barrel/chrome.js +9 -9
  13. package/dist/barrel/input.js +1 -1
  14. package/dist/barrel/interactions.js +3 -3
  15. package/dist/barrel/paint.js +2 -2
  16. package/dist/barrel/paths.js +19 -17
  17. package/dist/barrel/renderer.js +11 -7
  18. package/dist/barrel/routing.js +4 -1
  19. package/dist/barrel/scene.js +16 -12
  20. package/dist/barrel/text.js +5 -5
  21. package/dist/barrel/viewport.js +8 -8
  22. package/dist/canvas/Canvas.js +2 -2
  23. package/dist/canvas/Canvas.js.map +1 -1
  24. package/dist/canvas/CanvasView.js +20 -14
  25. package/dist/canvas/CanvasView.js.map +1 -1
  26. package/dist/canvas/GestureReason.js +64 -0
  27. package/dist/canvas/GestureReason.js.map +1 -0
  28. package/dist/canvas/GestureReason.module.js +7 -0
  29. package/dist/canvas/GestureReason.module.js.map +1 -0
  30. package/dist/canvas/LeanSceneCanvas.js +3 -1
  31. package/dist/canvas/LeanSceneCanvas.js.map +1 -1
  32. package/dist/canvas/NodeShape.js +2 -0
  33. package/dist/canvas/NodeShape.js.map +1 -1
  34. package/dist/canvas/PickHud.js +7 -3
  35. package/dist/canvas/PickHud.js.map +1 -1
  36. package/dist/canvas/SceneCanvas/CanvasKitProviders.js +1 -1
  37. package/dist/canvas/SceneCanvas/CanvasKitProviders.js.map +1 -1
  38. package/dist/canvas/SceneCanvas/DevHuds.js +2 -2
  39. package/dist/canvas/SceneCanvas/DevHuds.js.map +1 -1
  40. package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +4 -2
  41. package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js.map +1 -1
  42. package/dist/canvas/SceneCanvas/SceneCanvasInput.js +3 -1
  43. package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
  44. package/dist/canvas/SceneCanvas/brushOverlay.js.map +1 -1
  45. package/dist/canvas/SceneCanvas/builtinToolSets.js +17 -9
  46. package/dist/canvas/SceneCanvas/builtinToolSets.js.map +1 -1
  47. package/dist/canvas/SceneCanvas/canvasPassThrough.js +2 -1
  48. package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
  49. package/dist/canvas/SceneCanvas/defaultNodeProperties.js +75 -19
  50. package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
  51. package/dist/canvas/SceneCanvas/defaultNodeRouting.js +7 -1
  52. package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
  53. package/dist/canvas/SceneCanvas/defaultTextEditor.js +108 -0
  54. package/dist/canvas/SceneCanvas/defaultTextEditor.js.map +1 -0
  55. package/dist/canvas/SceneCanvas/describePresets.js +47 -0
  56. package/dist/canvas/SceneCanvas/describePresets.js.map +1 -0
  57. package/dist/canvas/SceneCanvas/features.js +57 -16
  58. package/dist/canvas/SceneCanvas/features.js.map +1 -1
  59. package/dist/canvas/SceneCanvas/mergeLayers.js +10 -1
  60. package/dist/canvas/SceneCanvas/mergeLayers.js.map +1 -1
  61. package/dist/canvas/SceneCanvas/namedPresets.js +52 -24
  62. package/dist/canvas/SceneCanvas/namedPresets.js.map +1 -1
  63. package/dist/canvas/SceneCanvas/presets/arrange.js +35 -10
  64. package/dist/canvas/SceneCanvas/presets/arrange.js.map +1 -1
  65. package/dist/canvas/SceneCanvas/presets/baseline.js +9 -0
  66. package/dist/canvas/SceneCanvas/presets/baseline.js.map +1 -0
  67. package/dist/canvas/SceneCanvas/presets/chrome.js.map +1 -1
  68. package/dist/canvas/SceneCanvas/presets/composites.js +43 -0
  69. package/dist/canvas/SceneCanvas/presets/composites.js.map +1 -0
  70. package/dist/canvas/SceneCanvas/presets/edit.js +79 -26
  71. package/dist/canvas/SceneCanvas/presets/edit.js.map +1 -1
  72. package/dist/canvas/SceneCanvas/presets/ingest.js +1 -0
  73. package/dist/canvas/SceneCanvas/presets/ingest.js.map +1 -1
  74. package/dist/canvas/SceneCanvas/presets/paths.js +58 -16
  75. package/dist/canvas/SceneCanvas/presets/paths.js.map +1 -1
  76. package/dist/canvas/SceneCanvas/presets/select.js +4 -2
  77. package/dist/canvas/SceneCanvas/presets/select.js.map +1 -1
  78. package/dist/canvas/SceneCanvas/presets/tools.js +24 -0
  79. package/dist/canvas/SceneCanvas/presets/tools.js.map +1 -0
  80. package/dist/canvas/SceneCanvas/presets/transform.js +4 -0
  81. package/dist/canvas/SceneCanvas/presets/transform.js.map +1 -1
  82. package/dist/canvas/SceneCanvas/presets/view.js +3 -1
  83. package/dist/canvas/SceneCanvas/presets/view.js.map +1 -1
  84. package/dist/canvas/SceneCanvas/props.js.map +1 -1
  85. package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js +1 -1
  86. package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js.map +1 -1
  87. package/dist/canvas/SceneCanvas/toolRegistry.js +5 -4
  88. package/dist/canvas/SceneCanvas/toolRegistry.js.map +1 -1
  89. package/dist/canvas/SceneCanvas/useCanvasTools.js +26 -10
  90. package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
  91. package/dist/canvas/SceneCanvas/useContributionRoles.js +15 -2
  92. package/dist/canvas/SceneCanvas/useContributionRoles.js.map +1 -1
  93. package/dist/canvas/SceneCanvas/useDepartureGhostLayer.js +5 -4
  94. package/dist/canvas/SceneCanvas/useDepartureGhostLayer.js.map +1 -1
  95. package/dist/canvas/SceneCanvas/useDispatcherOverlayLayer.js +1 -1
  96. package/dist/canvas/SceneCanvas/useNodeAtPoint.js.map +1 -1
  97. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +9 -8
  98. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
  99. package/dist/canvas/SceneCanvas/useSceneCanvasPathEdit.js +2 -2
  100. package/dist/canvas/SceneCanvas/useSceneCanvasPathEdit.js.map +1 -1
  101. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +4 -1
  102. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
  103. package/dist/canvas/SceneCanvas/useSceneChrome.js +7 -4
  104. package/dist/canvas/SceneCanvas/useSceneChrome.js.map +1 -1
  105. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js +9 -9
  106. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js.map +1 -1
  107. package/dist/canvas/SceneCanvas/useScenePicking.js +4 -7
  108. package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
  109. package/dist/canvas/SceneCanvas/useSceneRedrawSources.js +2 -1
  110. package/dist/canvas/SceneCanvas/useSceneRedrawSources.js.map +1 -1
  111. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +6 -6
  112. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  113. package/dist/canvas/SceneCanvas/useSceneStandardActions.js +2 -2
  114. package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
  115. package/dist/canvas/SceneCanvas/useSelectionOverlayLayer.js +1 -1
  116. package/dist/canvas/SceneCanvas/useSelectionOverlayLayer.js.map +1 -1
  117. package/dist/canvas/SceneCanvas/useSurfaceViewInputs.js.map +1 -1
  118. package/dist/canvas/SceneCanvas/useToolKeybindings.js +2 -2
  119. package/dist/canvas/SceneCanvas/useToolKeybindings.js.map +1 -1
  120. package/dist/canvas/SceneCanvas/useViewportActions.js +5 -2
  121. package/dist/canvas/SceneCanvas/useViewportActions.js.map +1 -1
  122. package/dist/canvas/SceneCanvas/worldPointAt.js.map +1 -1
  123. package/dist/canvas/SceneCanvas.js +22 -11
  124. package/dist/canvas/SceneCanvas.js.map +1 -1
  125. package/dist/canvas/SceneViewCanvas.js +1 -0
  126. package/dist/canvas/SceneViewCanvas.js.map +1 -1
  127. package/dist/canvas/ViewMarker.js.map +1 -1
  128. package/dist/canvas/accentColor.js +14 -1
  129. package/dist/canvas/accentColor.js.map +1 -1
  130. package/dist/canvas/affordanceAt.js +2 -2
  131. package/dist/canvas/affordanceAt.js.map +1 -1
  132. package/dist/canvas/buildSceneTree.js +5 -3
  133. package/dist/canvas/buildSceneTree.js.map +1 -1
  134. package/dist/canvas/deps/editAnchors.js +4 -4
  135. package/dist/canvas/deps/editAnchors.js.map +1 -1
  136. package/dist/canvas/deps/enteredGroup.js +14 -5
  137. package/dist/canvas/deps/enteredGroup.js.map +1 -1
  138. package/dist/canvas/deps/ingestion.js +5 -1
  139. package/dist/canvas/deps/ingestion.js.map +1 -1
  140. package/dist/canvas/deps/insert.js.map +1 -1
  141. package/dist/canvas/deps/insertSite.js +5 -3
  142. package/dist/canvas/deps/insertSite.js.map +1 -1
  143. package/dist/canvas/deps/nodeAtPoint.js +1 -12
  144. package/dist/canvas/deps/nodeAtPoint.js.map +1 -1
  145. package/dist/canvas/deps/pathEditCommits.js +3 -3
  146. package/dist/canvas/deps/pathEditCommits.js.map +1 -1
  147. package/dist/canvas/deps/slice.js +16 -2
  148. package/dist/canvas/deps/slice.js.map +1 -1
  149. package/dist/canvas/deps/snap.js +5 -5
  150. package/dist/canvas/deps/snap.js.map +1 -1
  151. package/dist/canvas/deps/textEdit.js +5 -6
  152. package/dist/canvas/deps/textEdit.js.map +1 -1
  153. package/dist/canvas/derivedPath.js +8 -3
  154. package/dist/canvas/derivedPath.js.map +1 -1
  155. package/dist/canvas/getNodeAtPoint.js +2 -2
  156. package/dist/canvas/getNodeAtPoint.js.map +1 -1
  157. package/dist/canvas/insertPreviewExtent.js.map +1 -1
  158. package/dist/canvas/layerSlots.js.map +1 -1
  159. package/dist/canvas/nodeShapes/measurePainter.js +1 -1
  160. package/dist/canvas/nodeShapes/measurePainter.js.map +1 -1
  161. package/dist/canvas/nodeShapes/strokedBounds.js +5 -3
  162. package/dist/canvas/nodeShapes/strokedBounds.js.map +1 -1
  163. package/dist/canvas/overlayRoleStrokes.js +6 -0
  164. package/dist/canvas/overlayRoleStrokes.js.map +1 -1
  165. package/dist/canvas/pickIndex/scenePickIndex.js +10 -12
  166. package/dist/canvas/pickIndex/scenePickIndex.js.map +1 -1
  167. package/dist/canvas/sceneAdapter.js +10 -19
  168. package/dist/canvas/sceneAdapter.js.map +1 -1
  169. package/dist/canvas/sceneClipboard.js +17 -4
  170. package/dist/canvas/sceneClipboard.js.map +1 -1
  171. package/dist/canvas/sceneViewRender.js +3 -2
  172. package/dist/canvas/sceneViewRender.js.map +1 -1
  173. package/dist/canvas/surfaceContribution.js.map +1 -1
  174. package/dist/canvas/useCanvasPaint.js +10 -4
  175. package/dist/canvas/useCanvasPaint.js.map +1 -1
  176. package/dist/canvas/useHitObserver.js +1 -1
  177. package/dist/canvas/useHitObserver.js.map +1 -1
  178. package/dist/canvas/useViewAim.js +1 -1
  179. package/dist/canvas/useViewAim.js.map +1 -1
  180. package/dist/canvas/useViewHelpers.js +8 -5
  181. package/dist/canvas/useViewHelpers.js.map +1 -1
  182. package/dist/canvas/useViewNesting.js +39 -12
  183. package/dist/canvas/useViewNesting.js.map +1 -1
  184. package/dist/canvas/viewInputs.js.map +1 -1
  185. package/dist/canvas/viewMarkers.js +2 -2
  186. package/dist/canvas/viewMarkers.js.map +1 -1
  187. package/dist/canvas/viewRegistry.js +2 -2
  188. package/dist/canvas/viewRegistry.js.map +1 -1
  189. package/dist/canvas/viewSnap.js +67 -0
  190. package/dist/canvas/viewSnap.js.map +1 -0
  191. package/dist/clipboard.d.ts +6 -12
  192. package/dist/{clone-qb2e1F5p.d.ts → clone-Bz4iqGfE.d.ts} +10 -5
  193. package/dist/clone.d.ts +5 -5
  194. package/dist/core/geometry/booleanOfPaths.js +23 -0
  195. package/dist/core/geometry/booleanOfPaths.js.map +1 -0
  196. package/dist/core/geometry/pathPoseDescriptor.js +12 -9
  197. package/dist/core/geometry/pathPoseDescriptor.js.map +1 -1
  198. package/dist/core/geometry/poseDescriptor.js +12 -3
  199. package/dist/core/geometry/poseDescriptor.js.map +1 -1
  200. package/dist/core/geometry/targetFrame.js +2 -13
  201. package/dist/core/geometry/targetFrame.js.map +1 -1
  202. package/dist/core/layers/render.js +2 -1
  203. package/dist/core/layers/render.js.map +1 -1
  204. package/dist/core/layers/retain.js +5 -3
  205. package/dist/core/layers/retain.js.map +1 -1
  206. package/dist/core/ops/setPath.js +3 -0
  207. package/dist/core/ops/setPath.js.map +1 -1
  208. package/dist/core/paint/meshKindLazy.js.map +1 -1
  209. package/dist/core/paint/paintKindEntry.js.map +1 -1
  210. package/dist/core/paint/paintKinds.js +8 -1
  211. package/dist/core/paint/paintKinds.js.map +1 -1
  212. package/dist/core/scene/NodeProperties.js.map +1 -1
  213. package/dist/core/scene/NodeRouting.js.map +1 -1
  214. package/dist/core/scene/booleanGroup.js +33 -0
  215. package/dist/core/scene/booleanGroup.js.map +1 -0
  216. package/dist/core/scene/booleanOfChildren.js +53 -0
  217. package/dist/core/scene/booleanOfChildren.js.map +1 -0
  218. package/dist/core/scene/carryDescendants.js +18 -6
  219. package/dist/core/scene/carryDescendants.js.map +1 -1
  220. package/dist/core/scene/composeFrame.js +42 -5
  221. package/dist/core/scene/composeFrame.js.map +1 -1
  222. package/dist/core/scene/composePose.js +35 -16
  223. package/dist/core/scene/composePose.js.map +1 -1
  224. package/dist/core/scene/depMemo.js +36 -18
  225. package/dist/core/scene/depMemo.js.map +1 -1
  226. package/dist/core/scene/derivedParts.js +2 -23
  227. package/dist/core/scene/derivedParts.js.map +1 -1
  228. package/dist/core/scene/effectivePose.js +10 -0
  229. package/dist/core/scene/effectivePose.js.map +1 -1
  230. package/dist/core/scene/historyForm.js +5 -4
  231. package/dist/core/scene/historyForm.js.map +1 -1
  232. package/dist/core/scene/kitOps.js +5 -71
  233. package/dist/core/scene/kitOps.js.map +1 -1
  234. package/dist/core/scene/kitRegistry.js +5 -0
  235. package/dist/core/scene/kitRegistry.js.map +1 -1
  236. package/dist/core/scene/layerOps.js +82 -0
  237. package/dist/core/scene/layerOps.js.map +1 -0
  238. package/dist/core/scene/layoutPass.js +14 -12
  239. package/dist/core/scene/layoutPass.js.map +1 -1
  240. package/dist/core/scene/lockGuard.js +9 -3
  241. package/dist/core/scene/lockGuard.js.map +1 -1
  242. package/dist/core/scene/nestedHit.js +5 -5
  243. package/dist/core/scene/nestedHit.js.map +1 -1
  244. package/dist/core/scene/nodeFlags.js +8 -2
  245. package/dist/core/scene/nodeFlags.js.map +1 -1
  246. package/dist/core/scene/nodeGates.js +31 -2
  247. package/dist/core/scene/nodeGates.js.map +1 -1
  248. package/dist/core/scene/pathEffects/effects.js +22 -3
  249. package/dist/core/scene/pathEffects/effects.js.map +1 -1
  250. package/dist/core/scene/pathEffects/textAlong.js +16 -3
  251. package/dist/core/scene/pathEffects/textAlong.js.map +1 -1
  252. package/dist/core/scene/pathEffects/textAlongBands.js +65 -0
  253. package/dist/core/scene/pathEffects/textAlongBands.js.map +1 -0
  254. package/dist/core/scene/reconcileSpecs.js +1 -1
  255. package/dist/core/scene/reconcileSpecs.js.map +1 -1
  256. package/dist/core/scene/scene.js +11 -5
  257. package/dist/core/scene/scene.js.map +1 -1
  258. package/dist/core/scene/sceneEdits.js +5 -2
  259. package/dist/core/scene/sceneEdits.js.map +1 -1
  260. package/dist/core/scene/scenePoseAdapter.js +10 -1
  261. package/dist/core/scene/scenePoseAdapter.js.map +1 -1
  262. package/dist/core/scene/scrollOffsets.js +93 -0
  263. package/dist/core/scene/scrollOffsets.js.map +1 -0
  264. package/dist/core/scene/serialization.js +15 -5
  265. package/dist/core/scene/serialization.js.map +1 -1
  266. package/dist/core/scene/statedFields.js +2 -1
  267. package/dist/core/scene/statedFields.js.map +1 -1
  268. package/dist/core/scene/strategyView.js +35 -0
  269. package/dist/core/scene/strategyView.js.map +1 -0
  270. package/dist/core/scene/types.js.map +1 -1
  271. package/dist/core/scene/useScene.js +2 -0
  272. package/dist/core/scene/useScene.js.map +1 -1
  273. package/dist/core/scene/worldSave.js +87 -0
  274. package/dist/core/scene/worldSave.js.map +1 -0
  275. package/dist/core/selection/selectionTarget.js +1 -1
  276. package/dist/core/selection/selectionTarget.js.map +1 -1
  277. package/dist/{cornerHandles-CNCb7aTY.d.ts → cornerHandles-DpmQM_z5.d.ts} +1 -1
  278. package/dist/{depSchema-CpIQzKWQ.d.ts → depSchema-C3BwxitB.d.ts} +119 -99
  279. package/dist/devtools-protocol.d.ts +2 -2
  280. package/dist/devtools.d.ts +2 -2
  281. package/dist/{features → domains}/chrome-caps/buildChromeCtx.js +1 -1
  282. package/dist/domains/chrome-caps/buildChromeCtx.js.map +1 -0
  283. package/dist/{features → domains}/chrome-caps/conditions.js +1 -1
  284. package/dist/domains/chrome-caps/conditions.js.map +1 -0
  285. package/dist/{features → domains}/chrome-caps/defaults.js +1 -1
  286. package/dist/domains/chrome-caps/defaults.js.map +1 -0
  287. package/dist/{features → domains}/chrome-caps/resolve.js +1 -1
  288. package/dist/domains/chrome-caps/resolve.js.map +1 -0
  289. package/dist/{features → domains}/chrome-caps/useChromeCaps.js +1 -1
  290. package/dist/domains/chrome-caps/useChromeCaps.js.map +1 -0
  291. package/dist/{features → domains}/chrome-caps/useHoverTracking.js +1 -1
  292. package/dist/domains/chrome-caps/useHoverTracking.js.map +1 -0
  293. package/dist/{features → domains}/ingestion/contentHandlers.js +20 -3
  294. package/dist/domains/ingestion/contentHandlers.js.map +1 -0
  295. package/dist/domains/ingestion/ghost.js +56 -0
  296. package/dist/domains/ingestion/ghost.js.map +1 -0
  297. package/dist/{features → domains}/ingestion/imageHandler.js +1 -1
  298. package/dist/domains/ingestion/imageHandler.js.map +1 -0
  299. package/dist/domains/ingestion/kitHandlers.js +22 -0
  300. package/dist/domains/ingestion/kitHandlers.js.map +1 -0
  301. package/dist/{features → domains}/ingestion/openFilePicker.js +1 -1
  302. package/dist/domains/ingestion/openFilePicker.js.map +1 -0
  303. package/dist/{features → domains}/ingestion/placement.js +1 -1
  304. package/dist/domains/ingestion/placement.js.map +1 -0
  305. package/dist/{features → domains}/ingestion/svgHandler.js +1 -1
  306. package/dist/domains/ingestion/svgHandler.js.map +1 -0
  307. package/dist/{features → domains}/ingestion/textHandler.js +9 -7
  308. package/dist/domains/ingestion/textHandler.js.map +1 -0
  309. package/dist/{features → domains}/ingestion/useCanvasIngestion.js +13 -15
  310. package/dist/domains/ingestion/useCanvasIngestion.js.map +1 -0
  311. package/dist/{features → domains}/ingestion/weaselJsonHandler.js +1 -1
  312. package/dist/domains/ingestion/weaselJsonHandler.js.map +1 -0
  313. package/dist/{features → domains}/longPress/longPressFeedback.js +1 -1
  314. package/dist/domains/longPress/longPressFeedback.js.map +1 -0
  315. package/dist/{features → domains}/measure/actionIds.js +1 -1
  316. package/dist/domains/measure/actionIds.js.map +1 -0
  317. package/dist/{features → domains}/measure/measureCommands.js +1 -1
  318. package/dist/domains/measure/measureCommands.js.map +1 -0
  319. package/dist/{features → domains}/measure/measureContribution.js +1 -1
  320. package/dist/domains/measure/measureContribution.js.map +1 -0
  321. package/dist/{features → domains}/measure/measureEdits.js +1 -1
  322. package/dist/domains/measure/measureEdits.js.map +1 -0
  323. package/dist/{features → domains}/measure/measureGestures.js +4 -7
  324. package/dist/domains/measure/measureGestures.js.map +1 -0
  325. package/dist/{features → domains}/measure/measureHit.js +1 -1
  326. package/dist/domains/measure/measureHit.js.map +1 -0
  327. package/dist/{features → domains}/measure/measureLayer.js +1 -1
  328. package/dist/domains/measure/measureLayer.js.map +1 -0
  329. package/dist/{features → domains}/measure/measurement.js +1 -1
  330. package/dist/domains/measure/measurement.js.map +1 -0
  331. package/dist/{features → domains}/measure/measurements.js +2 -2
  332. package/dist/domains/measure/measurements.js.map +1 -0
  333. package/dist/{features → domains}/modes/modeLayers.js +1 -1
  334. package/dist/domains/modes/modeLayers.js.map +1 -0
  335. package/dist/{features → domains}/modes/modeTriggers.js +3 -6
  336. package/dist/domains/modes/modeTriggers.js.map +1 -0
  337. package/dist/{features → domains}/pathEffects/effectSources.js +1 -1
  338. package/dist/domains/pathEffects/effectSources.js.map +1 -0
  339. package/dist/{features → domains}/pathEffects/index.js +1 -1
  340. package/dist/domains/pathEffects/index.js.map +1 -0
  341. package/dist/{features → domains}/pathEffects/readSource.js +1 -1
  342. package/dist/domains/pathEffects/readSource.js.map +1 -0
  343. package/dist/{features → domains}/pathEffects/textItems.js +11 -2
  344. package/dist/domains/pathEffects/textItems.js.map +1 -0
  345. package/dist/{features → domains}/pathEffects/useEffectSourcesLayer.js +1 -1
  346. package/dist/domains/pathEffects/useEffectSourcesLayer.js.map +1 -0
  347. package/dist/{features → domains}/paths/anchorData.js +1 -1
  348. package/dist/domains/paths/anchorData.js.map +1 -0
  349. package/dist/{features → domains}/paths/anchorMarkers.js +9 -7
  350. package/dist/domains/paths/anchorMarkers.js.map +1 -0
  351. package/dist/{features → domains}/paths/editablePath.js +1 -1
  352. package/dist/domains/paths/editablePath.js.map +1 -0
  353. package/dist/{features → domains}/paths/index.js +3 -3
  354. package/dist/{features → domains}/paths/originProjection.js +1 -1
  355. package/dist/domains/paths/originProjection.js.map +1 -0
  356. package/dist/{features → domains}/paths/pathEditExtension.js +1 -1
  357. package/dist/domains/paths/pathEditExtension.js.map +1 -0
  358. package/dist/{features → domains}/paths/pathEditState.js +1 -1
  359. package/dist/domains/paths/pathEditState.js.map +1 -0
  360. package/dist/{features → domains}/paths/pathEditingOverlayLayer.js +1 -1
  361. package/dist/domains/paths/pathEditingOverlayLayer.js.map +1 -0
  362. package/dist/{features → domains}/paths/pathLayer.js +1 -1
  363. package/dist/domains/paths/pathLayer.js.map +1 -0
  364. package/dist/{features → domains}/paths/pathRepresentation.js +1 -1
  365. package/dist/domains/paths/pathRepresentation.js.map +1 -0
  366. package/dist/{features → domains}/paths/pathSnap/glyphs.js +1 -1
  367. package/dist/domains/paths/pathSnap/glyphs.js.map +1 -0
  368. package/dist/{features → domains}/paths/pathSnap/pathSnapSource.js +1 -1
  369. package/dist/domains/paths/pathSnap/pathSnapSource.js.map +1 -0
  370. package/dist/{features → domains}/paths/pathSnap/sceneIndex.js +1 -1
  371. package/dist/domains/paths/pathSnap/sceneIndex.js.map +1 -0
  372. package/dist/{features → domains}/paths/pathSnap/snapGeometry.js +3 -2
  373. package/dist/domains/paths/pathSnap/snapGeometry.js.map +1 -0
  374. package/dist/{features → domains}/paths/penPreviewLayer.js +1 -1
  375. package/dist/domains/paths/penPreviewLayer.js.map +1 -0
  376. package/dist/{features → domains}/paths/shapeGenerator.js +6 -4
  377. package/dist/domains/paths/shapeGenerator.js.map +1 -0
  378. package/dist/{features → domains}/paths/strokeOutline.js +1 -1
  379. package/dist/domains/paths/strokeOutline.js.map +1 -0
  380. package/dist/{features → domains}/paths/useAnchorFields.js +1 -1
  381. package/dist/domains/paths/useAnchorFields.js.map +1 -0
  382. package/dist/{features → domains}/paths/usePathEdit.js +1 -1
  383. package/dist/domains/paths/usePathEdit.js.map +1 -0
  384. package/dist/{features → domains}/poseRun/usePoseRun.js +1 -1
  385. package/dist/domains/poseRun/usePoseRun.js.map +1 -0
  386. package/dist/{features → domains}/selection/enteredGroupFrame.js +49 -10
  387. package/dist/domains/selection/enteredGroupFrame.js.map +1 -0
  388. package/dist/{features → domains}/selection/enteredGroupScope.js +1 -1
  389. package/dist/domains/selection/enteredGroupScope.js.map +1 -0
  390. package/dist/domains/selection/enteredOperands.js +29 -0
  391. package/dist/domains/selection/enteredOperands.js.map +1 -0
  392. package/dist/{features → domains}/selection/handleCommands.js +1 -1
  393. package/dist/domains/selection/handleCommands.js.map +1 -0
  394. package/dist/{features → domains}/selection/overlay.js +1 -1
  395. package/dist/domains/selection/overlay.js.map +1 -0
  396. package/dist/{features → domains}/selection/selectionPose.js +1 -1
  397. package/dist/domains/selection/selectionPose.js.map +1 -0
  398. package/dist/{features → domains}/snap/angle2d.js +1 -1
  399. package/dist/domains/snap/angle2d.js.map +1 -0
  400. package/dist/{features → domains}/snap/debugCandidates.js +1 -1
  401. package/dist/domains/snap/debugCandidates.js.map +1 -0
  402. package/dist/{features → domains}/snap/gather.js +1 -1
  403. package/dist/domains/snap/gather.js.map +1 -0
  404. package/dist/{features → domains}/snap/inputSubspace.js +1 -1
  405. package/dist/domains/snap/inputSubspace.js.map +1 -0
  406. package/dist/{features → domains}/snap/lazySession.js +1 -1
  407. package/dist/domains/snap/lazySession.js.map +1 -0
  408. package/dist/{features → domains}/snap/marks.js +1 -1
  409. package/dist/domains/snap/marks.js.map +1 -0
  410. package/dist/{features → domains}/snap/outcomeStore.js +1 -1
  411. package/dist/domains/snap/outcomeStore.js.map +1 -0
  412. package/dist/{features → domains}/snap/pointSnap.js +1 -1
  413. package/dist/domains/snap/pointSnap.js.map +1 -0
  414. package/dist/{features → domains}/snap/probes2d.js +1 -1
  415. package/dist/domains/snap/probes2d.js.map +1 -0
  416. package/dist/{features → domains}/snap/readoutDraw.js +1 -1
  417. package/dist/domains/snap/readoutDraw.js.map +1 -0
  418. package/dist/{features → domains}/snap/readoutLayer.js +1 -1
  419. package/dist/domains/snap/readoutLayer.js.map +1 -0
  420. package/dist/{features → domains}/snap/registry.js +1 -1
  421. package/dist/domains/snap/registry.js.map +1 -0
  422. package/dist/{features → domains}/snap/resizeBox.js +1 -1
  423. package/dist/domains/snap/resizeBox.js.map +1 -0
  424. package/dist/{features → domains}/snap/roles.js +1 -1
  425. package/dist/domains/snap/roles.js.map +1 -0
  426. package/dist/{features → domains}/snap/rotationSource.js +1 -1
  427. package/dist/domains/snap/rotationSource.js.map +1 -0
  428. package/dist/{features → domains}/snap/sceneReads.js +24 -3
  429. package/dist/domains/snap/sceneReads.js.map +1 -0
  430. package/dist/{features → domains}/snap/sceneSession2d.js +3 -2
  431. package/dist/domains/snap/sceneSession2d.js.map +1 -0
  432. package/dist/{features → domains}/snap/screen.js +1 -1
  433. package/dist/domains/snap/screen.js.map +1 -0
  434. package/dist/{features → domains}/snap/session2d.js +3 -2
  435. package/dist/domains/snap/session2d.js.map +1 -0
  436. package/dist/{features → domains}/snap/spanSnap.js +1 -1
  437. package/dist/domains/snap/spanSnap.js.map +1 -0
  438. package/dist/{features → domains}/snap/stepAngles.js +1 -1
  439. package/dist/domains/snap/stepAngles.js.map +1 -0
  440. package/dist/{features → domains}/text/editingDrawOne.js +4 -4
  441. package/dist/domains/text/editingDrawOne.js.map +1 -0
  442. package/dist/domains/text/hostTextEditors.js +24 -0
  443. package/dist/domains/text/hostTextEditors.js.map +1 -0
  444. package/dist/{features → domains}/text/index.js +1 -1
  445. package/dist/{features → domains}/text/sceneTextDrafts.js +1 -1
  446. package/dist/domains/text/sceneTextDrafts.js.map +1 -0
  447. package/dist/{features → domains}/text/textHitAt.js +18 -2
  448. package/dist/domains/text/textHitAt.js.map +1 -0
  449. package/dist/{features → domains}/text/textLayer.js +1 -1
  450. package/dist/domains/text/textLayer.js.map +1 -0
  451. package/dist/{features → domains}/text/useSceneTextEdit.js +11 -3
  452. package/dist/domains/text/useSceneTextEdit.js.map +1 -0
  453. package/dist/{features → domains}/viewports/aim.js +1 -1
  454. package/dist/domains/viewports/aim.js.map +1 -0
  455. package/dist/{features → domains}/viewports/viewMarker.js +23 -5
  456. package/dist/domains/viewports/viewMarker.js.map +1 -0
  457. package/dist/{features → domains}/viewports/viewMarkerDrag.js +3 -3
  458. package/dist/domains/viewports/viewMarkerDrag.js.map +1 -0
  459. package/dist/{features → domains}/viewports/viewResolver.js +1 -1
  460. package/dist/domains/viewports/viewResolver.js.map +1 -0
  461. package/dist/{features → domains}/viewports/viewportLayer.js +1 -1
  462. package/dist/domains/viewports/viewportLayer.js.map +1 -0
  463. package/dist/icons.table.js +1 -1
  464. package/dist/icons.table.js.map +1 -1
  465. package/dist/icons.table.json +106 -6
  466. package/dist/index.d.ts +1441 -1050
  467. package/dist/index.js +90 -77
  468. package/dist/interactions/actions/admitsChild.js +6 -2
  469. package/dist/interactions/actions/admitsChild.js.map +1 -1
  470. package/dist/interactions/actions/align/align.js +1 -4
  471. package/dist/interactions/actions/align/align.js.map +1 -1
  472. package/dist/interactions/actions/booleans/booleanGroupAction.js +66 -0
  473. package/dist/interactions/actions/booleans/booleanGroupAction.js.map +1 -0
  474. package/dist/interactions/actions/booleans/keymap.js +52 -0
  475. package/dist/interactions/actions/booleans/keymap.js.map +1 -0
  476. package/dist/interactions/actions/booleans/operands.js +1 -1
  477. package/dist/interactions/actions/booleans/pieces.js +4 -11
  478. package/dist/interactions/actions/booleans/pieces.js.map +1 -1
  479. package/dist/interactions/actions/clipboard/action.js +3 -7
  480. package/dist/interactions/actions/clipboard/action.js.map +1 -1
  481. package/dist/interactions/actions/clipboard/clipboardOps.js +1 -1
  482. package/dist/interactions/actions/clipboard/clipboardOps.js.map +1 -1
  483. package/dist/interactions/actions/clipboard/materializePaste.js.map +1 -1
  484. package/dist/interactions/actions/defaults/anchorEditing.js +6 -6
  485. package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
  486. package/dist/interactions/actions/defaults/bendSegment.js +1 -1
  487. package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
  488. package/dist/interactions/actions/defaults/containerScroll.js +170 -0
  489. package/dist/interactions/actions/defaults/containerScroll.js.map +1 -0
  490. package/dist/interactions/actions/defaults/contextMenu.js +2 -5
  491. package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
  492. package/dist/interactions/actions/defaults/delete.js +1 -1
  493. package/dist/interactions/actions/defaults/delete.js.map +1 -1
  494. package/dist/interactions/actions/defaults/duplicate.js +1 -3
  495. package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
  496. package/dist/interactions/actions/defaults/enterGroup.js +7 -12
  497. package/dist/interactions/actions/defaults/enterGroup.js.map +1 -1
  498. package/dist/interactions/actions/defaults/enterPathEdit.js +4 -8
  499. package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
  500. package/dist/interactions/actions/defaults/group.js +12 -24
  501. package/dist/interactions/actions/defaults/group.js.map +1 -1
  502. package/dist/interactions/actions/defaults/ingest.js +18 -10
  503. package/dist/interactions/actions/defaults/ingest.js.map +1 -1
  504. package/dist/interactions/actions/defaults/insertPathAnchor.js +3 -3
  505. package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
  506. package/dist/interactions/actions/defaults/nudgeSteps.js +3 -21
  507. package/dist/interactions/actions/defaults/nudgeSteps.js.map +1 -1
  508. package/dist/interactions/actions/defaults/pathEditMarks.js.map +1 -1
  509. package/dist/interactions/actions/defaults/selectDeep.js +2 -2
  510. package/dist/interactions/actions/defaults/selectDeep.js.map +1 -1
  511. package/dist/interactions/actions/distribute/distribute.js.map +1 -1
  512. package/dist/interactions/actions/dropVerdict.js +9 -0
  513. package/dist/interactions/actions/dropVerdict.js.map +1 -0
  514. package/dist/interactions/actions/edit-anchors/action.js +2 -2
  515. package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
  516. package/dist/interactions/actions/edit-anchors/extensionDrags.js.map +1 -1
  517. package/dist/interactions/actions/edit-anchors/geometry.js +8 -6
  518. package/dist/interactions/actions/edit-anchors/geometry.js.map +1 -1
  519. package/dist/interactions/actions/edit-anchors/handleDrag.js.map +1 -1
  520. package/dist/interactions/actions/edit-anchors/preview.js +1 -1
  521. package/dist/interactions/actions/edit-anchors/preview.js.map +1 -1
  522. package/dist/interactions/actions/flip/action.js +3 -2
  523. package/dist/interactions/actions/flip/action.js.map +1 -1
  524. package/dist/interactions/actions/geometryProjection.js +8 -3
  525. package/dist/interactions/actions/geometryProjection.js.map +1 -1
  526. package/dist/interactions/actions/gestureFrames.js +1 -0
  527. package/dist/interactions/actions/gestureFrames.js.map +1 -1
  528. package/dist/interactions/actions/insert/action.js +6 -2
  529. package/dist/interactions/actions/insert/action.js.map +1 -1
  530. package/dist/interactions/actions/insert/frame.js +1 -1
  531. package/dist/interactions/actions/insert/frame.js.map +1 -1
  532. package/dist/interactions/actions/insert/placeAction.js +6 -8
  533. package/dist/interactions/actions/insert/placeAction.js.map +1 -1
  534. package/dist/interactions/actions/insert/session.js +2 -2
  535. package/dist/interactions/actions/insert/session.js.map +1 -1
  536. package/dist/interactions/actions/move/action.js +6 -15
  537. package/dist/interactions/actions/move/action.js.map +1 -1
  538. package/dist/interactions/actions/move/behaviors/layoutDrop.js +12 -2
  539. package/dist/interactions/actions/move/behaviors/layoutDrop.js.map +1 -1
  540. package/dist/interactions/actions/move/behaviors/reparentOnDrop.js +29 -10
  541. package/dist/interactions/actions/move/behaviors/reparentOnDrop.js.map +1 -1
  542. package/dist/interactions/actions/move/behaviors/snapToContainer.js +42 -6
  543. package/dist/interactions/actions/move/behaviors/snapToContainer.js.map +1 -1
  544. package/dist/interactions/actions/move/capture.js +4 -8
  545. package/dist/interactions/actions/move/capture.js.map +1 -1
  546. package/dist/interactions/actions/move/commit/index.js.map +1 -1
  547. package/dist/interactions/actions/move/commit/layoutDrop.js +4 -3
  548. package/dist/interactions/actions/move/commit/layoutDrop.js.map +1 -1
  549. package/dist/interactions/actions/move/commit/layoutRelease.js +15 -9
  550. package/dist/interactions/actions/move/commit/layoutRelease.js.map +1 -1
  551. package/dist/interactions/actions/move/commit/reparent.js +19 -13
  552. package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
  553. package/dist/interactions/actions/move/delta.js +51 -5
  554. package/dist/interactions/actions/move/delta.js.map +1 -1
  555. package/dist/interactions/actions/move/frame.js +8 -4
  556. package/dist/interactions/actions/move/frame.js.map +1 -1
  557. package/dist/interactions/actions/move/layout/container.js +19 -9
  558. package/dist/interactions/actions/move/layout/container.js.map +1 -1
  559. package/dist/interactions/actions/move/layout/dragged.js +2 -2
  560. package/dist/interactions/actions/move/layout/dragged.js.map +1 -1
  561. package/dist/interactions/actions/move/layout/dropPass.js +36 -18
  562. package/dist/interactions/actions/move/layout/dropPass.js.map +1 -1
  563. package/dist/interactions/actions/move/layout/exits.js +2 -2
  564. package/dist/interactions/actions/move/layout/exits.js.map +1 -1
  565. package/dist/interactions/actions/move/layout/glide.js +5 -5
  566. package/dist/interactions/actions/move/layout/glide.js.map +1 -1
  567. package/dist/interactions/actions/move/layout/view.js +55 -0
  568. package/dist/interactions/actions/move/layout/view.js.map +1 -0
  569. package/dist/interactions/actions/move/refusal.js +52 -0
  570. package/dist/interactions/actions/move/refusal.js.map +1 -0
  571. package/dist/interactions/actions/move/session.js +9 -8
  572. package/dist/interactions/actions/move/session.js.map +1 -1
  573. package/dist/interactions/actions/move/translate.js +10 -4
  574. package/dist/interactions/actions/move/translate.js.map +1 -1
  575. package/dist/interactions/actions/moveNodes.js +21 -5
  576. package/dist/interactions/actions/moveNodes.js.map +1 -1
  577. package/dist/interactions/actions/pathCommands/pathNodes.js +3 -3
  578. package/dist/interactions/actions/pathCommands/pathNodes.js.map +1 -1
  579. package/dist/interactions/actions/planeInput.js +14 -13
  580. package/dist/interactions/actions/planeInput.js.map +1 -1
  581. package/dist/interactions/actions/poseFrame.js +21 -8
  582. package/dist/interactions/actions/poseFrame.js.map +1 -1
  583. package/dist/interactions/actions/resize/action.js +1 -1
  584. package/dist/interactions/actions/resize/action.js.map +1 -1
  585. package/dist/interactions/actions/resize/aspect.js +1 -1
  586. package/dist/interactions/actions/resize/aspect.js.map +1 -1
  587. package/dist/interactions/actions/resize/commit.js +10 -1
  588. package/dist/interactions/actions/resize/commit.js.map +1 -1
  589. package/dist/interactions/actions/resize/frame.js +3 -2
  590. package/dist/interactions/actions/resize/frame.js.map +1 -1
  591. package/dist/interactions/actions/resize/held.js +44 -0
  592. package/dist/interactions/actions/resize/held.js.map +1 -0
  593. package/dist/interactions/actions/resize/session.js +3 -1
  594. package/dist/interactions/actions/resize/session.js.map +1 -1
  595. package/dist/interactions/actions/resolveOverlays.js +9 -0
  596. package/dist/interactions/actions/resolveOverlays.js.map +1 -1
  597. package/dist/interactions/actions/resolvePreviews.js +23 -7
  598. package/dist/interactions/actions/resolvePreviews.js.map +1 -1
  599. package/dist/interactions/actions/rotate/action.js +1 -1
  600. package/dist/interactions/actions/rotate/action.js.map +1 -1
  601. package/dist/interactions/actions/rotate/frame.js +1 -1
  602. package/dist/interactions/actions/rotate/frame.js.map +1 -1
  603. package/dist/interactions/actions/rotate/session.js +2 -2
  604. package/dist/interactions/actions/rotate/session.js.map +1 -1
  605. package/dist/interactions/actions/selectionSubtree.js +2 -4
  606. package/dist/interactions/actions/selectionSubtree.js.map +1 -1
  607. package/dist/interactions/actions/spatialReference.js +4 -9
  608. package/dist/interactions/actions/spatialReference.js.map +1 -1
  609. package/dist/interactions/gestures/dragRadial/dragRadial.js.map +1 -1
  610. package/dist/interactions/gestures/dragRect/dragRect.js.map +1 -1
  611. package/dist/kitSettingsSchema.js +2 -2
  612. package/dist/kitSettingsSchema.js.map +1 -1
  613. package/dist/{math-C_elShP_.d.ts → math-B_unOnF4.d.ts} +2 -2
  614. package/dist/math.d.ts +3 -3
  615. package/dist/move.d.ts +6 -6
  616. package/dist/{options-ClEm3SEl.d.ts → options-DAjkhbcs.d.ts} +5 -5
  617. package/dist/{poseDescriptor-DN2yfnq_.d.ts → poseDescriptor-CvSxVhCd.d.ts} +17 -4
  618. package/dist/resize.d.ts +6 -6
  619. package/dist/style.css +14 -0
  620. package/dist/test-seams.d.ts +11 -15
  621. package/dist/test-seams.js +1 -1
  622. package/dist/tools/builtin/eyedropper/useEyedropperTool.js +3 -6
  623. package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
  624. package/dist/tools/builtin/line/useLineTool.js.map +1 -1
  625. package/dist/tools/builtin/measure/useMeasureTool.js +2 -2
  626. package/dist/tools/builtin/measure/useMeasureTool.js.map +1 -1
  627. package/dist/tools/builtin/pen/penHandle.js +2 -2
  628. package/dist/tools/builtin/pen/penHandle.js.map +1 -1
  629. package/dist/tools/builtin/pen/usePenTool.js +13 -19
  630. package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
  631. package/dist/tools/builtin/polygon/usePolygonTool.js +1 -1
  632. package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
  633. package/dist/tools/builtin/select/groupEntry.js +1 -1
  634. package/dist/tools/builtin/select/groupEntry.js.map +1 -1
  635. package/dist/tools/builtin/select/selectionContributions.js +1 -1
  636. package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
  637. package/dist/tools/builtin/select/useSelectTool.js +7 -7
  638. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  639. package/dist/tools/builtin/slice/useSliceTool.js +5 -8
  640. package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
  641. package/dist/tools/builtin/star/useStarTool.js +1 -1
  642. package/dist/tools/builtin/star/useStarTool.js.map +1 -1
  643. package/dist/tools/index.js +2 -1
  644. package/dist/{types-49uYJN3j.d.ts → types-A_sNJYRd.d.ts} +8 -0
  645. package/dist/{types-BEdRIhNc.d.ts → types-DW14eu9B.d.ts} +131 -36
  646. package/dist/{types-p2alu-R1.d.ts → types-VCtfchp-.d.ts} +4 -6
  647. package/dist/util/color.js +12 -14
  648. package/dist/util/color.js.map +1 -1
  649. package/dist/version.js +1 -1
  650. package/package.json +25 -24
  651. package/dist/canvas/poseCascade.js +0 -65
  652. package/dist/canvas/poseCascade.js.map +0 -1
  653. package/dist/features/chrome-caps/buildChromeCtx.js.map +0 -1
  654. package/dist/features/chrome-caps/conditions.js.map +0 -1
  655. package/dist/features/chrome-caps/defaults.js.map +0 -1
  656. package/dist/features/chrome-caps/resolve.js.map +0 -1
  657. package/dist/features/chrome-caps/useChromeCaps.js.map +0 -1
  658. package/dist/features/chrome-caps/useHoverTracking.js.map +0 -1
  659. package/dist/features/ingestion/contentHandlers.js.map +0 -1
  660. package/dist/features/ingestion/imageHandler.js.map +0 -1
  661. package/dist/features/ingestion/openFilePicker.js.map +0 -1
  662. package/dist/features/ingestion/placement.js.map +0 -1
  663. package/dist/features/ingestion/registerKitHandlers.js +0 -43
  664. package/dist/features/ingestion/registerKitHandlers.js.map +0 -1
  665. package/dist/features/ingestion/svgHandler.js.map +0 -1
  666. package/dist/features/ingestion/textHandler.js.map +0 -1
  667. package/dist/features/ingestion/useCanvasIngestion.js.map +0 -1
  668. package/dist/features/ingestion/weaselJsonHandler.js.map +0 -1
  669. package/dist/features/longPress/longPressFeedback.js.map +0 -1
  670. package/dist/features/measure/actionIds.js.map +0 -1
  671. package/dist/features/measure/measureCommands.js.map +0 -1
  672. package/dist/features/measure/measureContribution.js.map +0 -1
  673. package/dist/features/measure/measureEdits.js.map +0 -1
  674. package/dist/features/measure/measureGestures.js.map +0 -1
  675. package/dist/features/measure/measureHit.js.map +0 -1
  676. package/dist/features/measure/measureLayer.js.map +0 -1
  677. package/dist/features/measure/measurement.js.map +0 -1
  678. package/dist/features/measure/measurements.js.map +0 -1
  679. package/dist/features/modes/modeLayers.js.map +0 -1
  680. package/dist/features/modes/modeTriggers.js.map +0 -1
  681. package/dist/features/pathEffects/effectSources.js.map +0 -1
  682. package/dist/features/pathEffects/index.js.map +0 -1
  683. package/dist/features/pathEffects/readSource.js.map +0 -1
  684. package/dist/features/pathEffects/textItems.js.map +0 -1
  685. package/dist/features/pathEffects/useEffectSourcesLayer.js.map +0 -1
  686. package/dist/features/paths/anchorData.js.map +0 -1
  687. package/dist/features/paths/anchorMarkers.js.map +0 -1
  688. package/dist/features/paths/editablePath.js.map +0 -1
  689. package/dist/features/paths/originProjection.js.map +0 -1
  690. package/dist/features/paths/pathEditExtension.js.map +0 -1
  691. package/dist/features/paths/pathEditState.js.map +0 -1
  692. package/dist/features/paths/pathEditingOverlayLayer.js.map +0 -1
  693. package/dist/features/paths/pathLayer.js.map +0 -1
  694. package/dist/features/paths/pathRepresentation.js.map +0 -1
  695. package/dist/features/paths/pathSnap/glyphs.js.map +0 -1
  696. package/dist/features/paths/pathSnap/pathSnapSource.js.map +0 -1
  697. package/dist/features/paths/pathSnap/sceneIndex.js.map +0 -1
  698. package/dist/features/paths/pathSnap/snapGeometry.js.map +0 -1
  699. package/dist/features/paths/penPreviewLayer.js.map +0 -1
  700. package/dist/features/paths/shapeGenerator.js.map +0 -1
  701. package/dist/features/paths/strokeOutline.js.map +0 -1
  702. package/dist/features/paths/useAnchorFields.js.map +0 -1
  703. package/dist/features/paths/usePathEdit.js.map +0 -1
  704. package/dist/features/poseRun/usePoseRun.js.map +0 -1
  705. package/dist/features/selection/enteredGroupFrame.js.map +0 -1
  706. package/dist/features/selection/enteredGroupScope.js.map +0 -1
  707. package/dist/features/selection/handleCommands.js.map +0 -1
  708. package/dist/features/selection/overlay.js.map +0 -1
  709. package/dist/features/selection/selectionPose.js.map +0 -1
  710. package/dist/features/snap/angle2d.js.map +0 -1
  711. package/dist/features/snap/debugCandidates.js.map +0 -1
  712. package/dist/features/snap/gather.js.map +0 -1
  713. package/dist/features/snap/inputSubspace.js.map +0 -1
  714. package/dist/features/snap/lazySession.js.map +0 -1
  715. package/dist/features/snap/marks.js.map +0 -1
  716. package/dist/features/snap/outcomeStore.js.map +0 -1
  717. package/dist/features/snap/pointSnap.js.map +0 -1
  718. package/dist/features/snap/probes2d.js.map +0 -1
  719. package/dist/features/snap/readoutDraw.js.map +0 -1
  720. package/dist/features/snap/readoutLayer.js.map +0 -1
  721. package/dist/features/snap/registry.js.map +0 -1
  722. package/dist/features/snap/resizeBox.js.map +0 -1
  723. package/dist/features/snap/roles.js.map +0 -1
  724. package/dist/features/snap/rotationSource.js.map +0 -1
  725. package/dist/features/snap/sceneReads.js.map +0 -1
  726. package/dist/features/snap/sceneSession2d.js.map +0 -1
  727. package/dist/features/snap/screen.js.map +0 -1
  728. package/dist/features/snap/session2d.js.map +0 -1
  729. package/dist/features/snap/spanSnap.js.map +0 -1
  730. package/dist/features/snap/stepAngles.js.map +0 -1
  731. package/dist/features/text/editingDrawOne.js.map +0 -1
  732. package/dist/features/text/sceneTextDrafts.js.map +0 -1
  733. package/dist/features/text/textHitAt.js.map +0 -1
  734. package/dist/features/text/textLayer.js.map +0 -1
  735. package/dist/features/text/useSceneTextEdit.js.map +0 -1
  736. package/dist/features/viewports/aim.js.map +0 -1
  737. package/dist/features/viewports/viewMarker.js.map +0 -1
  738. package/dist/features/viewports/viewMarkerDrag.js.map +0 -1
  739. package/dist/features/viewports/viewResolver.js.map +0 -1
  740. package/dist/features/viewports/viewportLayer.js.map +0 -1
  741. /package/dist/{features → domains}/chrome-caps/index.js +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"file":"useChromeCaps.js","names":[],"sources":["../../../src/features/chrome-caps/useChromeCaps.ts"],"sourcesContent":["/**\n * The rule context a surface's chrome-visibility rules and action eligibility\n * are evaluated against, and the hook that keeps one for `<SceneCanvas>`.\n */\nimport { useMemo } from 'react';\nimport type { ActiveMode } from '@weasel-js/modes';\nimport { useLatest } from '@weasel-js/react';\nimport {\n buildRuleCtx,\n DEFAULT_ALLOWED_CAPABILITIES,\n type DeviceProfile,\n type ModifierState,\n type RuleCtx,\n} from '@weasel-js/routing';\nimport type { NodeId } from '../../core/scene/types';\nimport { viewZoom, type View } from '../../core/viewport/view';\nimport { resolveVisibility } from './resolve';\nimport type { VisibilityRules } from './types';\n\n/**\n * The part of a rule context that belongs to one view. Everything else a\n * `RuleCtx` carries — mode, capabilities, device, focus — is the surface's and\n * the same for every view drawn on it.\n */\nexport interface ViewRuleInputs {\n selection: readonly NodeId[];\n view: View;\n /** What that view's dispatcher currently has in flight. */\n action: { kind: string | null; id: string | null };\n}\n\n/** The part of a rule context that belongs to the surface. */\nexport interface SurfaceRuleInputs {\n focused: boolean;\n /** The modifier keys held. Absent means none, so `modifierHeld` never matches. */\n modifiers?: ModifierState | undefined;\n hover: NodeId | null;\n /** Absent on a surface with no mode registry, which behaves as normal mode. */\n mode: ActiveMode | undefined;\n /** Whether one node may be resized. Absent means every node may. */\n resizable: ((id: string) => boolean) | undefined;\n editingAnchors: boolean;\n device: DeviceProfile;\n}\n\nconst NO_MODIFIERS = { alt: false, ctrl: false, meta: false, shift: false };\n\n/** One view's rule context on a surface. */\nexport function surfaceRuleCtx(surface: SurfaceRuleInputs, view: ViewRuleInputs): RuleCtx {\n const { resizable } = surface;\n return buildRuleCtx({\n focused: surface.focused,\n selection: view.selection,\n multiActive: view.selection.length > 1,\n modifiers: surface.modifiers ?? NO_MODIFIERS,\n action: view.action,\n hover: surface.hover,\n zoom: viewZoom(view.view),\n mode: surface.mode?.id ?? 'normal',\n // An empty set would make every `capability:` rule false and hide the\n // chrome those rules gate.\n allowedCapabilities: surface.mode?.allowedCapabilities ?? DEFAULT_ALLOWED_CAPABILITIES,\n // Left undefined without a predicate, which `resizable:` reads as resizable.\n selectionResizable: resizable ? view.selection.every((id) => resizable(id)) : undefined,\n editingAnchors: surface.editingAnchors,\n device: surface.device,\n });\n}\n\n/** Where a surface's rule context comes from. Read at the moment a rule is\n * evaluated, never at render. */\nexport interface ChromeCapsSource {\n /** The surface's rule table, over the kit defaults. */\n rules: VisibilityRules | undefined;\n /** The active mode. Absent when no mode registry is wired. */\n getMode: (() => ActiveMode) | undefined;\n /** Absent means the surface always counts as focused. */\n getFocused: (() => boolean) | undefined;\n getHover: () => NodeId | null;\n /** Absent means no modifier key ever counts as held. */\n getModifiers?: (() => ModifierState) | undefined;\n resizable: ((id: string) => boolean) | undefined;\n editingAnchors: () => boolean;\n device: DeviceProfile;\n}\n\n/**\n * A surface's chrome-caps answers for one view. The rules are surface-wide on\n * purpose — one consumer table, one set of kit defaults — but a rule keyed on\n * selection, camera or the in-flight action answers differently per view, so\n * the caller supplies that part and both the paint gate and the dispatcher's\n * eligibility filter read the same context.\n */\nexport interface ChromeCaps {\n /** `undefined` when the surface gates nothing: no mode registry is wired. */\n ruleCtx(inputs: ViewRuleInputs): RuleCtx | undefined;\n isVisible(inputs: ViewRuleInputs): (id: string) => boolean;\n}\n\n/** The chrome-caps answers for a surface. The object keeps its identity for\n * the life of the component and reads `source` as of the last commit. */\nexport function useChromeCaps(source: ChromeCapsSource): ChromeCaps {\n const live = useLatest(source);\n return useMemo(() => {\n const build = (inputs: ViewRuleInputs): RuleCtx => {\n const now = live.current;\n return surfaceRuleCtx({\n focused: now.getFocused ? now.getFocused() : true,\n modifiers: now.getModifiers?.(),\n hover: now.getHover(),\n mode: now.getMode?.(),\n resizable: now.resizable,\n editingAnchors: now.editingAnchors(),\n device: now.device,\n }, inputs);\n };\n return {\n ruleCtx: (inputs) => (live.current.getMode ? build(inputs) : undefined),\n isVisible: (inputs) => resolveVisibility(live.current.rules, build(inputs)),\n };\n }, [live]);\n}\n"],"mappings":";;;;;;;;;;;AA6CA,IAAM,eAAe;CAAE,KAAK;CAAO,MAAM;CAAO,MAAM;CAAO,OAAO;AAAM;;AAG1E,SAAgB,eAAe,SAA4B,MAA+B;CACxF,MAAM,EAAE,cAAc;CACtB,OAAO,aAAa;EAClB,SAAS,QAAQ;EACjB,WAAW,KAAK;EAChB,aAAa,KAAK,UAAU,SAAS;EACrC,WAAW,QAAQ,aAAa;EAChC,QAAQ,KAAK;EACb,OAAO,QAAQ;EACf,MAAM,SAAS,KAAK,IAAI;EACxB,MAAM,QAAQ,MAAM,MAAM;EAG1B,qBAAqB,QAAQ,MAAM,uBAAuB;EAE1D,oBAAoB,YAAY,KAAK,UAAU,OAAO,OAAO,UAAU,EAAE,CAAC,IAAI;EAC9E,gBAAgB,QAAQ;EACxB,QAAQ,QAAQ;CAClB,CAAC;AACH;;;AAkCA,SAAgB,cAAc,QAAsC;CAClE,MAAM,OAAO,UAAU,MAAM;CAC7B,OAAO,cAAc;EACnB,MAAM,SAAS,WAAoC;GACjD,MAAM,MAAM,KAAK;GACjB,OAAO,eAAe;IACpB,SAAS,IAAI,aAAa,IAAI,WAAW,IAAI;IAC7C,WAAW,IAAI,eAAe;IAC9B,OAAO,IAAI,SAAS;IACpB,MAAM,IAAI,UAAU;IACpB,WAAW,IAAI;IACf,gBAAgB,IAAI,eAAe;IACnC,QAAQ,IAAI;GACd,GAAG,MAAM;EACX;EACA,OAAO;GACL,UAAU,WAAY,KAAK,QAAQ,UAAU,MAAM,MAAM,IAAI;GAC7D,YAAY,WAAW,kBAAkB,KAAK,QAAQ,OAAO,MAAM,MAAM,CAAC;EAC5E;CACF,GAAG,CAAC,IAAI,CAAC;AACX"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"useHoverTracking.js","names":[],"sources":["../../../src/features/chrome-caps/useHoverTracking.ts"],"sourcesContent":["/** Last-hovered NodeId for chrome-caps rules. */\n\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { useLatest } from '@weasel-js/react';\nimport type { NodeId } from '../../core/scene/types';\n\n/** Options for `useHoverTracking`. */\nexport interface UseHoverTrackingArgs {\n /** Topmost id under a client point, or null. */\n nodeAtClientPoint: (clientX: number, clientY: number) => { id: NodeId } | null;\n enabled?: boolean;\n /** Called when the hovered id changes, `null` once the pointer is over no node or has left. */\n onChange?: (id: NodeId | null) => void;\n}\n\n/** What `useHoverTracking` returns. Both members keep their identity. */\nexport interface HoverTracking {\n /** The node the pointer is over, or null. */\n getHover: () => NodeId | null;\n /** Hand it the element to track, or null to stop: a callback ref, so an\n * element that mounts after the hook is tracked from the moment it arrives. */\n canvasRef: (element: HTMLElement | null) => void;\n}\n\n/** Track which node the pointer is over. Returns a getter rather than state,\n * so a pointer move does not re-render the component. Cleared on\n * `pointerleave`. */\nexport function useHoverTracking(args: UseHoverTrackingArgs): HoverTracking {\n const { nodeAtClientPoint, enabled = true } = args;\n const hoverRef = useRef<NodeId | null>(null);\n const nodeAtRef = useLatest(nodeAtClientPoint);\n const onChangeRef = useLatest(args.onChange);\n const elementRef = useRef<HTMLElement | null>(null);\n const enabledRef = useRef(enabled);\n const detachRef = useRef<(() => void) | null>(null);\n\n const hover = useCallback((id: NodeId | null) => {\n if (hoverRef.current === id) return;\n hoverRef.current = id;\n onChangeRef.current?.(id);\n }, [onChangeRef]);\n\n const sync = useCallback(() => {\n detachRef.current?.();\n detachRef.current = null;\n hover(null);\n const el = elementRef.current;\n if (!el || !enabledRef.current) return;\n const onMove = (e: PointerEvent) => hover(nodeAtRef.current(e.clientX, e.clientY)?.id ?? null);\n const onLeave = () => hover(null);\n el.addEventListener('pointermove', onMove);\n el.addEventListener('pointerleave', onLeave);\n detachRef.current = () => {\n el.removeEventListener('pointermove', onMove);\n el.removeEventListener('pointerleave', onLeave);\n };\n }, [nodeAtRef, hover]);\n\n useEffect(() => {\n enabledRef.current = enabled;\n sync();\n return () => {\n detachRef.current?.();\n detachRef.current = null;\n hover(null);\n };\n }, [enabled, sync, hover]);\n\n const canvasRef = useCallback((element: HTMLElement | null) => {\n if (elementRef.current === element) return;\n elementRef.current = element;\n sync();\n }, [sync]);\n const getHover = useCallback(() => hoverRef.current, []);\n\n return useMemo(() => ({ getHover, canvasRef }), [getHover, canvasRef]);\n}\n"],"mappings":";;;;;;;;AA2BA,SAAgB,iBAAiB,MAA2C;CAC1E,MAAM,EAAE,mBAAmB,UAAU,SAAS;CAC9C,MAAM,WAAW,OAAsB,IAAI;CAC3C,MAAM,YAAY,UAAU,iBAAiB;CAC7C,MAAM,cAAc,UAAU,KAAK,QAAQ;CAC3C,MAAM,aAAa,OAA2B,IAAI;CAClD,MAAM,aAAa,OAAO,OAAO;CACjC,MAAM,YAAY,OAA4B,IAAI;CAElD,MAAM,QAAQ,aAAa,OAAsB;EAC/C,IAAI,SAAS,YAAY,IAAI;EAC7B,SAAS,UAAU;EACnB,YAAY,UAAU,EAAE;CAC1B,GAAG,CAAC,WAAW,CAAC;CAEhB,MAAM,OAAO,kBAAkB;EAC7B,UAAU,UAAU;EACpB,UAAU,UAAU;EACpB,MAAM,IAAI;EACV,MAAM,KAAK,WAAW;EACtB,IAAI,CAAC,MAAM,CAAC,WAAW,SAAS;EAChC,MAAM,UAAU,MAAoB,MAAM,UAAU,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,IAAI;EAC7F,MAAM,gBAAgB,MAAM,IAAI;EAChC,GAAG,iBAAiB,eAAe,MAAM;EACzC,GAAG,iBAAiB,gBAAgB,OAAO;EAC3C,UAAU,gBAAgB;GACxB,GAAG,oBAAoB,eAAe,MAAM;GAC5C,GAAG,oBAAoB,gBAAgB,OAAO;EAChD;CACF,GAAG,CAAC,WAAW,KAAK,CAAC;CAErB,gBAAgB;EACd,WAAW,UAAU;EACrB,KAAK;EACL,aAAa;GACX,UAAU,UAAU;GACpB,UAAU,UAAU;GACpB,MAAM,IAAI;EACZ;CACF,GAAG;EAAC;EAAS;EAAM;CAAK,CAAC;CAEzB,MAAM,YAAY,aAAa,YAAgC;EAC7D,IAAI,WAAW,YAAY,SAAS;EACpC,WAAW,UAAU;EACrB,KAAK;CACP,GAAG,CAAC,IAAI,CAAC;CACT,MAAM,WAAW,kBAAkB,SAAS,SAAS,CAAC,CAAC;CAEvD,OAAO,eAAe;EAAE;EAAU;CAAU,IAAI,CAAC,UAAU,SAAS,CAAC;AACvE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"contentHandlers.js","names":[],"sources":["../../../src/features/ingestion/contentHandlers.ts"],"sourcesContent":["/**\n * Content-handler registry for external-content ingestion.\n *\n * All three arrival paths (OS drop, clipboard paste, file picker /\n * `CanvasExtensionApi.ingest`) converge here: `runIngest(items, ctx)`\n * partitions the items across registered handlers in priority order — the\n * first matching handler takes every item it matches; leftovers continue\n * down the list.\n *\n * MIME matching delegates to `mimeMatchesGlob` from `@weasel-js/gestures` —\n * the SAME matcher the binding-level `types` filter uses, so a binding that\n * matched can never disagree with the registry about what a glob means.\n */\nimport { mimeMatchesGlob, UNKNOWN_MIME } from '@weasel-js/gestures';\nimport { dwarn } from '../../debug';\nimport type { Scene } from 'core/scene/types';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport type { Op } from 'core/ops/types';\nimport type { ClipboardIngestCtx, InsertDep, SnapDep, SvgIngestOptions } from 'interactions/actions/depSchema';\nimport { admitIngestMime, type ActionDeps, type IngestItem } from '@weasel-js/routing';\n\n/** Context handed to every content handler for one ingest event. */\nexport interface IngestCtx {\n /** World-space arrival point (drop / pointed imperative ingest); `null`\n * for paste and point-less calls — handlers pick their own policy\n * (the kit image handler centers on the viewport). */\n point: { x: number; y: number } | null;\n /** Visible canvas area in world coordinates. */\n viewportWorldRect(): { x: number; y: number; width: number; height: number };\n /** The kit insert dep — id/layer/undoable-op supplied; the canonical way\n * for a handler to mint a node (`insert.commit(bounds, { kind, ... })`). */\n insert: InsertDep;\n /** The canvas's snap dep, for a handler that places what it inserts where a\n * gesture would: begin a session, take `at()`, `end()`. Its points are ctx\n * coordinates: world on a 2D canvas, screen in a 3D host. */\n snap?: SnapDep;\n /** Raw op commit for handlers that build their own ops. */\n applyOps(ops: Op[], label?: string): void;\n scene: Scene<unknown, string, unknown>;\n selection: SelectionApi;\n /** Consumer file→src resolver (SceneCanvas `ingestion.resolveSrc`).\n * When absent, the kit image handler embeds as a `data:` URI. */\n resolveSrc?: (file: File) => Promise<string>;\n /** Kit SVG-handler options (SceneCanvas `ingestion.svg`) — e.g.\n * `{ unpack: unpackSvgFiles }` (from `@weasel-js/svg`) to parse SVG files\n * into scene nodes. */\n svg?: SvgIngestOptions;\n /** Clipboard-paste seam — present when the hosting `SceneCanvas` supplied\n * an adapter with `commitPaste`. `reviver` comes from\n * `SceneCanvasProps.ingestion.clipboard`. Absent ⇒ the kit weasel-JSON\n * handler declines inert (dwarn, nothing ingested) — its matched items\n * were already consumed at match time, so they do NOT fall through;\n * only match-level misses flow on to other handlers. */\n clipboard?: ClipboardIngestCtx;\n /** Set to `true` by the kit weasel-JSON handler when it successfully\n * pastes a payload in this event. `kit:svg`'s `text/plain` SVG fallback\n * reads this to decline the SVG flavor of a copy whose weasel-JSON flavor\n * already ingested (avoids a double-paste when both flavors ride one\n * clipboard event). */\n consumedWeaselPayload?: boolean;\n /** Full action-deps bag, for consumer handlers that need more. */\n deps: ActionDeps;\n}\n\n/** A handler for content arriving by paste, drop or file picker. Handlers are\n * matched by MIME glob or predicate and run highest-priority first; the kit's\n * own register at a low priority so a consumer's handler wins by default. */\nexport interface ContentHandlerEntry {\n /** Stable identifier — used for unregistration and debugging\n * (`'kit:image'`, `'app:csv'`). */\n id: string;\n /** MIME glob(s) (`'image/*'`, `'text/csv'`) or an item predicate. A drop\n * or paste keeps string flavors of any exact MIME named here or in\n * `types` while the handler is registered, so a handler can claim a drag\n * from the app's own UI by a type of its own. */\n match: string | string[] | ((item: IngestItem) => boolean);\n /** For a predicate `match`: the MIME globs it could say yes to. A drag\n * still in flight shows its MIMEs and no contents, so a predicate cannot be\n * asked about it; this is what it is asked instead. Without it, no drag\n * can be refused while this handler is registered. */\n types?: readonly string[];\n /** Higher runs earlier. Kit defaults register at -100 so any consumer\n * handler (default 0) beats them. */\n priority?: number;\n handle(items: IngestItem[], ctx: IngestCtx): void | Promise<void>;\n}\n\nconst HANDLERS: ContentHandlerEntry[] = [];\nconst RELEASES = new Map<ContentHandlerEntry, (() => void)[]>();\n\n/** The exact MIMEs a handler names, for string flavors to be kept. A glob\n * names no one flavor, and `application/octet-stream` is what an untyped\n * file reports, never a string flavor anyone meant. */\nfunction namedMimes(entry: ContentHandlerEntry): string[] {\n const named = [...(typeof entry.match === 'function' ? [] : [entry.match].flat()), ...(entry.types ?? [])];\n return [...new Set(named)].filter((m) => !m.includes('*') && m !== 'application/octet-stream');\n}\n\nfunction unregister(entry: ContentHandlerEntry): void {\n const i = HANDLERS.indexOf(entry);\n if (i < 0) return;\n HANDLERS.splice(i, 1);\n // The same entry may be registered more than once; each registration admitted once.\n const releases = RELEASES.get(entry);\n releases?.pop()?.();\n if (releases?.length === 0) RELEASES.delete(entry);\n}\n\n/** Register a content handler. Returns a disposer that removes it. */\nexport function registerContentHandler(entry: ContentHandlerEntry): () => void {\n HANDLERS.push(entry);\n const admits = namedMimes(entry).map(admitIngestMime);\n const releases = RELEASES.get(entry) ?? [];\n releases.push(() => admits.forEach((release) => release()));\n RELEASES.set(entry, releases);\n let removed = false;\n return () => {\n if (removed) return;\n removed = true;\n unregister(entry);\n };\n}\n\nfunction byPriority(handlers: readonly ContentHandlerEntry[]): ContentHandlerEntry[] {\n return [...handlers].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));\n}\n\n/** Snapshot of registered handlers, priority-ordered (stable within ties —\n * registration order; Array.prototype.sort is stable per ES2019). */\nexport function getContentHandlers(): readonly ContentHandlerEntry[] {\n return byPriority(HANDLERS);\n}\n\n/** Options for {@link runIngest}. */\nexport interface RunIngestOptions {\n /**\n * The handlers to route through, in place of the registry. The registry is\n * shared by every canvas on the page, so this is how one arrival path\n * accepts a narrower set of content than the page has handlers for.\n * Ordered by `priority` like registered handlers; an item none of them\n * matches is ignored.\n */\n handlers?: readonly ContentHandlerEntry[];\n}\n\nfunction entryMatches(entry: ContentHandlerEntry, item: IngestItem): boolean {\n const { match } = entry;\n if (typeof match === 'function') {\n try {\n return match(item);\n } catch (err) {\n console.warn(`weasel ingest: handler \"${entry.id}\" match predicate threw`, err);\n return false;\n }\n }\n const globs = Array.isArray(match) ? match : [match];\n return globs.some((g) => mimeMatchesGlob(item.mime, g));\n}\n\n/**\n * Whether some handler could claim one of `items`: the registry's, or\n * `handlers`. An item with contents is put to each handler's `match`. One\n * with a MIME alone, from a drag still in flight, is answered from the globs\n * and a predicate's declared `types`; it is never refused when its MIME is\n * `UNKNOWN_MIME`, or while a predicate handler declares no types.\n */\nexport function couldClaim(\n items: readonly IngestItem[],\n handlers: readonly ContentHandlerEntry[] = HANDLERS,\n): boolean {\n return items.some((item) => {\n if ((item as { kind?: string }).kind !== undefined) return handlers.some((h) => entryMatches(h, item));\n if (item.mime === UNKNOWN_MIME) return true;\n return handlers.some((h) => {\n const globs = typeof h.match === 'function' ? h.types : [h.match].flat();\n return globs === undefined || globs.some((g) => mimeMatchesGlob(item.mime, g));\n });\n });\n}\n\n/**\n * Route one ingest event's items through the registry. Handlers are queued\n * as microtasks in priority order and their async work then overlaps (each\n * already owns a disjoint item set); a throwing or rejecting handler\n * `console.warn`s and doesn't block the others. The queue order is a real\n * invariant: a higher-priority handler's `handle` body runs before lower\n * ones start, so ctx-flag coordination (e.g. `consumedWeaselPayload`)\n * works — but only while the writing handler stays synchronous up to the\n * write and the reading handler reads before its first `await`.\n * Unmatched items are ignored with a debug-gated `dwarn('ingest', ...)`.\n */\nexport async function runIngest(\n items: IngestItem[],\n ctx: IngestCtx,\n opts: RunIngestOptions = {},\n): Promise<void> {\n let remaining = items;\n const runs: Promise<void>[] = [];\n const handlers = opts.handlers ? byPriority(opts.handlers) : getContentHandlers();\n\n for (const entry of handlers) {\n if (remaining.length === 0) break;\n const mine = remaining.filter((it) => entryMatches(entry, it));\n if (mine.length === 0) continue;\n remaining = remaining.filter((it) => !mine.includes(it));\n runs.push(\n Promise.resolve()\n .then(() => entry.handle(mine, ctx))\n .catch((err) => {\n console.warn(`weasel ingest: handler \"${entry.id}\" threw`, err);\n }),\n );\n }\n\n if (remaining.length > 0) {\n dwarn(\n 'ingest',\n `no content handler matched ${remaining.length} item(s):`,\n remaining.map((it) => it.mime).join(', '),\n );\n }\n\n await Promise.all(runs);\n}\n\n/** @internal test seam — clear the registry. Not part of the public barrel. */\nexport function _resetContentHandlersForTests(): void {\n while (HANDLERS.length > 0) unregister(HANDLERS[0]!);\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"imageHandler.js","names":[],"sources":["../../../src/features/ingestion/imageHandler.ts"],"sourcesContent":["/**\n * The kit's `image/*` content handler. With no consumer `resolveSrc`, a file\n * embeds as a `data:` URI so the scene stays serializable.\n *\n * Registered at priority -100 so any consumer handler (default 0) can take\n * image files first.\n */\nimport type { ContentHandlerEntry, IngestCtx } from './contentHandlers';\nimport type { IngestItem } from '@weasel-js/routing';\nimport { arrivalBounds } from './placement';\n\nconst VIEWPORT_FIT = 0.9;\n\nexport type Measure = (file: File) => Promise<{ width: number; height: number }>;\ntype ToDataUri = (file: File) => Promise<string>;\n\nconst defaultMeasure: Measure = async (file) => {\n const bitmap = await createImageBitmap(file);\n const size = { width: bitmap.width, height: bitmap.height };\n bitmap.close();\n return size;\n};\n\nconst defaultToDataUri: ToDataUri = (file) =>\n new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(reader.result as string);\n reader.onerror = () => reject(reader.error);\n reader.readAsDataURL(file);\n });\n\nlet measure: Measure = defaultMeasure;\nlet toDataUri: ToDataUri = defaultToDataUri;\n\n/** @internal Options for {@link embedFilesAsImageNodes} — used by the kit\n * SVG handler, whose files can't go through `createImageBitmap`. */\nexport interface EmbedImageOptions {\n /** Replace the raster measurer for these files (the SVG handler supplies\n * an `Image`-element one). Defaults to the module measurer (seam-aware). */\n measure?: Measure;\n /** Force the data-URI MIME. A sniffed `.svg` whose `File.type` was empty\n * would otherwise embed as `data:application/octet-stream;…`, which\n * browsers refuse to rasterize. No effect on `resolveSrc` output. */\n mimeOverride?: string;\n}\n\n/**\n * @internal The shared embed engine: for each file, measure → resolve a\n * `src` → fit-clamp to 90% of the visible viewport → insert one image node\n * centered on `ctx.point` (or the viewport center), cascading multi-file\n * batches by a fixed offset. A file that fails to measure or resolve is\n * skipped with a `console.warn`; the rest proceed.\n */\nexport async function embedFilesAsImageNodes(\n files: File[],\n ctx: IngestCtx,\n opts: EmbedImageOptions = {},\n): Promise<void> {\n let index = 0;\n for (const file of files) {\n try {\n // Measure first: a corrupt file should fail cheaply here, not after\n // a consumer `resolveSrc` has already uploaded it somewhere.\n const natural = await (opts.measure ?? measure)(file);\n let src = ctx.resolveSrc ? await ctx.resolveSrc(file) : await toDataUri(file);\n if (!ctx.resolveSrc && opts.mimeOverride) {\n src = src.replace(/^data:[^;,]*/, `data:${opts.mimeOverride}`);\n }\n const view = ctx.viewportWorldRect();\n const scale = Math.min(\n 1,\n (view.width * VIEWPORT_FIT) / natural.width,\n (view.height * VIEWPORT_FIT) / natural.height,\n );\n const size = { width: natural.width * scale, height: natural.height * scale };\n ctx.insert.commit(arrivalBounds(ctx, size, index), { kind: 'image', src });\n index++;\n } catch (err) {\n console.warn(`weasel ingest: image \"${file.name}\" failed to load`, err);\n }\n }\n}\n\nexport const kitImageHandler: ContentHandlerEntry = {\n id: 'kit:image',\n match: (item: IngestItem) => item.kind === 'file' && item.mime.startsWith('image/'),\n types: ['image/*'],\n priority: -100,\n handle(items, ctx: IngestCtx) {\n const files = items.flatMap((it) => (it.kind === 'file' ? [it.file] : []));\n return embedFilesAsImageNodes(files, ctx);\n },\n};\n\n/** @internal test seam — override the bitmap measurer (jsdom has no\n * `createImageBitmap`). Not part of the public barrel. */\nexport function __setImageMeasureForTests(fn: Measure): void {\n measure = fn;\n}\n\n/** @internal test seam — override the FileReader embed. */\nexport function __setFileToDataUriForTests(fn: ToDataUri): void {\n toDataUri = fn;\n}\n\n/** @internal test seam — restore default seams. */\nexport function _resetImageHandlerSeamsForTests(): void {\n measure = defaultMeasure;\n toDataUri = defaultToDataUri;\n}\n"],"mappings":";;;AAWA,IAAM,eAAe;AAKrB,IAAM,iBAA0B,OAAO,SAAS;CAC9C,MAAM,SAAS,MAAM,kBAAkB,IAAI;CAC3C,MAAM,OAAO;EAAE,OAAO,OAAO;EAAO,QAAQ,OAAO;CAAO;CAC1D,OAAO,MAAM;CACb,OAAO;AACT;AAEA,IAAM,oBAA+B,SACnC,IAAI,SAAS,SAAS,WAAW;CAC/B,MAAM,SAAS,IAAI,WAAW;CAC9B,OAAO,eAAe,QAAQ,OAAO,MAAgB;CACrD,OAAO,gBAAgB,OAAO,OAAO,KAAK;CAC1C,OAAO,cAAc,IAAI;AAC3B,CAAC;AAEH,IAAI,UAAmB;AACvB,IAAI,YAAuB;;;;;;;;AAqB3B,eAAsB,uBACpB,OACA,KACA,OAA0B,CAAC,GACZ;CACf,IAAI,QAAQ;CACZ,KAAK,MAAM,QAAQ,OACjB,IAAI;EAGF,MAAM,UAAU,OAAO,KAAK,WAAW,QAAA,CAAS,IAAI;EACpD,IAAI,MAAM,IAAI,aAAa,MAAM,IAAI,WAAW,IAAI,IAAI,MAAM,UAAU,IAAI;EAC5E,IAAI,CAAC,IAAI,cAAc,KAAK,cAC1B,MAAM,IAAI,QAAQ,gBAAgB,QAAQ,KAAK,cAAc;EAE/D,MAAM,OAAO,IAAI,kBAAkB;EACnC,MAAM,QAAQ,KAAK,IACjB,GACC,KAAK,QAAQ,eAAgB,QAAQ,OACrC,KAAK,SAAS,eAAgB,QAAQ,MACzC;EACA,MAAM,OAAO;GAAE,OAAO,QAAQ,QAAQ;GAAO,QAAQ,QAAQ,SAAS;EAAM;EAC5E,IAAI,OAAO,OAAO,cAAc,KAAK,MAAM,KAAK,GAAG;GAAE,MAAM;GAAS;EAAI,CAAC;EACzE;CACF,SAAS,KAAK;EACZ,QAAQ,KAAK,yBAAyB,KAAK,KAAK,mBAAmB,GAAG;CACxE;AAEJ;AAEA,IAAa,kBAAuC;CAClD,IAAI;CACJ,QAAQ,SAAqB,KAAK,SAAS,UAAU,KAAK,KAAK,WAAW,QAAQ;CAClF,OAAO,CAAC,SAAS;CACjB,UAAU;CACV,OAAO,OAAO,KAAgB;EAE5B,OAAO,uBADO,MAAM,SAAS,OAAQ,GAAG,SAAS,SAAS,CAAC,GAAG,IAAI,IAAI,CAAC,CACzC,GAAO,GAAG;CAC1C;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"openFilePicker.js","names":[],"sources":["../../../src/features/ingestion/openFilePicker.ts"],"sourcesContent":["/** Options for {@link openFilePicker}. */\nexport interface OpenFilePickerOptions {\n /** `<input accept>` filter, e.g. `'image/*'` or `'.csv,text/csv'`. */\n accept?: string;\n /** Allow multi-select. Default false. */\n multiple?: boolean;\n}\n\n/**\n * Show the browser's file picker and resolve with what the user chose —\n * empty when they cancel. Must be called from a user gesture (a click\n * handler): browsers block programmatic `input.click()` otherwise. Pairs\n * with `CanvasExtensionApi.ingest`:\n *\n * ```ts\n * const files = await openFilePicker({ accept: 'image/*', multiple: true });\n * canvasRef.current?.ingest(files);\n * ```\n */\nexport function openFilePicker(opts: OpenFilePickerOptions = {}): Promise<File[]> {\n return new Promise((resolve) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.hidden = true;\n if (opts.accept) input.accept = opts.accept;\n if (opts.multiple) input.multiple = true;\n document.body.appendChild(input);\n const finish = (files: File[]) => {\n input.remove();\n resolve(files);\n };\n input.addEventListener('change', () => finish(Array.from(input.files ?? [])), { once: true });\n // Where `cancel` is unsupported, the hidden input leaks until the page\n // unloads.\n input.addEventListener('cancel', () => finish([]), { once: true });\n input.click();\n });\n}\n"],"mappings":";;;;;;;;;;;;AAmBA,SAAgB,eAAe,OAA8B,CAAC,GAAoB;CAChF,OAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,QAAQ,SAAS,cAAc,OAAO;EAC5C,MAAM,OAAO;EACb,MAAM,SAAS;EACf,IAAI,KAAK,QAAQ,MAAM,SAAS,KAAK;EACrC,IAAI,KAAK,UAAU,MAAM,WAAW;EACpC,SAAS,KAAK,YAAY,KAAK;EAC/B,MAAM,UAAU,UAAkB;GAChC,MAAM,OAAO;GACb,QAAQ,KAAK;EACf;EACA,MAAM,iBAAiB,gBAAgB,OAAO,MAAM,KAAK,MAAM,SAAS,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC;EAG5F,MAAM,iBAAiB,gBAAgB,OAAO,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC;EACjE,MAAM,MAAM;CACd,CAAC;AACH"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"placement.js","names":[],"sources":["../../../src/features/ingestion/placement.ts"],"sourcesContent":["import type { IngestCtx } from './contentHandlers';\n\nconst CASCADE_OFFSET_PX = 24;\n\n/**\n * Where the kit's handlers put arriving content of a given size: centered on\n * `ctx.point` (a drop, a pointed ingest) or on the viewport (a paste), with the\n * `index`-th item of one arrival stepped down and right of the first.\n */\nexport function arrivalBounds(\n ctx: IngestCtx,\n size: { width: number; height: number },\n index: number,\n): { x: number; y: number; width: number; height: number } {\n const view = ctx.viewportWorldRect();\n const center = ctx.point ?? { x: view.x + view.width / 2, y: view.y + view.height / 2 };\n const offset = index * CASCADE_OFFSET_PX;\n return {\n x: center.x - size.width / 2 + offset,\n y: center.y - size.height / 2 + offset,\n width: size.width,\n height: size.height,\n };\n}\n"],"mappings":";AAEA,IAAM,oBAAoB;;;;;;AAO1B,SAAgB,cACd,KACA,MACA,OACyD;CACzD,MAAM,OAAO,IAAI,kBAAkB;CACnC,MAAM,SAAS,IAAI,SAAS;EAAE,GAAG,KAAK,IAAI,KAAK,QAAQ;EAAG,GAAG,KAAK,IAAI,KAAK,SAAS;CAAE;CACtF,MAAM,SAAS,QAAQ;CACvB,OAAO;EACL,GAAG,OAAO,IAAI,KAAK,QAAQ,IAAI;EAC/B,GAAG,OAAO,IAAI,KAAK,SAAS,IAAI;EAChC,OAAO,KAAK;EACZ,QAAQ,KAAK;CACf;AACF"}
@@ -1,43 +0,0 @@
1
- import { kitImageHandler } from "./imageHandler.js";
2
- import { kitSvgHandler } from "./svgHandler.js";
3
- import { kitWeaselJsonHandler } from "./weaselJsonHandler.js";
4
- import { registerContentHandler } from "./contentHandlers.js";
5
-
6
- //#region src/features/ingestion/registerKitHandlers.ts
7
- /**
8
- * Refcounted registration of the kit's default content handlers.
9
- * Each mounted `<SceneCanvas>` calls this on mount and disposes on unmount;
10
- * the kit handlers stay registered while at least one canvas is up. (A naive
11
- * skip-if-present would break the two-canvas case: if the mount that
12
- * registered `kit:image` unmounts first, the survivor would lose its image
13
- * handler.)
14
- */
15
- var refs = 0;
16
- var dispose = null;
17
- function acquireKitContentHandlers() {
18
- refs++;
19
- if (refs === 1) {
20
- const disposers = [
21
- kitImageHandler,
22
- kitSvgHandler,
23
- kitWeaselJsonHandler
24
- ].map(registerContentHandler);
25
- dispose = () => {
26
- for (const d of disposers) d();
27
- };
28
- }
29
- let released = false;
30
- return () => {
31
- if (released) return;
32
- released = true;
33
- refs--;
34
- if (refs === 0) {
35
- dispose?.();
36
- dispose = null;
37
- }
38
- };
39
- }
40
-
41
- //#endregion
42
- export { acquireKitContentHandlers };
43
- //# sourceMappingURL=registerKitHandlers.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registerKitHandlers.js","names":[],"sources":["../../../src/features/ingestion/registerKitHandlers.ts"],"sourcesContent":["/**\n * Refcounted registration of the kit's default content handlers.\n * Each mounted `<SceneCanvas>` calls this on mount and disposes on unmount;\n * the kit handlers stay registered while at least one canvas is up. (A naive\n * skip-if-present would break the two-canvas case: if the mount that\n * registered `kit:image` unmounts first, the survivor would lose its image\n * handler.)\n */\nimport { registerContentHandler } from './contentHandlers';\nimport { kitImageHandler } from './imageHandler';\nimport { kitSvgHandler } from './svgHandler';\nimport { kitWeaselJsonHandler } from './weaselJsonHandler';\n\nlet refs = 0;\nlet dispose: (() => void) | null = null;\n\nexport function acquireKitContentHandlers(): () => void {\n refs++;\n if (refs === 1) {\n const disposers = [kitImageHandler, kitSvgHandler, kitWeaselJsonHandler].map(registerContentHandler);\n dispose = () => { for (const d of disposers) d(); };\n }\n let released = false;\n return () => {\n if (released) return;\n released = true;\n refs--;\n if (refs === 0) {\n dispose?.();\n dispose = null;\n }\n };\n}\n\n/** @internal test seam. */\nexport function _resetKitContentHandlersForTests(): void {\n refs = 0;\n dispose = null;\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,IAAI,OAAO;AACX,IAAI,UAA+B;AAEnC,SAAgB,4BAAwC;CACtD;CACA,IAAI,SAAS,GAAG;EACd,MAAM,YAAY;GAAC;GAAiB;GAAe;EAAoB,CAAC,CAAC,IAAI,sBAAsB;EACnG,gBAAgB;GAAE,KAAK,MAAM,KAAK,WAAW,EAAE;EAAG;CACpD;CACA,IAAI,WAAW;CACf,aAAa;EACX,IAAI,UAAU;EACd,WAAW;EACX;EACA,IAAI,SAAS,GAAG;GACd,UAAU;GACV,UAAU;EACZ;CACF;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"svgHandler.js","names":[],"sources":["../../../src/features/ingestion/svgHandler.ts"],"sourcesContent":["/**\n * The kit's `image/svg+xml` content handler.\n *\n * Default: a dropped SVG stays **one scene node** — an embedded image\n * (`data.image.src` as a `data:image/svg+xml;…` URI, source bytes preserved\n * verbatim). Chromium's `createImageBitmap` rejects raw SVG blobs, so this\n * handler measures via an `Image` element instead, falling back to 300×150\n * (the CSS default object size) when the file declares no intrinsic\n * dimensions.\n *\n * `<SceneCanvas ingestion={{ svg: { unpack: unpackSvgFiles } }}>` switches to\n * parsing the file into native scene nodes. The unpacker is injected because\n * the SVG parser lives in `@weasel-js/svg`, which depends on this package.\n *\n * Registered at priority -90: ahead of `kit:image` (-100) so SVG files\n * never hit the raster measure path, still behind any consumer handler\n * (default 0).\n */\nimport type { ContentHandlerEntry, IngestCtx } from './contentHandlers';\nimport type { IngestItem } from '@weasel-js/routing';\nimport { embedFilesAsImageNodes, type Measure } from './imageHandler';\n\nexport const SVG_MIME = 'image/svg+xml';\n\n// CSS default object size — what browsers use for replaced content with no\n// intrinsic dimensions (e.g. a viewBox-only SVG in an <img>).\nconst FALLBACK_WIDTH = 300;\nconst FALLBACK_HEIGHT = 150;\n\n/**\n * True for file items that are SVG by MIME — or by `.svg` extension when\n * the source offered no usable type (an empty `File.type` normalizes to\n * `application/octet-stream` at materialization).\n */\nexport function isSvgFileItem(item: IngestItem): boolean {\n if (item.kind !== 'file') return false;\n if (item.mime === SVG_MIME) return true;\n return item.mime === 'application/octet-stream' && /\\.svg$/i.test(item.file.name);\n}\n\n/**\n * Strict-prefix sniff: could this text be an SVG document? `<svg` (or an XML\n * declaration, then optional whitespace/comments/DOCTYPE, then `<svg`) must\n * open the document — no mid-document substring scans. Exists because Safari drops both custom MIMEs\n * and `image/svg+xml` from async clipboard writes, leaving `text/plain` as\n * the only surviving flavor of an SVG copy.\n */\nexport function sniffSvgText(text: string): boolean {\n let t = text.trimStart();\n if (t.startsWith('<?xml')) {\n const end = t.indexOf('?>');\n if (end < 0) return false;\n t = t.slice(end + 2);\n }\n for (;;) {\n t = t.trimStart();\n if (t.startsWith('<!--')) {\n const end = t.indexOf('-->');\n if (end < 0) return false;\n t = t.slice(end + 3);\n continue;\n }\n // Skipping ANY doctype is safe — a non-SVG doctype's root element fails\n // the final `<svg` check anyway. No internal-subset (`[...]`) handling.\n if (/^<!doctype/i.test(t)) {\n const end = t.indexOf('>');\n if (end < 0) return false;\n t = t.slice(end + 1);\n continue;\n }\n break;\n }\n return /^<svg[\\s>/]/i.test(t);\n}\n\nconst defaultMeasure: Measure = async (file) => {\n const url = URL.createObjectURL(file);\n try {\n const img = new Image();\n img.src = url;\n await img.decode();\n return {\n width: img.naturalWidth || FALLBACK_WIDTH,\n height: img.naturalHeight || FALLBACK_HEIGHT,\n };\n } finally {\n URL.revokeObjectURL(url);\n }\n};\n\nlet measure: Measure = defaultMeasure;\n\nexport const kitSvgHandler: ContentHandlerEntry = {\n id: 'kit:svg',\n // Files by MIME/extension, plus the text/plain fallback flavor: SVG markup\n // pasted as plain text (the only flavor Safari lets an SVG copy keep).\n match: (item) =>\n isSvgFileItem(item)\n || (item.kind === 'string' && item.mime === 'text/plain' && sniffSvgText(item.text)),\n types: [SVG_MIME, 'application/octet-stream', 'text/plain'],\n priority: -90,\n handle(items, ctx: IngestCtx) {\n const files = items.flatMap((it) => (it.kind === 'file' ? [it.file] : []));\n // String items are skipped when the weasel-JSON handler already pasted\n // this event's copy: ingesting both flavors would double-paste.\n // WARNING: this read must stay before any `await` in this handle — the\n // sync-ordering of handler microtasks is what makes the flag visible.\n if (!ctx.consumedWeaselPayload) {\n for (const it of items) {\n if (it.kind === 'string') files.push(new File([it.text], 'pasted.svg', { type: SVG_MIME }));\n }\n }\n if (files.length === 0) return;\n if (ctx.svg?.unpack) return ctx.svg.unpack(files, ctx);\n // Thunk (not the variable) so the test seam is read at call time.\n return embedFilesAsImageNodes(files, ctx, {\n measure: (f) => measure(f),\n mimeOverride: SVG_MIME,\n });\n },\n};\n\n/** @internal test seam — override the Image-element measurer (jsdom can't\n * decode SVG). Not part of the public barrel. */\nexport function __setSvgMeasureForTests(fn: Measure): void {\n measure = fn;\n}\n\n/** @internal test seam — restore the default measurer. */\nexport function _resetSvgHandlerSeamsForTests(): void {\n measure = defaultMeasure;\n}\n"],"mappings":";;;AAsBA,IAAa,WAAW;AAIxB,IAAM,iBAAiB;AACvB,IAAM,kBAAkB;;;;;;AAOxB,SAAgB,cAAc,MAA2B;CACvD,IAAI,KAAK,SAAS,QAAQ,OAAO;CACjC,IAAI,KAAK,SAAS,UAAU,OAAO;CACnC,OAAO,KAAK,SAAS,8BAA8B,UAAU,KAAK,KAAK,KAAK,IAAI;AAClF;;;;;;;;AASA,SAAgB,aAAa,MAAuB;CAClD,IAAI,IAAI,KAAK,UAAU;CACvB,IAAI,EAAE,WAAW,OAAO,GAAG;EACzB,MAAM,MAAM,EAAE,QAAQ,IAAI;EAC1B,IAAI,MAAM,GAAG,OAAO;EACpB,IAAI,EAAE,MAAM,MAAM,CAAC;CACrB;CACA,SAAS;EACP,IAAI,EAAE,UAAU;EAChB,IAAI,EAAE,WAAW,MAAM,GAAG;GACxB,MAAM,MAAM,EAAE,QAAQ,KAAK;GAC3B,IAAI,MAAM,GAAG,OAAO;GACpB,IAAI,EAAE,MAAM,MAAM,CAAC;GACnB;EACF;EAGA,IAAI,cAAc,KAAK,CAAC,GAAG;GACzB,MAAM,MAAM,EAAE,QAAQ,GAAG;GACzB,IAAI,MAAM,GAAG,OAAO;GACpB,IAAI,EAAE,MAAM,MAAM,CAAC;GACnB;EACF;EACA;CACF;CACA,OAAO,eAAe,KAAK,CAAC;AAC9B;AAEA,IAAM,iBAA0B,OAAO,SAAS;CAC9C,MAAM,MAAM,IAAI,gBAAgB,IAAI;CACpC,IAAI;EACF,MAAM,MAAM,IAAI,MAAM;EACtB,IAAI,MAAM;EACV,MAAM,IAAI,OAAO;EACjB,OAAO;GACL,OAAO,IAAI,gBAAgB;GAC3B,QAAQ,IAAI,iBAAiB;EAC/B;CACF,UAAU;EACR,IAAI,gBAAgB,GAAG;CACzB;AACF;AAEA,IAAI,UAAmB;AAEvB,IAAa,gBAAqC;CAChD,IAAI;CAGJ,QAAQ,SACN,cAAc,IAAI,KACd,KAAK,SAAS,YAAY,KAAK,SAAS,gBAAgB,aAAa,KAAK,IAAI;CACpF,OAAO;EAAC;EAAU;EAA4B;CAAY;CAC1D,UAAU;CACV,OAAO,OAAO,KAAgB;EAC5B,MAAM,QAAQ,MAAM,SAAS,OAAQ,GAAG,SAAS,SAAS,CAAC,GAAG,IAAI,IAAI,CAAC,CAAE;EAKzE,IAAI,CAAC,IAAI,uBACP;QAAK,MAAM,MAAM,OACf,IAAI,GAAG,SAAS,UAAU,MAAM,KAAK,IAAI,KAAK,CAAC,GAAG,IAAI,GAAG,cAAc,EAAE,MAAM,SAAS,CAAC,CAAC;EAC5F;EAEF,IAAI,MAAM,WAAW,GAAG;EACxB,IAAI,IAAI,KAAK,QAAQ,OAAO,IAAI,IAAI,OAAO,OAAO,GAAG;EAErD,OAAO,uBAAuB,OAAO,KAAK;GACxC,UAAU,MAAM,QAAQ,CAAC;GACzB,cAAc;EAChB,CAAC;CACH;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"textHandler.js","names":[],"sources":["../../../src/features/ingestion/textHandler.ts"],"sourcesContent":["/**\n * The kit's `text/plain` content handler: a pasted or dropped string becomes\n * a text node. Not registered by default; a canvas takes it through\n * `ingestion.handlers`.\n *\n * The node's box is the string's own laid-out size at the default text style,\n * so it holds the text exactly and wraps nowhere the string does not break.\n * Placement is the image handler's: on the point, or centered in the viewport.\n *\n * Priority -110, below every kit handler, so SVG markup and a copied node\n * payload arriving as plain text go to the handlers that read them.\n */\nimport { measureTextBounds } from '@weasel-js/text';\nimport type { IngestItem } from '@weasel-js/routing';\nimport type { ContentHandlerEntry, IngestCtx } from './contentHandlers';\nimport { arrivalBounds } from './placement';\n\n/** Options for {@link createTextHandler}. */\nexport interface TextHandlerOptions {\n /** Size of a string's layout box in world units. Default\n * `measureTextBounds`, which reads the registered font. */\n measure?: (text: string) => { width: number; height: number };\n}\n\n/** Build the plain-text content handler; {@link kitTextHandler} is the one\n * with the default measurer. */\nexport function createTextHandler(opts: TextHandlerOptions = {}): ContentHandlerEntry {\n const measure = opts.measure ?? ((text: string) => measureTextBounds(text));\n return {\n id: 'kit:text',\n match: (item: IngestItem) => item.kind === 'string' && item.mime === 'text/plain',\n types: ['text/plain'],\n priority: -110,\n handle(items, ctx: IngestCtx) {\n let index = 0;\n for (const item of items) {\n if (item.kind !== 'string') continue;\n const text = item.text.replace(/\\r\\n?/g, '\\n').replace(/\\n+$/, '');\n if (text.trim() === '') continue;\n ctx.insert.commit(arrivalBounds(ctx, measure(text), index), { kind: 'text', text });\n index++;\n }\n },\n };\n}\n\n/**\n * Turns pasted or dropped plain text into a text node sized to the string. Registered nowhere by\n * default: a canvas opts in with `ingestion={{ handlers: [kitTextHandler] }}`.\n */\nexport const kitTextHandler: ContentHandlerEntry = createTextHandler();\n"],"mappings":";;;;;;;;;;;;;;;;;;AA0BA,SAAgB,kBAAkB,OAA2B,CAAC,GAAwB;CACpF,MAAM,UAAU,KAAK,aAAa,SAAiB,kBAAkB,IAAI;CACzE,OAAO;EACL,IAAI;EACJ,QAAQ,SAAqB,KAAK,SAAS,YAAY,KAAK,SAAS;EACrE,OAAO,CAAC,YAAY;EACpB,UAAU;EACV,OAAO,OAAO,KAAgB;GAC5B,IAAI,QAAQ;GACZ,KAAK,MAAM,QAAQ,OAAO;IACxB,IAAI,KAAK,SAAS,UAAU;IAC5B,MAAM,OAAO,KAAK,KAAK,QAAQ,UAAU,IAAI,CAAC,CAAC,QAAQ,QAAQ,EAAE;IACjE,IAAI,KAAK,KAAK,MAAM,IAAI;IACxB,IAAI,OAAO,OAAO,cAAc,KAAK,QAAQ,IAAI,GAAG,KAAK,GAAG;KAAE,MAAM;KAAQ;IAAK,CAAC;IAClF;GACF;EACF;CACF;AACF;;;;;AAMA,IAAa,iBAAsC,kBAAkB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"useCanvasIngestion.js","names":[],"sources":["../../../src/features/ingestion/useCanvasIngestion.ts"],"sourcesContent":["import { useLatest } from '@weasel-js/react';\nimport { itemsFromFiles, type ActionsRegistry, type IngestItem } from '@weasel-js/routing';\nimport type { InsertAdapter } from 'core/adapters/types';\nimport type { ClipboardIngestCtx } from 'interactions/actions/depSchema';\nimport { useCallback, useEffect, useMemo } from 'react';\nimport { dwarn } from '../../debug';\nimport { registerContentHandler, type ContentHandlerEntry } from './contentHandlers';\nimport { acquireKitContentHandlers } from './registerKitHandlers';\n\n/** What `useCanvasIngestion` reads of `<SceneCanvas ingestion>`. */\nexport interface CanvasIngestionOptions {\n handlers?: ContentHandlerEntry[];\n clipboard?: {\n reviver?: ClipboardIngestCtx['reviver'];\n enabled?: boolean;\n onOmitted?: ClipboardIngestCtx['onOmitted'];\n };\n}\n\nexport interface CanvasIngestion {\n /** `SceneCanvasApi.ingest`. */\n ingest(input: File[] | IngestItem[], point?: { x: number; y: number }): boolean;\n /** For the `ingestion` dep. Absent, so the kit weasel-JSON handler declines, when\n * `clipboard.enabled` is false or the adapter cannot `commitPaste`. */\n clipboard: ClipboardIngestCtx | undefined;\n}\n\n/**\n * One canvas's share of ingestion: its content handlers for as long as it is mounted, the\n * clipboard seam pastes re-materialize through, and the imperative `ingest`, which triggers\n * the `ingest` action so its deps resolve as they do on the dispatcher path.\n *\n * `getRegistry` answers null until the canvas's action registry is reachable.\n */\nexport function useCanvasIngestion(\n adapter: InsertAdapter<{ id: string }>,\n getRegistry: () => ActionsRegistry | null,\n options: CanvasIngestionOptions | undefined,\n): CanvasIngestion {\n // The kit's default handlers are refcounted across mounted canvases.\n const handlers = options?.handlers;\n useEffect(() => {\n const disposers = [acquireKitContentHandlers()];\n for (const h of handlers ?? []) disposers.push(registerContentHandler(h));\n return () => disposers.forEach((d) => d());\n }, [handlers]);\n\n const reviver = options?.clipboard?.reviver;\n const enabled = options?.clipboard?.enabled !== false;\n const onOmitted = useLatest(options?.clipboard?.onOmitted);\n const clipboard = useMemo<ClipboardIngestCtx | undefined>(() => {\n if (!enabled || !adapter.commitPaste) return undefined;\n return {\n adapter,\n ...(reviver ? { reviver } : {}),\n onOmitted: (omitted) => onOmitted.current?.(omitted),\n };\n }, [adapter, enabled, reviver, onOmitted]);\n\n const registryOf = useLatest(getRegistry);\n const ingest = useCallback<CanvasIngestion['ingest']>((input, point) => {\n const items: IngestItem[] = (input as (File | IngestItem)[]).map((entry) =>\n entry instanceof File ? itemsFromFiles([entry])[0] : entry,\n );\n if (items.length === 0) return false;\n const registry = registryOf.current();\n if (!registry) {\n dwarn('ingest', 'CanvasExtensionApi.ingest called before the action registry mounted — call ignored. Defer to an effect or event handler.');\n return false;\n }\n const params = { items, ...(point ? { worldX: point.x, worldY: point.y } : {}) };\n const ran = registry.trigger('ingest', params);\n if (!ran) dwarn('ingest', 'CanvasExtensionApi.ingest ran nothing: this canvas has no `ingest` action. Add `ingest` to `features`.');\n return ran;\n }, [registryOf]);\n\n return { ingest, clipboard };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkCA,SAAgB,mBACd,SACA,aACA,SACiB;CAEjB,MAAM,WAAW,SAAS;CAC1B,gBAAgB;EACd,MAAM,YAAY,CAAC,0BAA0B,CAAC;EAC9C,KAAK,MAAM,KAAK,YAAY,CAAC,GAAG,UAAU,KAAK,uBAAuB,CAAC,CAAC;EACxE,aAAa,UAAU,SAAS,MAAM,EAAE,CAAC;CAC3C,GAAG,CAAC,QAAQ,CAAC;CAEb,MAAM,UAAU,SAAS,WAAW;CACpC,MAAM,UAAU,SAAS,WAAW,YAAY;CAChD,MAAM,YAAY,UAAU,SAAS,WAAW,SAAS;CACzD,MAAM,YAAY,cAA8C;EAC9D,IAAI,CAAC,WAAW,CAAC,QAAQ,aAAa,OAAO;EAC7C,OAAO;GACL;GACA,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;GAC7B,YAAY,YAAY,UAAU,UAAU,OAAO;EACrD;CACF,GAAG;EAAC;EAAS;EAAS;EAAS;CAAS,CAAC;CAEzC,MAAM,aAAa,UAAU,WAAW;CAiBxC,OAAO;EAAE,QAhBM,aAAwC,OAAO,UAAU;GACtE,MAAM,QAAuB,MAAgC,KAAK,UAChE,iBAAiB,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,KACvD;GACA,IAAI,MAAM,WAAW,GAAG,OAAO;GAC/B,MAAM,WAAW,WAAW,QAAQ;GACpC,IAAI,CAAC,UAAU;IACb,MAAM,UAAU,0HAA0H;IAC1I,OAAO;GACT;GACA,MAAM,SAAS;IAAE;IAAO,GAAI,QAAQ;KAAE,QAAQ,MAAM;KAAG,QAAQ,MAAM;IAAE,IAAI,CAAC;GAAG;GAC/E,MAAM,MAAM,SAAS,QAAQ,UAAU,MAAM;GAC7C,IAAI,CAAC,KAAK,MAAM,UAAU,wGAAwG;GAClI,OAAO;EACT,GAAG,CAAC,UAAU,CAEL;EAAQ;CAAU;AAC7B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"weaselJsonHandler.js","names":[],"sources":["../../../src/features/ingestion/weaselJsonHandler.ts"],"sourcesContent":["/**\n * The kit's weasel-JSON clipboard content handler.\n *\n * Consumes `application/x-weasel-clipboard+json` payloads in either MIME\n * spelling (Chromium's async-clipboard API round-trips custom formats with\n * a `web ` prefix), `text/plain` that sniffs as the wire format, and\n * `text/plain` SVG carrying the wire format embedded in a `<metadata>`\n * element (Chromium's DOM paste event exposes ONLY `text/plain`). It replays\n * them as a paste through the hosting canvas's adapter, mirroring\n * `useClipboardOps.paste`'s op construction exactly so OS-arriving pastes\n * and in-memory pastes are indistinguishable in history.\n *\n * Declines when `ctx.clipboard` is absent or the payload fails to parse.\n * `runIngest` consumes items at *match* time, so \"leave it for other\n * handlers\" is the sniff's job: non-weasel `text/plain` never matches.\n */\nimport type { NodeId } from 'core/scene/types';\nimport type { ClipboardSnapshot } from 'core/adapters/types';\nimport { materializePaste } from 'interactions/actions/clipboard/materializePaste';\nimport {\n WEASEL_CLIPBOARD_MIME,\n WEASEL_CLIPBOARD_MIME_WEB,\n sniffWeaselClipboardText,\n parseWeaselClipboardText,\n extractWeaselClipboardFromSvg,\n} from 'interactions/actions/clipboard/wireFormat';\nimport { dwarn } from '../../debug';\nimport type { ContentHandlerEntry, IngestCtx } from './contentHandlers';\n// Direct module import (not the ./index barrel) to avoid a cycle — the\n// barrel re-exports this handler.\nimport { sniffSvgText } from './svgHandler';\n\n/** text/plain SVG whose `<metadata>` carries a verified weasel payload —\n * the draw→draw DOM-paste flavor (Chromium exposes only text/plain there).\n * The `\"weaselClipboard\"` substring guard keeps match cheap on arbitrary\n * external SVG pastes before the extraction regex runs. */\nconst isMetadataBearingSvg = (text: string): boolean =>\n text.includes('\"weaselClipboard\"')\n && sniffSvgText(text)\n && extractWeaselClipboardFromSvg(text) !== null;\n\nexport const kitWeaselJsonHandler: ContentHandlerEntry = {\n id: 'kit:weasel-json',\n // Above the other kit handlers (-100/-90) so weasel payloads never fall\n // through to the SVG/text paths; still below consumer handlers (default 0).\n priority: -50,\n match: (item) =>\n item.kind === 'string'\n && (item.mime === WEASEL_CLIPBOARD_MIME\n || item.mime === WEASEL_CLIPBOARD_MIME_WEB\n || (item.mime === 'text/plain'\n && (sniffWeaselClipboardText(item.text) || isMetadataBearingSvg(item.text)))),\n types: [WEASEL_CLIPBOARD_MIME, WEASEL_CLIPBOARD_MIME_WEB, 'text/plain'],\n handle(items, ctx: IngestCtx) {\n const clipboard = ctx.clipboard;\n if (!clipboard) {\n dwarn('clipboard', 'weasel payload arrived but ctx.clipboard is not wired (ingestion.clipboard disabled or absent) — declining');\n return;\n }\n if (!clipboard.adapter.commitPaste) {\n dwarn('clipboard', 'weasel payload arrived but the clipboard adapter lacks commitPaste — declining');\n return;\n }\n const item = items.find((it) => it.kind === 'string');\n if (!item || item.kind !== 'string') return;\n // The metadata-bearing-SVG flavor: unwrap the embedded payload before\n // parsing. Extraction was already verified at match time, so a null here\n // is the same inert-decline class as a corrupt custom-MIME payload.\n const text = sniffSvgText(item.text)\n ? extractWeaselClipboardFromSvg(item.text)\n : item.text;\n if (text === null) {\n dwarn('clipboard', 'weasel payload vanished from the SVG metadata between match and handle — declining');\n return;\n }\n const nodes = parseWeaselClipboardText(text, clipboard.reviver);\n if (nodes === null) {\n dwarn('clipboard', 'weasel payload unusable — declining (see prior warning if the parse threw)');\n return;\n }\n // `ctx.point` is null for paste, so no dropPoint is threaded — the\n // cascade offset is the placement.\n const snapshot: ClipboardSnapshot = { items: nodes };\n const result = materializePaste(clipboard.adapter, snapshot, {\n currentSelection: ctx.selection.get(),\n onOmitted: clipboard.onOmitted,\n });\n if (!result) return;\n ctx.applyOps(result.ops, 'Paste');\n ctx.selection.set(result.newIds as NodeId[]);\n // So kit:svg's text/plain fallback declines the SVG flavor of this copy.\n // WARNING: do not add an `await` earlier in this handle — the flag must\n // be written before lower-priority handlers' microtasks run.\n ctx.consumedWeaselPayload = true;\n },\n};\n"],"mappings":";;;;;;;;;;AAoCA,IAAM,wBAAwB,SAC5B,KAAK,SAAS,qBAAmB,KAC9B,aAAa,IAAI,KACjB,8BAA8B,IAAI,MAAM;AAE7C,IAAa,uBAA4C;CACvD,IAAI;CAGJ,UAAU;CACV,QAAQ,SACN,KAAK,SAAS,aACV,KAAK,SAAS,yBACb,KAAK,SAAS,6BACb,KAAK,SAAS,iBACZ,yBAAyB,KAAK,IAAI,KAAK,qBAAqB,KAAK,IAAI;CAC/E,OAAO;EAAC;EAAuB;EAA2B;CAAY;CACtE,OAAO,OAAO,KAAgB;EAC5B,MAAM,YAAY,IAAI;EACtB,IAAI,CAAC,WAAW;GACd,MAAM,aAAa,4GAA4G;GAC/H;EACF;EACA,IAAI,CAAC,UAAU,QAAQ,aAAa;GAClC,MAAM,aAAa,gFAAgF;GACnG;EACF;EACA,MAAM,OAAO,MAAM,MAAM,OAAO,GAAG,SAAS,QAAQ;EACpD,IAAI,CAAC,QAAQ,KAAK,SAAS,UAAU;EAIrC,MAAM,OAAO,aAAa,KAAK,IAAI,IAC/B,8BAA8B,KAAK,IAAI,IACvC,KAAK;EACT,IAAI,SAAS,MAAM;GACjB,MAAM,aAAa,oFAAoF;GACvG;EACF;EACA,MAAM,QAAQ,yBAAyB,MAAM,UAAU,OAAO;EAC9D,IAAI,UAAU,MAAM;GAClB,MAAM,aAAa,4EAA4E;GAC/F;EACF;EAGA,MAAM,WAA8B,EAAE,OAAO,MAAM;EACnD,MAAM,SAAS,iBAAiB,UAAU,SAAS,UAAU;GAC3D,kBAAkB,IAAI,UAAU,IAAI;GACpC,WAAW,UAAU;EACvB,CAAC;EACD,IAAI,CAAC,QAAQ;EACb,IAAI,SAAS,OAAO,KAAK,OAAO;EAChC,IAAI,UAAU,IAAI,OAAO,MAAkB;EAI3C,IAAI,wBAAwB;CAC9B;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"longPressFeedback.js","names":["circlePath"],"sources":["../../../src/features/longPress/longPressFeedback.ts"],"sourcesContent":["/**\n * The default \"press is registering\" feedback for a touch or pen long-press: a\n * ring around the press point that fills over the hold. It reads the `longPress`\n * dep the gesture dispatcher publishes, so it shows only for a press some\n * binding would fire on, and disappears when the press fires, moves past the\n * drag threshold, lifts, or is canceled.\n */\nimport type { LongPressState } from '@weasel-js/routing';\nimport { prefersReducedMotion } from '@weasel-js/react';\nimport type { RenderLayer } from 'core/layers/render';\nimport { ellipsePath, polylineFromPoints, type Path } from '@weasel-js/geom';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { CanvasExtensionApi } from '../../canvas/canvasExtension';\nimport type { ContributionDepReader, SurfaceContribution } from '../../canvas/surfaceContribution';\nimport { accentColorOf } from '../../canvas/accentColor';\n\n/** Entry and layer id of the default feedback, so a host can find or order it. */\nexport const LONG_PRESS_FEEDBACK_ID = 'long-press-feedback';\n\n/** Options for {@link createLongPressFeedbackContribution}. */\nexport interface LongPressFeedbackOptions {\n /** Entry and layer id. Default {@link LONG_PRESS_FEEDBACK_ID}. */\n id?: string;\n /** Ring radius in CSS pixels. Default 22 — clear of a fingertip. */\n radius?: number;\n /** Ring stroke width in CSS pixels. Default 3. */\n width?: number;\n /** Ring color. Default the theme's `--wzl-accent`, read off the canvas\n * element when each press starts. */\n color?: string | (() => string);\n /** How long a press is held before the ring appears, in ms, so an ordinary\n * tap shows nothing. Default 120. */\n delay?: number;\n /** `true` shows a static full ring instead of one that fills, and runs no\n * frame loop. Default `'auto'`: follow `prefers-reduced-motion`, read when\n * each press starts. */\n reducedMotion?: boolean | 'auto';\n}\n\nconst RING_SEGMENTS = 64;\nconst TRACK_OPACITY = 0.3;\n\nconst nowMs = (): number => performance.now();\n\n/** An open arc from 12 o'clock, clockwise on a y-down screen, through `t` of a turn. */\nfunction arcPath(cx: number, cy: number, r: number, t: number): Path {\n const n = Math.max(1, Math.ceil(RING_SEGMENTS * t));\n const pts: { x: number; y: number }[] = [];\n for (let i = 0; i <= n; i++) {\n const a = (i / n) * t * Math.PI * 2;\n pts.push({ x: cx + r * Math.sin(a), y: cy - r * Math.cos(a) });\n }\n return polylineFromPoints(pts);\n}\n\nfunction circlePath(cx: number, cy: number, r: number): Path {\n return ellipsePath({ x: cx - r, y: cy - r, width: 2 * r, height: 2 * r });\n}\n\nconst viewIdOf = (data: unknown): string | null =>\n (data as { viewId?: string | null } | null)?.viewId ?? null;\n\n/**\n * The default long-press feedback, as one entry. `<SceneCanvas>` installs it\n * unless `longPress={{ feedback: false }}`; pass a contribution of your own there to\n * replace it — it reads the same `longPress` dep this one does.\n */\nexport function createLongPressFeedbackContribution(\n opts: LongPressFeedbackOptions = {},\n): SurfaceContribution {\n const id = opts.id ?? LONG_PRESS_FEEDBACK_ID;\n const radius = opts.radius ?? 22;\n const width = opts.width ?? 3;\n const delay = opts.delay ?? 120;\n\n let state: LongPressState | undefined;\n let color = '';\n let reduced = false;\n\n const showing = (): boolean => {\n const p = state?.get();\n return !!p && p.armed;\n };\n\n const layer: RenderLayer<unknown> = {\n id,\n label: 'Long-press feedback',\n space: 'screen',\n draw: (data): DrawCommand[] => {\n const p = state?.get();\n // Screen space is the whole canvas, so the root pass draws the ring\n // wherever the press routed; a view pass drawing it too would double it.\n if (!state || !p || !p.armed || viewIdOf(data) !== null) return [];\n const now = nowMs();\n if (now - p.startedAt < delay) return [];\n const { x, y } = p.local;\n const stroke = (opacity?: number) => ({\n paint: { color, ...(opacity !== undefined ? { opacity } : {}) },\n width: { px: width },\n cap: 'round' as const,\n });\n if (reduced) return [{ kind: 'path', path: circlePath(x, y, radius), stroke: stroke() }];\n return [\n { kind: 'path', path: circlePath(x, y, radius), stroke: stroke(TRACK_OPACITY) },\n { kind: 'path', path: arcPath(x, y, radius, state.progress(now)), stroke: stroke() },\n ];\n },\n };\n\n const attach = (api: CanvasExtensionApi, deps: ContributionDepReader): (() => void) => {\n const store = deps.get('longPress');\n if (!store) return () => {};\n state = store;\n let appear: ReturnType<typeof setTimeout> | undefined;\n\n const onChange = (): void => {\n clearTimeout(appear);\n appear = undefined;\n const p = store.get();\n if (p?.armed) {\n color = typeof opts.color === 'function' ? opts.color() : (opts.color ?? accentColorOf(api.element));\n reduced = opts.reducedMotion === 'auto' || opts.reducedMotion === undefined\n ? prefersReducedMotion()\n : opts.reducedMotion;\n // A static ring changes once, when it appears; nothing needs frames.\n const wait = delay - (nowMs() - p.startedAt);\n if (reduced && wait > 0) appear = setTimeout(() => { api.requestRedraw(); }, wait);\n }\n api.requestRedraw();\n };\n\n const offStore = store.subscribe(onChange);\n onChange();\n return () => {\n offStore();\n clearTimeout(appear);\n state = undefined;\n api.requestRedraw();\n };\n };\n\n return {\n id,\n eligibility: { always: true },\n overlay: layer,\n attach,\n // The filling ring asks for the next frame from each painted one while it shows.\n afterPaint: (ctx) => {\n if (!reduced && showing()) ctx.requestFrame();\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"actionIds.js","names":[],"sources":["../../../src/features/measure/actionIds.ts"],"sourcesContent":["/** The ids of the measure feature's actions. */\nexport const MEASURE_ACTION_IDS = {\n /** The measure tool's drag, which adds a measurement. */\n measure: 'measure',\n /** The measure tool's click, which picks the measurement under it. */\n pickAt: 'measure.pickAt',\n /** A click on a measurement under any tool, which picks it. */\n pick: 'measure.pick',\n /** A drag on a measurement under any tool: a picked end moves that end, the\n * rest moves the whole measurement. */\n drag: 'measure.drag',\n moveBy: 'measure.moveBy',\n rotateBy: 'measure.rotateBy',\n delete: 'measure.delete',\n clear: 'measure.clear',\n unpick: 'measure.unpick',\n} as const;\n"],"mappings":";;AACA,IAAa,qBAAqB;;CAEhC,SAAS;;CAET,QAAQ;;CAER,MAAM;;;CAGN,MAAM;CACN,QAAQ;CACR,UAAU;CACV,QAAQ;CACR,OAAO;CACP,QAAQ;AACV"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"measureCommands.js","names":[],"sources":["../../../src/features/measure/measureCommands.ts"],"sourcesContent":["import type { Action, ActionDeps } from '@weasel-js/routing';\nimport { ActionDisabledReason } from '@weasel-js/routing';\nimport type { NodeId, Scene } from 'core/scene/types';\nimport { translateSelection } from 'interactions/actions/defaults/nudge';\nimport { requiresSelection } from 'interactions/actions/defaults/requiresSelection';\nimport { commitOps } from 'interactions/actions/defaultCommitAdapter';\nimport { commitTransformOps, transformApplyOps } from 'interactions/actions/transformFinish';\nimport { poseDescriptorOf } from 'interactions/actions/poseDescriptorDep';\nimport { rotateSelectionOps } from 'interactions/actions/rotate/replay';\nimport { recordTransform } from 'interactions/actions/transform-again/memory';\nimport { measurementOf, type Measurement } from './measurement';\nimport { currentMeasureId, measureIds, type Measurements } from './measurements';\nimport { deleteMeasureOps, MEASURE_LABELS } from './measureEdits';\nimport { MEASURE_ACTION_IDS } from './actionIds';\n\nconst MOVE_LABEL = 'Move by Measurement';\nconst ROTATE_LABEL = 'Rotate by Measurement';\n\ntype AnyScene = Scene<unknown, string, unknown>;\n\nfunction current(deps: ActionDeps | undefined, ms: Measurements): { id: NodeId; m: Measurement } | null {\n const scene = deps?.scene as AnyScene | undefined;\n if (!scene) return null;\n const id = currentMeasureId(scene, ms);\n const m = id === null ? null : measurementOf(scene.get(id));\n return id !== null && m ? { id, m } : null;\n}\n\n/** Enabled with a measurement to apply and a selection to apply it to. */\nfunction transformable(ms: Measurements) {\n return (deps?: ActionDeps) => {\n const c = current(deps, ms);\n if (!c || c.m.distance === 0) return ActionDisabledReason.NotApplicable;\n return requiresSelection(deps);\n };\n}\n\nfunction moveBy(ms: Measurements): Action {\n return {\n id: MEASURE_ACTION_IDS.moveBy,\n label: MOVE_LABEL,\n group: 'measure',\n eligible: { capability: 'transforms-selection' },\n requires: ['selection', 'scene', 'applyOps', 'geometryProjection', 'poseDescriptor', 'transformFollowers'],\n enabled: transformable(ms),\n invoker: {\n timing: 'immediate',\n run: (deps) => {\n const c = current(deps, ms);\n const { selection, scene } = deps;\n if (!c || !selection || !scene) return;\n const { dx, dy } = c.m;\n const moved = translateSelection(\n selection, scene, dx, dy, transformApplyOps(deps), deps.geometryProjection,\n poseDescriptorOf(deps.poseDescriptor), MOVE_LABEL,\n );\n if (moved) recordTransform(scene, { kind: 'translate', dx, dy }, selection.get());\n },\n },\n };\n}\n\nfunction rotateBy(ms: Measurements): Action {\n return {\n id: MEASURE_ACTION_IDS.rotateBy,\n label: ROTATE_LABEL,\n group: 'measure',\n eligible: { capability: 'transforms-selection' },\n requires: ['selection', 'scene', 'applyOps', 'poseDescriptor', 'view', 'transformFollowers'],\n enabled: transformable(ms),\n invoker: {\n timing: 'immediate',\n run: (deps) => {\n const c = current(deps, ms);\n const { selection } = deps;\n const scene = deps.scene as AnyScene | undefined;\n if (!c || !selection || !scene) return;\n const selected = selection.get() as string[];\n // The measurement reads counterclockwise-positive; the canvas turns clockwise-positive.\n const turn = -c.m.angle;\n const ops = rotateSelectionOps(\n scene, selected, deps, turn, 'union', ROTATE_LABEL,\n );\n if (!commitTransformOps(deps, ops, ROTATE_LABEL)) return;\n recordTransform(scene, { kind: 'rotate', angle: turn, pivot: 'union' }, selected);\n },\n },\n };\n}\n\nfunction remove(ms: Measurements): Action {\n return {\n id: MEASURE_ACTION_IDS.delete,\n label: MEASURE_LABELS.delete,\n group: 'measure',\n requires: ['scene', 'applyOps', 'selection'],\n // Declining with nothing measured leaves Delete to the ambient `delete`.\n enabled: (deps) => (current(deps, ms) ? true : ActionDisabledReason.NotApplicable),\n invoker: {\n timing: 'immediate',\n run: (deps) => {\n const c = current(deps, ms);\n if (!c) return;\n commitOps(deps, deleteMeasureOps(deps.scene as AnyScene, [c.id], MEASURE_LABELS.delete), MEASURE_LABELS.delete);\n if (ms.picked.get() === c.id) ms.picked.set(null);\n },\n },\n };\n}\n\nfunction clearAll(ms: Measurements): Action {\n const all = (deps: ActionDeps | undefined) => {\n const scene = deps?.scene as AnyScene | undefined;\n return scene ? measureIds(scene, ms) : [];\n };\n return {\n id: MEASURE_ACTION_IDS.clear,\n label: MEASURE_LABELS.clear,\n group: 'measure',\n requires: ['scene', 'applyOps', 'selection'],\n enabled: (deps) => (all(deps).length > 0 ? true : ActionDisabledReason.NotApplicable),\n invoker: {\n timing: 'immediate',\n run: (deps) => {\n const ids = all(deps);\n if (ids.length === 0) return;\n commitOps(deps, deleteMeasureOps(deps.scene as AnyScene, ids, MEASURE_LABELS.clear), MEASURE_LABELS.clear);\n ms.picked.set(null);\n },\n },\n };\n}\n\nfunction unpick(ms: Measurements): Action {\n return {\n id: MEASURE_ACTION_IDS.unpick,\n label: 'Deselect Measurement',\n // Declining with nothing picked leaves Escape to the ambient `escape`.\n enabled: () => (ms.picked.get() !== null ? true : ActionDisabledReason.NotApplicable),\n invoker: { timing: 'immediate', run: () => ms.picked.set(null) },\n };\n}\n\n/**\n * The commands over a scene's measurements: move the content selection by\n * the current measurement's offset, or turn it about its center by the\n * measured angle — each one undo step Transform Again can repeat — delete the\n * current measurement, clear them all, and drop the pick. The current\n * measurement is the picked one, else the newest. All but the pick's are in\n * the `measure` group.\n */\nexport function measureCommands(ms: Measurements): Action[] {\n return [moveBy(ms), rotateBy(ms), remove(ms), clearAll(ms), unpick(ms)];\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"measureContribution.js","names":[],"sources":["../../../src/features/measure/measureContribution.ts"],"sourcesContent":["import { hitPredicate } from '@weasel-js/gestures';\nimport type { Scene } from 'core/scene/types';\nimport type { SurfaceContribution } from 'canvas/surfaceContribution';\nimport type { Measurements } from './measurements';\nimport { measureCommands } from './measureCommands';\nimport { measureDragAction, measurePickAction } from './measureGestures';\nimport { MEASURE_AFFORDANCE_KIND, measurementsLayer } from './measureLayer';\nimport { MEASURE_ACTION_IDS } from './actionIds';\n\nconst onMeasurement = hitPredicate((hit) => (hit as { kind?: string } | null | undefined)?.kind === MEASURE_AFFORDANCE_KIND);\n\nconst MODS_ANY = { shift: 'optional', alt: 'optional', meta: 'optional', ctrl: 'optional' } as const;\n\n/**\n * A scene's measurements on a canvas, whatever tool is active: the layer\n * that shows the picked one and claims presses on measurements, a click on\n * one to pick it and a drag to edit it, and the commands — Move by, Rotate\n * by, Delete, Clear — a readout binds buttons to. Install it with\n * `<SceneCanvas ambient={[…]}>`; `useMeasureTool` adds the measuring itself.\n */\nexport function measurementsContribution(\n scene: Scene<unknown, string, unknown>,\n ms: Measurements,\n): SurfaceContribution {\n const layer = measurementsLayer(scene, ms);\n return {\n id: 'weasel-measure',\n eligibility: { claimed: true },\n attach: (api) => {\n const off = api.registerLayer(layer);\n api.requestRedraw();\n return off;\n },\n actions: [...measureCommands(ms), measurePickAction(ms), measureDragAction(ms)],\n bindings: [\n { spec: { kind: 'click', target: { kindOf: onMeasurement }, mods: MODS_ANY }, actionId: MEASURE_ACTION_IDS.pick, opts: { id: 'weasel-measure.click' } },\n { spec: { kind: 'drag', target: { kindOf: onMeasurement }, mods: MODS_ANY }, actionId: MEASURE_ACTION_IDS.drag, opts: { id: 'weasel-measure.drag' } },\n ],\n };\n}\n"],"mappings":";;;;;;;AASA,IAAM,gBAAgB,cAAc,QAAS,KAA8C,SAAS,uBAAuB;AAE3H,IAAM,WAAW;CAAE,OAAO;CAAY,KAAK;CAAY,MAAM;CAAY,MAAM;AAAW;;;;;;;;AAS1F,SAAgB,yBACd,OACA,IACqB;CACrB,MAAM,QAAQ,kBAAkB,OAAO,EAAE;CACzC,OAAO;EACL,IAAI;EACJ,aAAa,EAAE,SAAS,KAAK;EAC7B,SAAS,QAAQ;GACf,MAAM,MAAM,IAAI,cAAc,KAAK;GACnC,IAAI,cAAc;GAClB,OAAO;EACT;EACA,SAAS;GAAC,GAAG,gBAAgB,EAAE;GAAG,kBAAkB,EAAE;GAAG,kBAAkB,EAAE;EAAC;EAC9E,UAAU,CACR;GAAE,MAAM;IAAE,MAAM;IAAS,QAAQ,EAAE,QAAQ,cAAc;IAAG,MAAM;GAAS;GAAG,UAAU,mBAAmB;GAAM,MAAM,EAAE,IAAI,uBAAuB;EAAE,GACtJ;GAAE,MAAM;IAAE,MAAM;IAAQ,QAAQ,EAAE,QAAQ,cAAc;IAAG,MAAM;GAAS;GAAG,UAAU,mBAAmB;GAAM,MAAM,EAAE,IAAI,sBAAsB;EAAE,CACtJ;CACF;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"measureEdits.js","names":[],"sources":["../../../src/features/measure/measureEdits.ts"],"sourcesContent":["import type { Op } from 'core/ops/types';\nimport type { Node, NodeId, Scene } from 'core/scene/types';\nimport { createSetDataOp } from 'core/ops/setData';\nimport { createTransformOp } from 'core/ops/transform';\nimport { deleteNode, insertAt } from 'interactions/actions/pathCommands/pathNodes';\nimport {\n measureEndsOf, measureNode, measurePose, withMeasureEnd,\n type MeasureData, type MeasureEnd, type MeasurePoint,\n} from './measurement';\n\ntype AnyScene = Scene<unknown, string, unknown>;\ntype AnyNode = Node<unknown, string, unknown>;\n\n/** The labels measurement edits land in history under. */\nexport const MEASURE_LABELS = {\n add: 'Measure',\n moveEnd: 'Move Measurement End',\n move: 'Move Measurement',\n delete: 'Delete Measurement',\n clear: 'Clear Measurements',\n} as const;\n\n/** The op adding a measurement from `start` to `end` on `layer`, at the top. */\nexport function addMeasureOp(scene: AnyScene, layer: string, start: MeasurePoint, end: MeasurePoint): { id: NodeId; op: Op } {\n const id = scene.mintId();\n return { id, op: insertAt(measureNode(id, layer, start, end), -1, MEASURE_LABELS.add) };\n}\n\n/** `node` as it reads after `next`: what an edit writes and its preview shows. */\nfunction changeOps(node: AnyNode, next: { data: MeasureData; pose: unknown }, label: string): Op[] {\n return [\n createTransformOp({ id: node.id, from: node.pose, to: next.pose, label }),\n createSetDataOp({ id: node.id, from: node.data, to: next.data, label }),\n ];\n}\n\n/** The ops moving `node`'s `which` end to `to`. */\nexport function moveEndOps(node: AnyNode, which: MeasureEnd, to: MeasurePoint): Op[] {\n const next = withMeasureEnd(node, which, to);\n return next ? changeOps(node, next, MEASURE_LABELS.moveEnd) : [];\n}\n\n/** `node` carried by `(dx, dy)`, both ends together. */\nexport function translatedMeasure(node: AnyNode, dx: number, dy: number): { data: MeasureData; pose: unknown } | null {\n const ends = measureEndsOf(node);\n if (!ends) return null;\n const start = { x: ends.start.x + dx, y: ends.start.y + dy };\n const end = { x: ends.end.x + dx, y: ends.end.y + dy };\n return { data: { ...(node.data as MeasureData), measure: { start, end } }, pose: measurePose(start, end) };\n}\n\n/** The ops carrying `node` by `(dx, dy)`. */\nexport function moveMeasureOps(node: AnyNode, dx: number, dy: number): Op[] {\n const next = translatedMeasure(node, dx, dy);\n return next ? changeOps(node, next, MEASURE_LABELS.move) : [];\n}\n\n/** The ops deleting each of `ids`, as one edit labeled `label`. */\nexport function deleteMeasureOps(scene: AnyScene, ids: readonly NodeId[], label: string): Op[] {\n const ops: Op[] = [];\n for (const id of ids) {\n const node = scene.get(id);\n if (node) ops.push(deleteNode(scene, node, label));\n }\n return ops;\n}\n"],"mappings":";;;;;;;AAcA,IAAa,iBAAiB;CAC5B,KAAK;CACL,SAAS;CACT,MAAM;CACN,QAAQ;CACR,OAAO;AACT;;AAGA,SAAgB,aAAa,OAAiB,OAAe,OAAqB,KAA2C;CAC3H,MAAM,KAAK,MAAM,OAAO;CACxB,OAAO;EAAE;EAAI,IAAI,SAAS,YAAY,IAAI,OAAO,OAAO,GAAG,GAAG,IAAI,eAAe,GAAG;CAAE;AACxF;;AAGA,SAAS,UAAU,MAAe,MAA4C,OAAqB;CACjG,OAAO,CACL,kBAAkB;EAAE,IAAI,KAAK;EAAI,MAAM,KAAK;EAAM,IAAI,KAAK;EAAM;CAAM,CAAC,GACxE,gBAAgB;EAAE,IAAI,KAAK;EAAI,MAAM,KAAK;EAAM,IAAI,KAAK;EAAM;CAAM,CAAC,CACxE;AACF;;AAGA,SAAgB,WAAW,MAAe,OAAmB,IAAwB;CACnF,MAAM,OAAO,eAAe,MAAM,OAAO,EAAE;CAC3C,OAAO,OAAO,UAAU,MAAM,MAAM,eAAe,OAAO,IAAI,CAAC;AACjE;;AAGA,SAAgB,kBAAkB,MAAe,IAAY,IAAyD;CACpH,MAAM,OAAO,cAAc,IAAI;CAC/B,IAAI,CAAC,MAAM,OAAO;CAClB,MAAM,QAAQ;EAAE,GAAG,KAAK,MAAM,IAAI;EAAI,GAAG,KAAK,MAAM,IAAI;CAAG;CAC3D,MAAM,MAAM;EAAE,GAAG,KAAK,IAAI,IAAI;EAAI,GAAG,KAAK,IAAI,IAAI;CAAG;CACrD,OAAO;EAAE,MAAM;GAAE,GAAI,KAAK;GAAsB,SAAS;IAAE;IAAO;GAAI;EAAE;EAAG,MAAM,YAAY,OAAO,GAAG;CAAE;AAC3G;;AAGA,SAAgB,eAAe,MAAe,IAAY,IAAkB;CAC1E,MAAM,OAAO,kBAAkB,MAAM,IAAI,EAAE;CAC3C,OAAO,OAAO,UAAU,MAAM,MAAM,eAAe,IAAI,IAAI,CAAC;AAC9D;;AAGA,SAAgB,iBAAiB,OAAiB,KAAwB,OAAqB;CAC7F,MAAM,MAAY,CAAC;CACnB,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,OAAO,MAAM,IAAI,EAAE;EACzB,IAAI,MAAM,IAAI,KAAK,WAAW,OAAO,MAAM,KAAK,CAAC;CACnD;CACA,OAAO;AACT"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"measureGestures.js","names":[],"sources":["../../../src/features/measure/measureGestures.ts"],"sourcesContent":["import type { Action, ActionDeps, BindingOpts, InvocationCtx, OngoingHandle } from '@weasel-js/routing';\nimport { resolveParams } from '@weasel-js/routing';\nimport type { NodeId, Scene } from 'core/scene/types';\nimport { commitOps } from 'interactions/actions/defaultCommitAdapter';\nimport { SNAP_ANGLE_STEP, SNAP_ROLES, snapMods } from 'features/snap/roles';\nimport { spanSnap } from 'features/snap/spanSnap';\nimport { measureBetween, measureEndsOf, withMeasureEnd, type MeasureEnd, type MeasurePoint } from './measurement';\nimport type { Measurements } from './measurements';\nimport { measureAt, type MeasureHit } from './measureHit';\nimport { addMeasureOp, moveEndOps, moveMeasureOps, translatedMeasure } from './measureEdits';\nimport { MEASURE_ACTION_IDS } from './actionIds';\n\ntype AnyScene = Scene<unknown, string, unknown>;\n\n/** Screen-px radius around a measurement that picks it. */\nexport const MEASURE_HIT_PX = 6;\n\nconst pointOf = (at: InvocationCtx): MeasurePoint => at.drag?.current ?? at.world;\n\n/** World units `px` screen pixels span at the deps' view. */\nexport function pxToWorld(deps: ActionDeps | undefined, px: number): number {\n const scale = deps?.view?.get().scale.x ?? 1;\n return px / (scale || 1);\n}\n\n/**\n * The measure tool's drag: from press to pointer, each end through a `point`\n * session of the `snap` dep. While `snap.constrain` (Shift) is held the end\n * stays on the line through the snapped start at the nearest step of the\n * binding param `angleStep` (radians, default 15°), and lands where that line\n * meets a target. The drawing shows in `ms.draft`; on release it becomes a\n * measurement node, one undo step, and the picked one.\n */\nexport function measureCreateAction(ms: Measurements): Action {\n return {\n id: MEASURE_ACTION_IDS.measure,\n label: 'Measure',\n modifierRoles: SNAP_ROLES,\n requires: ['snap', 'scene', 'applyOps', 'selection'],\n invoker: {\n timing: 'ongoing',\n // The dispatcher builds deps once, for `start`; later events carry none.\n start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle {\n const deps = ctx.deps;\n // Read on every event, so a step changed mid-drag reaches it.\n const snap = spanSnap(deps.snap, () => {\n const step = resolveParams(opts?.params)?.['angleStep'];\n return { step: typeof step === 'number' ? step : SNAP_ANGLE_STEP };\n });\n const start = snap.start(ctx.drag?.start ?? ctx.world, snapMods(ctx)).point;\n const endAt = (at: InvocationCtx): MeasurePoint => snap.end(pointOf(at), snapMods(at)).point;\n ms.draft.set(measureBetween(start, start));\n return {\n kind: 'measure',\n onMove: (moveCtx) => ms.draft.set(measureBetween(start, endAt(moveCtx))),\n onEnd: (endCtx, reason) => {\n try {\n ms.draft.set(null);\n const scene = deps.scene as AnyScene | undefined;\n if (reason !== 'commit' || !scene) return;\n const end = endAt(endCtx);\n if (end.x === start.x && end.y === start.y) return;\n const { id, op } = addMeasureOp(scene, ms.layer, start, end);\n commitOps(deps, [op], 'Measure');\n ms.picked.set(id);\n } finally {\n snap.close();\n }\n },\n };\n },\n },\n };\n}\n\n/** The measure tool's click: picks the measurement under it, or drops the\n * pick on empty canvas. */\nexport function measurePickAtAction(ms: Measurements): Action {\n return {\n id: MEASURE_ACTION_IDS.pickAt,\n label: 'Pick Measurement',\n requires: ['scene', 'view'],\n invoker: {\n timing: 'immediate',\n run: (deps, params) => {\n const p = params as { pressX?: number; pressY?: number } | undefined;\n const scene = deps.scene as AnyScene | undefined;\n if (!scene || p?.pressX === undefined || p.pressY === undefined) return;\n const hit = measureAt(scene, ms, { x: p.pressX, y: p.pressY }, pxToWorld(deps, MEASURE_HIT_PX));\n ms.picked.set(hit?.id ?? null);\n },\n },\n };\n}\n\n/** What a press on the measurements layer resolved, as its hit-test claims it. */\nexport const hitOf = (affordance: unknown): MeasureHit | null =>\n (affordance as { payload?: MeasureHit } | null | undefined)?.payload ?? null;\n\n/** A click on a measurement under any tool: picks it, leaving the content\n * selection as it was. */\nexport function measurePickAction(ms: Measurements): Action {\n return {\n id: MEASURE_ACTION_IDS.pick,\n label: 'Pick Measurement',\n invoker: {\n timing: 'immediate',\n run: (_deps, params) => {\n const hit = hitOf((params as { affordance?: unknown } | undefined)?.affordance);\n if (hit) ms.picked.set(hit.id);\n },\n },\n };\n}\n\n/** The node `id` on `scene`, as a measurement edit reads it. */\nfunction nodeOf(scene: AnyScene | undefined, id: NodeId) {\n return scene?.get(id);\n}\n\n/**\n * A drag on a measurement under any tool. On an end of the picked\n * measurement it moves that end, keeping its offset from the press, through\n * a `point` session of the `snap` dep in which the measurement's own ends\n * offer nothing; anywhere else on a\n * measurement it carries the whole measurement with the pointer. Previewed\n * while it runs; one undo step on release.\n */\nexport function measureDragAction(ms: Measurements): Action {\n return {\n id: MEASURE_ACTION_IDS.drag,\n label: 'Drag Measurement',\n modifierRoles: SNAP_ROLES,\n requires: ['scene', 'snap', 'applyOps', 'selection'],\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx): OngoingHandle {\n const hit = hitOf(ctx.drag?.affordance);\n const deps = ctx.deps;\n const scene = deps.scene as AnyScene | undefined;\n const node = hit ? nodeOf(scene, hit.id) : undefined;\n if (!hit || !node) return {};\n const end: MeasureEnd | null = hit.part !== 'line' && ms.picked.get() === hit.id ? hit.part : null;\n const from = ctx.drag?.start ?? ctx.world;\n const grabbed = (end && measureEndsOf(node)?.[end]) || from;\n const snap = end ? deps.snap?.begin({ gesture: 'point', press: grabbed, ids: [hit.id] }) ?? null : null;\n let at = from;\n let landed = grabbed;\n const next = () => (end\n ? withMeasureEnd(node, end, landed)\n : translatedMeasure(node, at.x - from.x, at.y - from.y));\n const follow = (c: InvocationCtx) => {\n at = pointOf(c);\n const held = { x: grabbed.x + at.x - from.x, y: grabbed.y + at.y - from.y };\n landed = snap ? snap.at(held, snapMods(c)).point : held;\n };\n ms.picked.set(hit.id);\n return {\n kind: MEASURE_ACTION_IDS.drag,\n onMove: follow,\n onEnd: (endCtx, reason) => {\n try {\n if (reason !== 'commit') return;\n follow(endCtx);\n const ops = end ? moveEndOps(node, end, landed) : moveMeasureOps(node, at.x - from.x, at.y - from.y);\n commitOps(deps, ops, ops[0]?.label ?? 'Move Measurement');\n } finally {\n snap?.end();\n }\n },\n previewIds: () => [hit.id],\n previewOpaqueIds: () => [hit.id],\n previewPose: () => next()?.pose ?? null,\n previewData: () => next()?.data ?? null,\n };\n },\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"measureHit.js","names":[],"sources":["../../../src/features/measure/measureHit.ts"],"sourcesContent":["import type { NodeId, Scene } from 'core/scene/types';\nimport { measureEndsOf, type MeasureEnd, type MeasurePoint } from './measurement';\nimport { measureIds, type Measurements } from './measurements';\nimport { nodesAsShown, shownLookup } from 'core/scene/nodeAsShown';\nimport { nodeGates } from 'core/scene/nodeGates';\nimport type { SnapSource } from 'features/snap/types';\n\n/** Which part of which measurement lies under a point. */\nexport interface MeasureHit {\n id: NodeId;\n part: MeasureEnd | 'line';\n}\n\n\nfunction distanceToSegment(p: MeasurePoint, a: MeasurePoint, b: MeasurePoint): number {\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n const len2 = dx * dx + dy * dy;\n const t = len2 === 0 ? 0 : Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2));\n return Math.hypot(p.x - (a.x + t * dx), p.y - (a.y + t * dy));\n}\n\n/**\n * The measurement under `p`, newest first: an end within `tolerance` world\n * units of it, else a line within the same distance. A hidden or locked\n * measurement (`nodeGates`) is passed over.\n */\nexport function measureAt(\n scene: Pick<Scene<unknown, string, unknown>, 'renderOrderNodes' | 'get' | 'layers' | 'overrides'>,\n ms: Pick<Measurements, 'layer'>,\n p: MeasurePoint,\n tolerance: number,\n): MeasureHit | null {\n const gates = nodeGates(scene);\n // Hit where the ends are shown: a gesture moving one publishes it as a data override.\n const shown = shownLookup(scene);\n const ids = measureIds(scene, ms).reverse().filter((id) => !gates.hidden(id) && !gates.locked(id));\n for (const id of ids) {\n const ends = measureEndsOf(shown.get(id));\n if (!ends) continue;\n for (const part of ['end', 'start'] as const) {\n if (Math.hypot(p.x - ends[part].x, p.y - ends[part].y) <= tolerance) return { id, part };\n }\n }\n for (const id of ids) {\n const ends = measureEndsOf(shown.get(id));\n if (ends && distanceToSegment(p, ends.start, ends.end) <= tolerance) return { id, part: 'line' };\n }\n return null;\n}\n\n/** Options for `measureEndSource`. */\nexport interface MeasureEndSourceOptions {\n /** Overrides the canvas's reach for measurement ends, in screen px. */\n reachPx?: number;\n}\n\n/** The ends of every shown measurement as snap targets for a pointer probe. */\nexport function measureEndSource(opts: MeasureEndSourceOptions = {}): SnapSource {\n return {\n id: 'measure-ends',\n roles: ['pointer'],\n ...(opts.reachPx !== undefined ? { reachPx: opts.reachPx } : {}),\n *collect(q) {\n if (!q.scene) return;\n const gates = nodeGates(q.scene);\n const skip = new Set(q.exclude);\n const reach = q.reachWorld(q.reachPx);\n for (const node of nodesAsShown(q.scene.overrides, q.scene.renderOrderNodes())) {\n const ends = measureEndsOf(node);\n if (!ends || skip.has(node.id) || gates.hidden(node.id)) continue;\n for (const end of [ends.start, ends.end]) {\n const at = { x: end.x, y: end.y };\n for (const p of q.probes) {\n if (Math.hypot(at.x - p.at.x, at.y - p.at.y) > reach) continue;\n yield { probe: p.id, locus: { at, dirs: [] }, mark: { kind: 'point', at, glyph: 'center' } };\n }\n }\n }\n },\n };\n}\n"],"mappings":";;;;;;AAcA,SAAS,kBAAkB,GAAiB,GAAiB,GAAyB;CACpF,MAAM,KAAK,EAAE,IAAI,EAAE;CACnB,MAAM,KAAK,EAAE,IAAI,EAAE;CACnB,MAAM,OAAO,KAAK,KAAK,KAAK;CAC5B,MAAM,IAAI,SAAS,IAAI,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,EAAE,IAAI,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,MAAM,IAAI,CAAC;CAChG,OAAO,KAAK,MAAM,EAAE,KAAK,EAAE,IAAI,IAAI,KAAK,EAAE,KAAK,EAAE,IAAI,IAAI,GAAG;AAC9D;;;;;;AAOA,SAAgB,UACd,OACA,IACA,GACA,WACmB;CACnB,MAAM,QAAQ,UAAU,KAAK;CAE7B,MAAM,QAAQ,YAAY,KAAK;CAC/B,MAAM,MAAM,WAAW,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,OAAO,CAAC,MAAM,OAAO,EAAE,KAAK,CAAC,MAAM,OAAO,EAAE,CAAC;CACjG,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,OAAO,cAAc,MAAM,IAAI,EAAE,CAAC;EACxC,IAAI,CAAC,MAAM;EACX,KAAK,MAAM,QAAQ,CAAC,OAAO,OAAO,GAChC,IAAI,KAAK,MAAM,EAAE,IAAI,KAAK,KAAK,CAAC,GAAG,EAAE,IAAI,KAAK,KAAK,CAAC,CAAC,KAAK,WAAW,OAAO;GAAE;GAAI;EAAK;CAE3F;CACA,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,OAAO,cAAc,MAAM,IAAI,EAAE,CAAC;EACxC,IAAI,QAAQ,kBAAkB,GAAG,KAAK,OAAO,KAAK,GAAG,KAAK,WAAW,OAAO;GAAE;GAAI,MAAM;EAAO;CACjG;CACA,OAAO;AACT;;AASA,SAAgB,iBAAiB,OAAgC,CAAC,GAAe;CAC/E,OAAO;EACL,IAAI;EACJ,OAAO,CAAC,SAAS;EACjB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;EAC9D,CAAC,QAAQ,GAAG;GACV,IAAI,CAAC,EAAE,OAAO;GACd,MAAM,QAAQ,UAAU,EAAE,KAAK;GAC/B,MAAM,OAAO,IAAI,IAAI,EAAE,OAAO;GAC9B,MAAM,QAAQ,EAAE,WAAW,EAAE,OAAO;GACpC,KAAK,MAAM,QAAQ,aAAa,EAAE,MAAM,WAAW,EAAE,MAAM,iBAAiB,CAAC,GAAG;IAC9E,MAAM,OAAO,cAAc,IAAI;IAC/B,IAAI,CAAC,QAAQ,KAAK,IAAI,KAAK,EAAE,KAAK,MAAM,OAAO,KAAK,EAAE,GAAG;IACzD,KAAK,MAAM,OAAO,CAAC,KAAK,OAAO,KAAK,GAAG,GAAG;KACxC,MAAM,KAAK;MAAE,GAAG,IAAI;MAAG,GAAG,IAAI;KAAE;KAChC,KAAK,MAAM,KAAK,EAAE,QAAQ;MACxB,IAAI,KAAK,MAAM,GAAG,IAAI,EAAE,GAAG,GAAG,GAAG,IAAI,EAAE,GAAG,CAAC,IAAI,OAAO;MACtD,MAAM;OAAE,OAAO,EAAE;OAAI,OAAO;QAAE;QAAI,MAAM,CAAC;OAAE;OAAG,MAAM;QAAE,MAAM;QAAS;QAAI,OAAO;OAAS;MAAE;KAC7F;IACF;GACF;EACF;CACF;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"measureLayer.js","names":[],"sources":["../../../src/features/measure/measureLayer.ts"],"sourcesContent":["import type { RenderLayer } from 'core/layers/render';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { LayerHit } from 'affordances/types';\nimport type { Scene } from 'core/scene/types';\nimport { worldToScreen } from 'core/viewport/viewTransform';\nimport { viewToTransform } from 'core/viewport/view';\nimport { rectPath } from '@weasel-js/geom';\nimport { OVERLAY_ROLE_STROKES } from 'canvas/overlayRoleStrokes';\nimport { measureCommands } from 'canvas/nodeShapes/measurePainter';\nimport { measureEndsOf, type MeasurePoint } from './measurement';\nimport type { Measurements } from './measurements';\nimport { measureAt, type MeasureHit } from './measureHit';\nimport { MEASURE_HIT_PX } from './measureGestures';\n\n/** The registered layer's id; a press it claims routes as `layer:<id>`. */\nexport const MEASURE_LAYER_ID = 'weasel-measure';\n/** The affordance kind a press on a measurement routes under. */\nexport const MEASURE_AFFORDANCE_KIND = `layer:${MEASURE_LAYER_ID}`;\n\nconst HANDLE_PX = 3.5;\n\ntype AnyScene = Scene<unknown, string, unknown>;\n\n/**\n * The measurements' interactive layer, under every tool: it draws the\n * measurement a measure drag is making and a handle on each end of the picked\n * one, and claims a press on a measurement so a click picks it and a drag\n * edits it rather than reaching the tool. Under the measure tool it claims\n * only the picked measurement's ends, so a drag from anywhere else measures.\n */\nexport function measurementsLayer(scene: AnyScene, ms: Measurements): RenderLayer<unknown> {\n return {\n id: MEASURE_LAYER_ID,\n label: 'Measurements',\n space: 'screen',\n subscribe: (listener) => {\n const offs = [ms.picked.subscribe(listener), ms.draft.subscribe(listener), ms.measuring.subscribe(listener)];\n return () => { for (const off of offs) off(); };\n },\n draw: (_data, view) => {\n const t = viewToTransform(view);\n const toScreen = (p: MeasurePoint): MeasurePoint => {\n const [x, y] = worldToScreen(p.x, p.y, t);\n return { x, y };\n };\n const out: DrawCommand[] = [];\n const draft = ms.draft.get();\n if (draft) out.push(...measureCommands(toScreen(draft.start), toScreen(draft.end), 1));\n const picked = ms.picked.get();\n const ends = picked === null ? null : measureEndsOf(scene.get(picked));\n if (ends && !scene.isHidden(picked!)) {\n const stroke = { ...OVERLAY_ROLE_STROKES['measure'], width: 1 };\n for (const p of [ends.start, ends.end].map(toScreen)) {\n out.push({\n kind: 'path',\n path: rectPath(p.x - HANDLE_PX, p.y - HANDLE_PX, HANDLE_PX * 2, HANDLE_PX * 2),\n fill: { paint: { color: '#ffffff' } },\n stroke,\n });\n }\n }\n return out;\n },\n hitTest: (wx, wy, _data, view): LayerHit<MeasureHit> | null => {\n const hit = measureAt(scene, ms, { x: wx, y: wy }, MEASURE_HIT_PX / (view.scale.x || 1));\n if (!hit) return null;\n const onPickedEnd = hit.part !== 'line' && hit.id === ms.picked.get();\n if (ms.measuring.get() && !onPickedEnd) return null;\n return {\n initialScratch: hit,\n strength: 'exclusive',\n claimedKinds: ['pointer'],\n cursor: onPickedEnd ? 'crosshair' : 'move',\n };\n },\n };\n}\n"],"mappings":";;;;;;;;;;;AAeA,IAAa,mBAAmB;;AAEhC,IAAa,0BAA0B,SAAS;AAEhD,IAAM,YAAY;;;;;;;;AAWlB,SAAgB,kBAAkB,OAAiB,IAAwC;CACzF,OAAO;EACL,IAAI;EACJ,OAAO;EACP,OAAO;EACP,YAAY,aAAa;GACvB,MAAM,OAAO;IAAC,GAAG,OAAO,UAAU,QAAQ;IAAG,GAAG,MAAM,UAAU,QAAQ;IAAG,GAAG,UAAU,UAAU,QAAQ;GAAC;GAC3G,aAAa;IAAE,KAAK,MAAM,OAAO,MAAM,IAAI;GAAG;EAChD;EACA,OAAO,OAAO,SAAS;GACrB,MAAM,IAAI,gBAAgB,IAAI;GAC9B,MAAM,YAAY,MAAkC;IAClD,MAAM,CAAC,GAAG,KAAK,cAAc,EAAE,GAAG,EAAE,GAAG,CAAC;IACxC,OAAO;KAAE;KAAG;IAAE;GAChB;GACA,MAAM,MAAqB,CAAC;GAC5B,MAAM,QAAQ,GAAG,MAAM,IAAI;GAC3B,IAAI,OAAO,IAAI,KAAK,GAAG,gBAAgB,SAAS,MAAM,KAAK,GAAG,SAAS,MAAM,GAAG,GAAG,CAAC,CAAC;GACrF,MAAM,SAAS,GAAG,OAAO,IAAI;GAC7B,MAAM,OAAO,WAAW,OAAO,OAAO,cAAc,MAAM,IAAI,MAAM,CAAC;GACrE,IAAI,QAAQ,CAAC,MAAM,SAAS,MAAO,GAAG;IACpC,MAAM,SAAS;KAAE,GAAG,qBAAqB;KAAY,OAAO;IAAE;IAC9D,KAAK,MAAM,KAAK,CAAC,KAAK,OAAO,KAAK,GAAG,CAAC,CAAC,IAAI,QAAQ,GACjD,IAAI,KAAK;KACP,MAAM;KACN,MAAM,SAAS,EAAE,IAAI,WAAW,EAAE,IAAI,WAAW,YAAY,GAAG,YAAY,CAAC;KAC7E,MAAM,EAAE,OAAO,EAAE,OAAO,UAAU,EAAE;KACpC;IACF,CAAC;GAEL;GACA,OAAO;EACT;EACA,UAAU,IAAI,IAAI,OAAO,SAAsC;GAC7D,MAAM,MAAM,UAAU,OAAO,IAAI;IAAE,GAAG;IAAI,GAAG;GAAG,GAAG,kBAAkB,KAAK,MAAM,KAAK,EAAE;GACvF,IAAI,CAAC,KAAK,OAAO;GACjB,MAAM,cAAc,IAAI,SAAS,UAAU,IAAI,OAAO,GAAG,OAAO,IAAI;GACpE,IAAI,GAAG,UAAU,IAAI,KAAK,CAAC,aAAa,OAAO;GAC/C,OAAO;IACL,gBAAgB;IAChB,UAAU;IACV,cAAc,CAAC,SAAS;IACxB,QAAQ,cAAc,cAAc;GACtC;EACF;CACF;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"measurement.js","names":[],"sources":["../../../src/features/measure/measurement.ts"],"sourcesContent":["import type { Node, NodeId, RectPose } from 'core/scene/types';\n\n/** A world-space point. */\nexport interface MeasurePoint { x: number; y: number }\n\n/**\n * The offset from `start` to `end`, in world units. `dx` and `dy` are on the\n * world's own axes, where y grows downward; `angle` reads like a protractor\n * instead, counterclockwise-positive on screen, so a line rising up and to\n * the right at 30° reads +30°. The canvas's own rotations are\n * clockwise-positive: a rotate that follows the line turns by `-angle`.\n */\nexport interface Measurement {\n readonly start: MeasurePoint;\n readonly end: MeasurePoint;\n readonly dx: number;\n readonly dy: number;\n readonly distance: number;\n /** `atan2(-dy, dx)`, in `(-π, π]`. Zero for a zero-length measurement. */\n readonly angle: number;\n}\n\n/** Measure from `start` to `end`. */\nexport function measureBetween(start: MeasurePoint, end: MeasurePoint): Measurement {\n const dx = end.x - start.x;\n const dy = end.y - start.y;\n return {\n start: { x: start.x, y: start.y },\n end: { x: end.x, y: end.y },\n dx,\n dy,\n distance: Math.hypot(dx, dy),\n // `|| 0` drops the sign of a negated zero, which would read a leftward line as -π.\n angle: dx === 0 && dy === 0 ? 0 : Math.atan2(-dy || 0, dx),\n };\n}\n\n/** What a measurement node keeps in `data`: its two ends, in world units. A\n * `label` beside it names the node in a layer list. */\nexport interface MeasureData {\n measure: { start: MeasurePoint; end: MeasurePoint };\n label?: string;\n}\n\n/** Which end of a measurement. */\nexport type MeasureEnd = 'start' | 'end';\n\n\n/** The ends `node` measures between, or null when it is not a measurement. */\nexport function measureEndsOf(node: Pick<Node<unknown, string, unknown>, 'data'> | undefined): MeasureData['measure'] | null {\n const m = (node?.data as Partial<MeasureData> | null | undefined)?.measure;\n if (!m || typeof m !== 'object' || !m.start || !m.end) return null;\n return m;\n}\n\n/** The measurement `node` holds, or null when it is not one. */\nexport function measurementOf(node: Pick<Node<unknown, string, unknown>, 'data'> | undefined): Measurement | null {\n const ends = measureEndsOf(node);\n return ends ? measureBetween(ends.start, ends.end) : null;\n}\n\n/** The box a measurement's pose holds: its two ends' bounds, which is what\n * culling, area queries, and fitting the view read. */\nexport function measurePose(start: MeasurePoint, end: MeasurePoint): RectPose {\n return {\n x: Math.min(start.x, end.x),\n y: Math.min(start.y, end.y),\n width: Math.abs(end.x - start.x),\n height: Math.abs(end.y - start.y),\n };\n}\n\n/** A measurement node on `layer`, ready to insert. */\nexport function measureNode(id: NodeId, layer: string, start: MeasurePoint, end: MeasurePoint): Node<unknown, string, unknown> {\n const data: MeasureData = { measure: { start: { ...start }, end: { ...end } } };\n return { id, kind: 'mark', layer, parent: null, pose: measurePose(start, end), data };\n}\n\n/** `node`'s data and pose with its `which` end moved to `to`. */\nexport function withMeasureEnd(\n node: Node<unknown, string, unknown>,\n which: MeasureEnd,\n to: MeasurePoint,\n): { data: MeasureData; pose: RectPose } | null {\n const ends = measureEndsOf(node);\n if (!ends) return null;\n const next = { ...ends, [which]: { x: to.x, y: to.y } };\n const data: MeasureData = { ...(node.data as MeasureData), measure: next };\n return { data, pose: measurePose(next.start, next.end) };\n}\n"],"mappings":";;AAuBA,SAAgB,eAAe,OAAqB,KAAgC;CAClF,MAAM,KAAK,IAAI,IAAI,MAAM;CACzB,MAAM,KAAK,IAAI,IAAI,MAAM;CACzB,OAAO;EACL,OAAO;GAAE,GAAG,MAAM;GAAG,GAAG,MAAM;EAAE;EAChC,KAAK;GAAE,GAAG,IAAI;GAAG,GAAG,IAAI;EAAE;EAC1B;EACA;EACA,UAAU,KAAK,MAAM,IAAI,EAAE;EAE3B,OAAO,OAAO,KAAK,OAAO,IAAI,IAAI,KAAK,MAAM,CAAC,MAAM,GAAG,EAAE;CAC3D;AACF;;AAcA,SAAgB,cAAc,MAA+F;CAC3H,MAAM,KAAK,MAAM,KAAA,EAAkD;CACnE,IAAI,CAAC,KAAK,OAAO,MAAM,YAAY,CAAC,EAAE,SAAS,CAAC,EAAE,KAAK,OAAO;CAC9D,OAAO;AACT;;AAGA,SAAgB,cAAc,MAAoF;CAChH,MAAM,OAAO,cAAc,IAAI;CAC/B,OAAO,OAAO,eAAe,KAAK,OAAO,KAAK,GAAG,IAAI;AACvD;;;AAIA,SAAgB,YAAY,OAAqB,KAA6B;CAC5E,OAAO;EACL,GAAG,KAAK,IAAI,MAAM,GAAG,IAAI,CAAC;EAC1B,GAAG,KAAK,IAAI,MAAM,GAAG,IAAI,CAAC;EAC1B,OAAO,KAAK,IAAI,IAAI,IAAI,MAAM,CAAC;EAC/B,QAAQ,KAAK,IAAI,IAAI,IAAI,MAAM,CAAC;CAClC;AACF;;AAGA,SAAgB,YAAY,IAAY,OAAe,OAAqB,KAAmD;CAC7H,MAAM,OAAoB,EAAE,SAAS;EAAE,OAAO,EAAE,GAAG,MAAM;EAAG,KAAK,EAAE,GAAG,IAAI;CAAE,EAAE;CAC9E,OAAO;EAAE;EAAI,MAAM;EAAQ;EAAO,QAAQ;EAAM,MAAM,YAAY,OAAO,GAAG;EAAG;CAAK;AACtF;;AAGA,SAAgB,eACd,MACA,OACA,IAC8C;CAC9C,MAAM,OAAO,cAAc,IAAI;CAC/B,IAAI,CAAC,MAAM,OAAO;CAClB,MAAM,OAAO;EAAE,GAAG;GAAO,QAAQ;GAAE,GAAG,GAAG;GAAG,GAAG,GAAG;EAAE;CAAE;CAEtD,OAAO;EAAE;GADmB,GAAI,KAAK;GAAsB,SAAS;EAC3D;EAAM,MAAM,YAAY,KAAK,OAAO,KAAK,GAAG;CAAE;AACzD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"measurements.js","names":[],"sources":["../../../src/features/measure/measurements.ts"],"sourcesContent":["import { createEmitter } from '@weasel-js/registry';\nimport type { NodeId, Scene } from 'core/scene/types';\nimport { measurementOf, type Measurement } from './measurement';\n\n/** One value, readable and watchable — what a readout re-renders from. */\nexport interface MeasureCell<T> {\n get(): T;\n set(next: T): void;\n subscribe(listener: () => void): () => void;\n}\n\nfunction cell<T>(initial: T): MeasureCell<T> {\n let value = initial;\n const changed = createEmitter();\n return {\n get: () => value,\n set: (next) => {\n if (Object.is(next, value)) return;\n value = next;\n changed.emit();\n },\n subscribe: changed.subscribe,\n };\n}\n\n/**\n * Where a scene's measurements go, and the state around them that is not\n * the document's: which one is picked, and the one a measure drag is still\n * drawing. The measurements themselves are nodes on `layer`, an annotation\n * layer (`isAnnotationLayer`), so they save with the scene, undo with it,\n * and stay out of export and content picking.\n *\n * The pick sits beside the content selection rather than in it: picking a\n * measurement leaves the selection alone, so Move by can carry the selection\n * by the picked measurement.\n */\nexport interface Measurements {\n /** The annotation layer new measurements are added to. */\n readonly layer: string;\n /** The picked measurement's id. */\n readonly picked: MeasureCell<NodeId | null>;\n /** The measurement a measure drag is drawing, before it is added. */\n readonly draft: MeasureCell<Measurement | null>;\n /** Whether the measure tool is active, which changes what a press on a\n * measurement does: under it, only the picked one's ends take a drag, so a\n * drag from anywhere else starts a new measurement. */\n readonly measuring: MeasureCell<boolean>;\n}\n\n/** The measurements on `layer`, none picked and none in progress. */\nexport function createMeasurements(options: { layer: string }): Measurements {\n return {\n layer: options.layer,\n picked: cell<NodeId | null>(null),\n draft: cell<Measurement | null>(null),\n measuring: cell(false),\n };\n}\n\n\n/** The measurement nodes on `ms.layer`, oldest first. */\nexport function measureIds(scene: Pick<Scene<unknown, string, unknown>, 'renderOrderNodes'>, ms: Pick<Measurements, 'layer'>): NodeId[] {\n return scene.renderOrderNodes()\n .filter((n) => n.layer === ms.layer && measurementOf(n) !== null)\n .map((n) => n.id);\n}\n\n/** The measurement commands act on: the picked one while it is still in the\n * scene, else the newest. */\nexport function currentMeasureId(\n scene: Pick<Scene<unknown, string, unknown>, 'renderOrderNodes' | 'get'>,\n ms: Pick<Measurements, 'layer' | 'picked'>,\n): NodeId | null {\n const picked = ms.picked.get();\n if (picked !== null && measurementOf(scene.get(picked)) !== null) return picked;\n return measureIds(scene, ms).at(-1) ?? null;\n}\n"],"mappings":";;;;AAWA,SAAS,KAAQ,SAA4B;CAC3C,IAAI,QAAQ;CACZ,MAAM,UAAU,cAAc;CAC9B,OAAO;EACL,WAAW;EACX,MAAM,SAAS;GACb,IAAI,OAAO,GAAG,MAAM,KAAK,GAAG;GAC5B,QAAQ;GACR,QAAQ,KAAK;EACf;EACA,WAAW,QAAQ;CACrB;AACF;;AA2BA,SAAgB,mBAAmB,SAA0C;CAC3E,OAAO;EACL,OAAO,QAAQ;EACf,QAAQ,KAAoB,IAAI;EAChC,OAAO,KAAyB,IAAI;EACpC,WAAW,KAAK,KAAK;CACvB;AACF;;AAIA,SAAgB,WAAW,OAAkE,IAA2C;CACtI,OAAO,MAAM,iBAAiB,CAAC,CAC5B,QAAQ,MAAM,EAAE,UAAU,GAAG,SAAS,cAAc,CAAC,MAAM,IAAI,CAAC,CAChE,KAAK,MAAM,EAAE,EAAE;AACpB;;;AAIA,SAAgB,iBACd,OACA,IACe;CACf,MAAM,SAAS,GAAG,OAAO,IAAI;CAC7B,IAAI,WAAW,QAAQ,cAAc,MAAM,IAAI,MAAM,CAAC,MAAM,MAAM,OAAO;CACzE,OAAO,WAAW,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK;AACzC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"modeLayers.js","names":[],"sources":["../../../src/features/modes/modeLayers.ts"],"sourcesContent":["import type { Rect } from '@weasel-js/geom';\nimport type { ModeDecorations, ModeRegistry, WorkspaceVisual } from '@weasel-js/modes';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { RenderLayer } from 'core/layers/render';\nimport { viewToTransform } from 'core/viewport/view';\nimport { worldToScreen } from 'core/viewport/viewTransform';\n\n/** Options for {@link workspaceTintLayer}. */\nexport interface WorkspaceTintLayerOptions {\n registry: ModeRegistry;\n /** The page, in world coordinates. When given, the tint covers only the\n * workspace around it, each strip fading toward the page edge if the mode\n * names a gradient. Without it the tint covers the whole canvas. */\n page?: () => Rect | null;\n /** Opacity for every mode's tint, in place of its `workspace.intensity`. */\n intensity?: number;\n /** The tint shown in a mode with none of its own, and what announces a change to it. */\n fallback?: { visual: () => WorkspaceVisual | null; subscribe: (fn: () => void) => () => void };\n id?: string;\n label?: string;\n}\n\nconst DEFAULT_INTENSITY = 0.12;\n\n/**\n * A screen-space layer painting the active mode's `WorkspaceVisual`, or the `fallback`'s in a\n * mode without one: a wash at `intensity`, else that visual's own, bottom-up or top-down when it\n * names a gradient.\n */\nexport function workspaceTintLayer(opts: WorkspaceTintLayerOptions): RenderLayer<unknown> {\n const { registry } = opts;\n return {\n id: opts.id ?? 'mode-tint',\n label: opts.label ?? 'Mode tint',\n space: 'screen',\n subscribe: (repaint) => {\n const offMode = registry.subscribe(repaint);\n const offFallback = opts.fallback?.subscribe(repaint);\n return () => { offMode(); offFallback?.(); };\n },\n draw: (_data, view, dims) => {\n const own = registry.current().workspace;\n const ws = own?.tint && own.tint !== 'transparent' ? own : opts.fallback?.visual() ?? undefined;\n const tint = ws?.tint;\n if (!ws || !tint || tint === 'transparent') return [];\n const opacity = opts.intensity ?? ws.intensity ?? DEFAULT_INTENSITY;\n const { width: W, height: H } = dims;\n\n const strip = (\n x: number, y: number, width: number, height: number,\n from: { x: number; y: number }, to: { x: number; y: number },\n ): DrawCommand => ({\n kind: 'path',\n path: { kind: 'rect', x, y, width, height },\n fill: { paint: ws.gradient\n ? { kind: 'linear-gradient', from, to, stops: [{ offset: 0, color: tint }, { offset: 1, color: 'transparent' }], opacity }\n : { kind: 'solid', color: tint, opacity } },\n });\n\n const whole = (): DrawCommand[] => [\n ws.gradient === 'top-down'\n ? strip(0, 0, W, H, { x: 0, y: 0 }, { x: 0, y: H })\n : strip(0, 0, W, H, { x: 0, y: H }, { x: 0, y: 0 }),\n ];\n\n const page = opts.page?.();\n if (!page) return whole();\n\n const t = viewToTransform(view);\n const [sx0, sy0] = worldToScreen(page.x, page.y, t);\n const [sx1, sy1] = worldToScreen(page.x + page.width, page.y + page.height, t);\n const pl = Math.max(0, Math.min(sx0, sx1));\n const pt = Math.max(0, Math.min(sy0, sy1));\n const pr = Math.min(W, Math.max(sx0, sx1));\n const pb = Math.min(H, Math.max(sy0, sy1));\n if (pr <= pl || pb <= pt) {\n return [{ kind: 'path', path: { kind: 'rect', x: 0, y: 0, width: W, height: H }, fill: { paint: { kind: 'solid', color: tint, opacity } } }];\n }\n\n const cmds: DrawCommand[] = [];\n const push = (\n x: number, y: number, w: number, h: number,\n from: { x: number; y: number }, to: { x: number; y: number },\n ): void => { if (w > 0 && h > 0) cmds.push(strip(x, y, w, h, from, to)); };\n push(0, 0, W, pt, { x: 0, y: 0 }, { x: 0, y: pt });\n push(0, pb, W, H - pb, { x: 0, y: H }, { x: 0, y: pb });\n push(0, pt, pl, pb - pt, { x: 0, y: 0 }, { x: pl, y: 0 });\n push(pr, pt, W - pr, pb - pt, { x: W, y: 0 }, { x: pr, y: 0 });\n return cmds;\n },\n };\n}\n\n/** A world-space layer drawing a `ModeDecorations`' active painter. It\n * subscribes to the decorations, so a mode switch or a new painter repaints\n * it. */\nexport function modeDecorationLayer(\n decorations: ModeDecorations,\n opts: { id?: string; label?: string } = {},\n): RenderLayer<unknown> {\n return {\n id: opts.id ?? 'mode-decorations',\n label: opts.label ?? 'Mode decorations',\n subscribe: decorations.subscribe,\n draw: () => decorations.paint() as DrawCommand[],\n };\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"modeTriggers.js","names":[],"sources":["../../../src/features/modes/modeTriggers.ts"],"sourcesContent":["import { modeLabel, type ModeDefinition, type ModeRegistry } from '@weasel-js/modes';\nimport { ActionDisabledReason, type Action, type ActionDeps, type Contribution } from '@weasel-js/routing';\n\n/** What a mode's double-click trigger does. */\nexport interface ModeTriggerHandlers {\n /** Enter mode `id` on the node `targetId`. Without it nothing is bound. */\n enter?: (id: string, targetId: string) => void;\n /**\n * Whether mode `id` can be entered on `targetId` now. Where it says no the double-click falls\n * through to the next binding on it, so a canvas action that already enters the mode on some\n * of those nodes keeps them: `enterPathEdit` on a path with anchors, say. Default yes.\n */\n canEnter?: (id: string, targetId: string) => boolean;\n}\n\n/**\n * The registry's double-click entries as an always-on contribution: for each mode whose\n * `entry.trigger` is `'double-click-target'`, one action bound to a double-click on each routing\n * kind its `entry.targets` names, entering the mode on the node double-clicked. Install it with\n * `<SceneCanvas ambient>`, beside `modeShortcuts`.\n *\n * The target is the node the dispatcher classified the double-click on (the `hitAt` dep's\n * `doubleClick`, else its hit), the one the binding's `kind:` target matched: a double-click on\n * a grouped member names its outermost group inside the entered group (`<SceneCanvas enteredGroup>`).\n * The action is live in every mode, the target mode included, so a double-click on another node\n * moves a mode onto it; `canEnter` and `enter` decide what a switch from the current mode means.\n *\n * The bindings ride the ambient tier: a tool that claims the double-click itself, the pen\n * finishing a stroke, keeps it.\n */\nexport function modeTriggers(registry: ModeRegistry, handlers: ModeTriggerHandlers): Contribution<never> {\n const actions: Action[] = [];\n const { enter } = handlers;\n if (enter) {\n for (const mode of registry.list()) {\n const targets = mode.entry?.trigger === 'double-click-target' ? mode.entry.targets ?? [] : [];\n if (targets.length > 0) actions.push(triggerAction(mode, targets, enter, handlers.canEnter));\n }\n }\n return { id: 'modes.triggers', eligibility: { always: true }, actions };\n}\n\nfunction triggerAction(\n mode: ModeDefinition,\n targets: readonly string[],\n enter: (id: string, targetId: string) => void,\n canEnter: ModeTriggerHandlers['canEnter'],\n): Action {\n const targetAt = (deps: ActionDeps | undefined, at: { x: number; y: number } | undefined): string | null => {\n if (!at) return null;\n const hit = deps?.hitAt?.(at);\n const id = (hit?.doubleClick ?? hit)?.id;\n if (id === undefined) return null;\n return canEnter?.(mode.id, id) === false ? null : id;\n };\n const id = `mode.${mode.id}.trigger`;\n return {\n id,\n label: `Enter ${modeLabel(mode)}`,\n defaultBinding: targets.map((kind) => ({\n spec: { kind: 'doubleClick' as const, target: `kind:${kind}` as const },\n opts: { id: `${id}.doubleClick.${kind}` },\n })),\n requires: ['hitAt'],\n enabled: (deps, at) => (targetAt(deps, at) !== null ? true : ActionDisabledReason.NotApplicable),\n invoker: {\n timing: 'immediate',\n run: (deps, params) => {\n const x = params?.worldX, y = params?.worldY;\n const id = typeof x === 'number' && typeof y === 'number' ? targetAt(deps, { x, y }) : null;\n if (id !== null) enter(mode.id, id);\n },\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,aAAa,UAAwB,UAAoD;CACvG,MAAM,UAAoB,CAAC;CAC3B,MAAM,EAAE,UAAU;CAClB,IAAI,OACF,KAAK,MAAM,QAAQ,SAAS,KAAK,GAAG;EAClC,MAAM,UAAU,KAAK,OAAO,YAAY,wBAAwB,KAAK,MAAM,WAAW,CAAC,IAAI,CAAC;EAC5F,IAAI,QAAQ,SAAS,GAAG,QAAQ,KAAK,cAAc,MAAM,SAAS,OAAO,SAAS,QAAQ,CAAC;CAC7F;CAEF,OAAO;EAAE,IAAI;EAAkB,aAAa,EAAE,QAAQ,KAAK;EAAG;CAAQ;AACxE;AAEA,SAAS,cACP,MACA,SACA,OACA,UACQ;CACR,MAAM,YAAY,MAA8B,OAA4D;EAC1G,IAAI,CAAC,IAAI,OAAO;EAChB,MAAM,MAAM,MAAM,QAAQ,EAAE;EAC5B,MAAM,MAAM,KAAK,eAAe,IAAA,EAAM;EACtC,IAAI,OAAO,QAAW,OAAO;EAC7B,OAAO,WAAW,KAAK,IAAI,EAAE,MAAM,QAAQ,OAAO;CACpD;CACA,MAAM,KAAK,QAAQ,KAAK,GAAG;CAC3B,OAAO;EACL;EACA,OAAO,SAAS,UAAU,IAAI;EAC9B,gBAAgB,QAAQ,KAAK,UAAU;GACrC,MAAM;IAAE,MAAM;IAAwB,QAAQ,QAAQ;GAAgB;GACtE,MAAM,EAAE,IAAI,GAAG,GAAG,eAAe,OAAO;EAC1C,EAAE;EACF,UAAU,CAAC,OAAO;EAClB,UAAU,MAAM,OAAQ,SAAS,MAAM,EAAE,MAAM,OAAO,OAAO,qBAAqB;EAClF,SAAS;GACP,QAAQ;GACR,MAAM,MAAM,WAAW;IACrB,MAAM,IAAI,QAAQ,QAAQ,IAAI,QAAQ;IACtC,MAAM,KAAK,OAAO,MAAM,YAAY,OAAO,MAAM,WAAW,SAAS,MAAM;KAAE;KAAG;IAAE,CAAC,IAAI;IACvF,IAAI,OAAO,MAAM,MAAM,KAAK,IAAI,EAAE;GACpC;EACF;CACF;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"effectSources.js","names":[],"sources":["../../../src/features/pathEffects/effectSources.ts"],"sourcesContent":["/**\n * A path effect that hides its sources is edited through itself: its text and\n * its path are reached from the effect node.\n */\nimport { isTextData } from '@weasel-js/text';\nimport { pathEffectOf } from 'core/scene/pathEffects/derive';\nimport { sourcesOf } from 'core/scene/dependents';\nimport type { NodeId, Scene } from 'core/scene/types';\n\n/**\n * For a path-effect node with `hideSources`, the source an edit of `role`\n * goes to: `'text'`, its first source when that is a text node; `'path'`, the\n * path it lays along — the second source of an along kind, a blend's spine.\n * `null` otherwise. The one answer every edit route asks. It takes a source\n * hidden before the effect was made too: either way the effect is what shows it.\n */\nexport function effectSourceFor<TData, TLayer extends string, TPose>(\n scene: Pick<Scene<TData, TLayer, TPose>, 'get'>,\n id: NodeId,\n role: 'text' | 'path',\n): NodeId | null {\n const node = scene.get(id);\n const effect = pathEffectOf(node);\n if (effect === null || effect.hideSources !== true) return null;\n const at = role === 'text' ? 0 : effect.kind === 'blend' ? 2 : 1;\n const source = sourcesOf(node)[at];\n const found = source === undefined ? undefined : scene.get(source);\n if (found === undefined) return null;\n return role === 'text' && !isTextData(found.data) ? null : source!;\n}\n\n/**\n * The sources path-effect node `id` holds hidden: with `hideSources`, each of\n * its sources but those it found hidden. Empty for any other node.\n */\nexport function sourcesHiddenBy<TData, TLayer extends string, TPose>(\n scene: Pick<Scene<TData, TLayer, TPose>, 'get'>,\n id: NodeId,\n): NodeId[] {\n const node = scene.get(id);\n const effect = pathEffectOf(node);\n if (effect?.hideSources !== true) return [];\n const found = effect.foundHidden ?? [];\n return sourcesOf(node).filter((_, at) => !found.includes(at));\n}\n\n/**\n * The sources a removal of `ids` takes with it: those that a path effect with\n * `hideSources` in the removal hid, and that nothing staying still derives\n * from. A source another effect still reads stays, and so does one shown\n * again since or named in `keep`. Transitive, so an effect's hidden effect\n * takes its own too.\n */\nexport function hiddenSourcesTakenWith<TData, TLayer extends string, TPose>(\n scene: Pick<Scene<TData, TLayer, TPose>, 'get' | 'removalClosure'>,\n ids: readonly NodeId[],\n keep: ReadonlySet<NodeId> = new Set(),\n): NodeId[] {\n const going = new Set<NodeId>(scene.removalClosure(ids));\n const taken: NodeId[] = [];\n const within = (node: NodeId, root: NodeId): boolean => {\n for (let at: NodeId | null | undefined = node; at != null; at = scene.get(at)?.parent) if (at === root) return true;\n return false;\n };\n let grew = true;\n while (grew) {\n grew = false;\n for (const id of [...going]) {\n for (const source of sourcesHiddenBy(scene, id)) {\n if (going.has(source) || keep.has(source) || scene.get(source)?.hidden !== true) continue;\n const closure = scene.removalClosure([source]);\n if (!closure.every((n) => going.has(n) || within(n, source))) continue;\n for (const n of closure) going.add(n);\n taken.push(source);\n grew = true;\n }\n }\n }\n return taken;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,SAAgB,gBACd,OACA,IACA,MACe;CACf,MAAM,OAAO,MAAM,IAAI,EAAE;CACzB,MAAM,SAAS,aAAa,IAAI;CAChC,IAAI,WAAW,QAAQ,OAAO,gBAAgB,MAAM,OAAO;CAC3D,MAAM,KAAK,SAAS,SAAS,IAAI,OAAO,SAAS,UAAU,IAAI;CAC/D,MAAM,SAAS,UAAU,IAAI,CAAC,CAAC;CAC/B,MAAM,QAAQ,WAAW,SAAY,SAAY,MAAM,IAAI,MAAM;CACjE,IAAI,UAAU,QAAW,OAAO;CAChC,OAAO,SAAS,UAAU,CAAC,WAAW,MAAM,IAAI,IAAI,OAAO;AAC7D;;;;;AAMA,SAAgB,gBACd,OACA,IACU;CACV,MAAM,OAAO,MAAM,IAAI,EAAE;CACzB,MAAM,SAAS,aAAa,IAAI;CAChC,IAAI,QAAQ,gBAAgB,MAAM,OAAO,CAAC;CAC1C,MAAM,QAAQ,OAAO,eAAe,CAAC;CACrC,OAAO,UAAU,IAAI,CAAC,CAAC,QAAQ,GAAG,OAAO,CAAC,MAAM,SAAS,EAAE,CAAC;AAC9D;;;;;;;;AASA,SAAgB,uBACd,OACA,KACA,uBAA4B,IAAI,IAAI,GAC1B;CACV,MAAM,QAAQ,IAAI,IAAY,MAAM,eAAe,GAAG,CAAC;CACvD,MAAM,QAAkB,CAAC;CACzB,MAAM,UAAU,MAAc,SAA0B;EACtD,KAAK,IAAI,KAAgC,MAAM,MAAM,MAAM,KAAK,MAAM,IAAI,EAAE,CAAC,EAAE,QAAQ,IAAI,OAAO,MAAM,OAAO;EAC/G,OAAO;CACT;CACA,IAAI,OAAO;CACX,OAAO,MAAM;EACX,OAAO;EACP,KAAK,MAAM,MAAM,CAAC,GAAG,KAAK,GACxB,KAAK,MAAM,UAAU,gBAAgB,OAAO,EAAE,GAAG;GAC/C,IAAI,MAAM,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,CAAC,EAAE,WAAW,MAAM;GACjF,MAAM,UAAU,MAAM,eAAe,CAAC,MAAM,CAAC;GAC7C,IAAI,CAAC,QAAQ,OAAO,MAAM,MAAM,IAAI,CAAC,KAAK,OAAO,GAAG,MAAM,CAAC,GAAG;GAC9D,KAAK,MAAM,KAAK,SAAS,MAAM,IAAI,CAAC;GACpC,MAAM,KAAK,MAAM;GACjB,OAAO;EACT;CAEJ;CACA,OAAO;AACT"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/features/pathEffects/index.ts"],"sourcesContent":["/**\n * The kit's path effects, wired to its painters: the derivations a scene\n * holding blends, bends and repeats registers, and the registry helper that\n * registers them.\n */\nimport {\n createPathEffectDerivations, PATH_EFFECT, type PathEffectDerivations,\n} from 'core/scene/pathEffects/derive';\nimport type { SceneRegistry } from 'core/scene/types';\nimport { readEffectSource } from './readSource';\n\nexport { readEffectSource };\n\n/** The kit's path-effect derivations, reading sources through the painter\n * registry. One pair for every scene, so a saved node finds them by key. */\nexport const PATH_EFFECT_DERIVATIONS: PathEffectDerivations = createPathEffectDerivations(readEffectSource);\n\n/**\n * `registry` with the path-effect derivations under `kit:pathEffect`, for\n * `createScene({ registry })` or `sceneFromJSON(json, { registry })`. A scene\n * built without them cannot hold a path effect: the actions that make one are\n * disabled there, and a saved one does not load.\n */\nexport function withPathEffectsRegistry<TPose, TData = unknown, TLayer extends string = string>(\n registry: SceneRegistry<TPose, TData, TLayer> = {},\n): SceneRegistry<TPose, TData, TLayer> {\n type Fns<K extends 'derivePath' | 'derivePose'> = NonNullable<SceneRegistry<TPose, TData, TLayer>[K]>[string];\n return {\n ...registry,\n derivePath: { [PATH_EFFECT]: PATH_EFFECT_DERIVATIONS.derivePath as unknown as Fns<'derivePath'>, ...registry.derivePath },\n derivePose: { [PATH_EFFECT]: PATH_EFFECT_DERIVATIONS.derivePose as unknown as Fns<'derivePose'>, ...registry.derivePose },\n };\n}\n"],"mappings":";;;;;;;;;;;AAeA,IAAa,0BAAiD,4BAA4B,gBAAgB;;;;;;;AAQ1G,SAAgB,wBACd,WAAgD,CAAC,GACZ;CAErC,OAAO;EACL,GAAG;EACH,YAAY;IAAG,cAAc,wBAAwB;GAA4C,GAAG,SAAS;EAAW;EACxH,YAAY;IAAG,cAAc,wBAAwB;GAA4C,GAAG,SAAS;EAAW;CAC1H;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"readSource.js","names":[],"sources":["../../../src/features/pathEffects/readSource.ts"],"sourcesContent":["/**\n * How the kit's path effects read a source node: its outline from its\n * painter's silhouette, its paint as `kit:path` resolves it.\n */\nimport { pathThroughPoseFrame, poseLinearOf, type PathInWorldPose } from '@weasel-js/geom';\nimport type { Fill, Stroke } from '@weasel-js/paint';\nimport { isTextData, type TextOutlineSource } from '@weasel-js/text';\nimport { findNodeShape } from 'canvas/NodeShape';\nimport { isRectPose } from 'core/geometry/autoPoseDescriptor';\nimport { noteFontRead } from 'core/scene/fontReads';\nimport type { EffectSource } from 'core/scene/pathEffects/source';\nimport type { DerivedDep } from 'core/scene/types';\nimport { pathPaintOf } from 'canvas/nodeShapes/nodePaint';\nimport { textEffectItems } from './textItems';\n\n/** `dep` as a path effect's source, or `null` when it has no outline. A text\n * node's source also carries its grapheme clusters as items. */\nexport function readEffectSource(dep: DerivedDep<unknown>): EffectSource | null {\n const data = dep.node.data;\n const text = isTextData(data);\n // A text node's outline is laid out in its face, which may not have landed: without this read, an effect that found\n // no outline while the face loads would never look again.\n if (text) noteFontRead();\n const source = readShape(dep);\n const pose = dep.pose;\n if (source === null || !text || !isRectPose(pose)) return source;\n return { ...source, items: () => textEffectItems(data as TextOutlineSource, pose as PathInWorldPose) };\n}\n\nfunction readShape(dep: DerivedDep<unknown>): EffectSource | null {\n const data = (dep.node.data ?? {}) as { fill?: Fill | null; stroke?: Stroke | null };\n const { fill, stroke } = pathPaintOf(data, dep.inherited);\n const paint = { fill, stroke: stroke ?? null };\n const shown = dep.path;\n if (shown !== null) return { world: shown, ...paint };\n const local = findNodeShape(dep.node)?.silhouette?.(dep.node, dep.pose) ?? null;\n if (local === null) return null;\n const world = pathThroughPoseFrame(local, dep.pose);\n const pose = dep.pose;\n if (!isRectPose(pose) || poseLinearOf(pose) !== null) return { world, ...paint };\n return {\n world,\n framed: { path: local, cx: pose.x + pose.width / 2, cy: pose.y + pose.height / 2, rotation: pose.rotation ?? 0 },\n ...paint,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAiBA,SAAgB,iBAAiB,KAA+C;CAC9E,MAAM,OAAO,IAAI,KAAK;CACtB,MAAM,OAAO,WAAW,IAAI;CAG5B,IAAI,MAAM,aAAa;CACvB,MAAM,SAAS,UAAU,GAAG;CAC5B,MAAM,OAAO,IAAI;CACjB,IAAI,WAAW,QAAQ,CAAC,QAAQ,CAAC,WAAW,IAAI,GAAG,OAAO;CAC1D,OAAO;EAAE,GAAG;EAAQ,aAAa,gBAAgB,MAA2B,IAAuB;CAAE;AACvG;AAEA,SAAS,UAAU,KAA+C;CAChE,MAAM,OAAQ,IAAI,KAAK,QAAQ,CAAC;CAChC,MAAM,EAAE,MAAM,WAAW,YAAY,MAAM,IAAI,SAAS;CACxD,MAAM,QAAQ;EAAE;EAAM,QAAQ,UAAU;CAAK;CAC7C,MAAM,QAAQ,IAAI;CAClB,IAAI,UAAU,MAAM,OAAO;EAAE,OAAO;EAAO,GAAG;CAAM;CACpD,MAAM,QAAQ,cAAc,IAAI,IAAI,CAAC,EAAE,aAAa,IAAI,MAAM,IAAI,IAAI,KAAK;CAC3E,IAAI,UAAU,MAAM,OAAO;CAC3B,MAAM,QAAQ,qBAAqB,OAAO,IAAI,IAAI;CAClD,MAAM,OAAO,IAAI;CACjB,IAAI,CAAC,WAAW,IAAI,KAAK,aAAa,IAAI,MAAM,MAAM,OAAO;EAAE;EAAO,GAAG;CAAM;CAC/E,OAAO;EACL;EACA,QAAQ;GAAE,MAAM;GAAO,IAAI,KAAK,IAAI,KAAK,QAAQ;GAAG,IAAI,KAAK,IAAI,KAAK,SAAS;GAAG,UAAU,KAAK,YAAY;EAAE;EAC/G,GAAG;CACL;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"textItems.js","names":[],"sources":["../../../src/features/pathEffects/textItems.ts"],"sourcesContent":["/**\n * A text node as a path effect's items: its first line's grapheme clusters,\n * each one's outlines in its own frame, sized as the node draws them.\n */\nimport { poseFrameOf, transformPath, type Affine, type PathInWorldPose } from '@weasel-js/geom';\nimport {\n loadTextOutlines, textClusters, TextOutlinesError, type TextCluster, type TextOutlineSource,\n} from '@weasel-js/text';\nimport { noteFontRead } from 'core/scene/fontReads';\nimport type { EffectItem } from 'core/scene/pathEffects/source';\n\n/**\n * The clusters of `data`'s first line as items, laid in `pose`'s box width.\n * The pose's scale grows them; its rotation and skew are dropped, since the\n * path an effect lays them along decides their turn. `[]` while a face's\n * outlines are loading — the load is started, and the font read is noted so\n * the derivation re-runs once it lands.\n */\nexport function textEffectItems(data: TextOutlineSource, pose: PathInWorldPose): readonly EffectItem[] {\n noteFontRead();\n let clusters: TextCluster[];\n try {\n clusters = textClusters(data, { width: pose.width });\n } catch (err) {\n if (!(err instanceof TextOutlinesError)) throw err;\n loadTextOutlines(data).catch(() => {});\n return [];\n }\n const k = scaleOf(pose);\n const grow: Affine | null = k === 1 ? null : [k, 0, 0, k, 0, 0];\n return clusters.map((c) => ({\n length: c.advance * k,\n middle: c.middle * k,\n parts: c.paths.map((p) => ({\n path: grow === null ? p.path : transformPath(p.path, grow),\n fill: p.fill,\n stroke: p.stroke ?? null,\n })),\n }));\n}\n\nfunction scaleOf(pose: PathInWorldPose): number {\n const m = poseFrameOf(pose);\n return m === null ? 1 : Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2]));\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,SAAgB,gBAAgB,MAAyB,MAA8C;CACrG,aAAa;CACb,IAAI;CACJ,IAAI;EACF,WAAW,aAAa,MAAM,EAAE,OAAO,KAAK,MAAM,CAAC;CACrD,SAAS,KAAK;EACZ,IAAI,EAAE,eAAe,oBAAoB,MAAM;EAC/C,iBAAiB,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC;EACrC,OAAO,CAAC;CACV;CACA,MAAM,IAAI,QAAQ,IAAI;CACtB,MAAM,OAAsB,MAAM,IAAI,OAAO;EAAC;EAAG;EAAG;EAAG;EAAG;EAAG;CAAC;CAC9D,OAAO,SAAS,KAAK,OAAO;EAC1B,QAAQ,EAAE,UAAU;EACpB,QAAQ,EAAE,SAAS;EACnB,OAAO,EAAE,MAAM,KAAK,OAAO;GACzB,MAAM,SAAS,OAAO,EAAE,OAAO,cAAc,EAAE,MAAM,IAAI;GACzD,MAAM,EAAE;GACR,QAAQ,EAAE,UAAU;EACtB,EAAE;CACJ,EAAE;AACJ;AAEA,SAAS,QAAQ,MAA+B;CAC9C,MAAM,IAAI,YAAY,IAAI;CAC1B,OAAO,MAAM,OAAO,IAAI,KAAK,KAAK,KAAK,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC;AACvE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"useEffectSourcesLayer.js","names":[],"sources":["../../../src/features/pathEffects/useEffectSourcesLayer.ts"],"sourcesContent":["/**\n * The layer that shows what a selected path effect was made from: the outlines\n * of the sources it hides, drawn faintly under the selection chrome.\n */\nimport { useMemo } from 'react';\nimport { useLatest } from '@weasel-js/react';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport { PATH_OVERLAY_ROLES } from 'canvas/pathOverlay';\nimport { sceneDepLookup } from 'canvas/derivedPath';\nimport { chromeStateFrom } from 'canvas/drawEnvelope';\nimport type { RenderLayer } from 'core/layers/render';\nimport { sourcesHiddenBy } from './effectSources';\nimport type { NodeId, Scene } from 'core/scene/types';\nimport { readEffectSource } from './readSource';\n\nconst FAINT = 0.4;\n\n/** For each selected path-effect node, the outlines of the sources it holds hidden (`sourcesHiddenBy`) as the effect\n * reads them, in a one-screen-pixel stroke of the `'chrome'` overlay color at 40% opacity. The selection comes off the draw envelope. */\nexport function useEffectSourcesLayer<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n): RenderLayer<unknown> {\n const sceneRef = useLatest(scene);\n return useMemo<RenderLayer<unknown>>(() => ({\n id: 'effect-sources',\n label: 'Hidden effect sources',\n space: 'world',\n draw: (data) => {\n const selected = chromeStateFrom(data)?.selection ?? [];\n const sc = sceneRef.current;\n const stroke = PATH_OVERLAY_ROLES['chrome']!.stroke!;\n const depOf = sceneDepLookup(sc);\n const lines: DrawCommand[] = [];\n for (const id of selected) {\n for (const source of sourcesHiddenBy(sc, id as NodeId)) {\n if (!sc.isHidden(source)) continue;\n const dep = depOf(source);\n const path = dep === undefined ? null : readEffectSource(dep as never)?.world ?? null;\n if (path !== null) lines.push({ kind: 'path', path, stroke: { paint: stroke, width: { px: 1 } } });\n }\n }\n return lines.length === 0 ? [] : [{ kind: 'group', alpha: FAINT, children: lines }];\n },\n }), [sceneRef]);\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,IAAM,QAAQ;;;AAId,SAAgB,sBACd,OACsB;CACtB,MAAM,WAAW,UAAU,KAAK;CAChC,OAAO,eAAqC;EAC1C,IAAI;EACJ,OAAO;EACP,OAAO;EACP,OAAO,SAAS;GACd,MAAM,WAAW,gBAAgB,IAAI,CAAC,EAAE,aAAa,CAAC;GACtD,MAAM,KAAK,SAAS;GACpB,MAAM,SAAS,mBAAmB,SAAS,CAAE;GAC7C,MAAM,QAAQ,eAAe,EAAE;GAC/B,MAAM,QAAuB,CAAC;GAC9B,KAAK,MAAM,MAAM,UACf,KAAK,MAAM,UAAU,gBAAgB,IAAI,EAAY,GAAG;IACtD,IAAI,CAAC,GAAG,SAAS,MAAM,GAAG;IAC1B,MAAM,MAAM,MAAM,MAAM;IACxB,MAAM,OAAO,QAAQ,SAAY,OAAO,iBAAiB,GAAY,CAAC,EAAE,SAAS;IACjF,IAAI,SAAS,MAAM,MAAM,KAAK;KAAE,MAAM;KAAQ;KAAM,QAAQ;MAAE,OAAO;MAAQ,OAAO,EAAE,IAAI,EAAE;KAAE;IAAE,CAAC;GACnG;GAEF,OAAO,MAAM,WAAW,IAAI,CAAC,IAAI,CAAC;IAAE,MAAM;IAAS,OAAO;IAAO,UAAU;GAAM,CAAC;EACpF;CACF,IAAI,CAAC,QAAQ,CAAC;AAChB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"anchorData.js","names":[],"sources":["../../../src/features/paths/anchorData.ts"],"sourcesContent":["/**\n * What a node keeps per anchor or per point of its path beside the geometry,\n * carried through an anchor edit: a skin's weights, the fill's and the\n * stroke's vertex colors, and the stroke's widths. The edit says where each\n * new point came from (`editAnchorSetTracked`); everything here follows it.\n */\nimport {\n anchorOriginsOf, remapAnchorValues, type PointOrigin, type PolygonPath,\n} from '@weasel-js/geom';\nimport { vertexHalfWidths, vertexWidthFromSides, type VertexWidth } from '@weasel-js/paint';\nimport type { Skin } from '@weasel-js/animator';\nimport { remapSkin } from '../../animation/rig/skinBinding';\n\n/** Channels per anchor in a `vertexColors` array. */\nconst COLOR_STRIDE = 4;\n\ninterface AnchorDataCarrier {\n skin?: Skin;\n vertexColors?: number[];\n stroke?: { vertexColors?: number[]; vertexWidths?: VertexWidth[]; width?: unknown } | null;\n}\n\ntype Edit = { before: PolygonPath; after: PolygonPath; origins: readonly (PointOrigin | null)[] };\n\nconst swapped = (w: VertexWidth): VertexWidth => (typeof w === 'number' ? w : { left: w.right, right: w.left });\n\n/**\n * A stroke's `vertexWidths` through an edit. A kept anchor keeps its entry,\n * its sides traded where its subpath now runs the other way; a made one\n * blends the sides of the two it came between, an unset side reading the\n * stroke's own `width`; a deleted one's entry goes. Unset stays `NaN`.\n */\nfunction carryWidths(widths: readonly VertexWidth[], width: unknown, { before, after, origins }: Edit): VertexWidth[] {\n const fallback = typeof width === 'number' ? width : Number.NaN;\n return anchorOriginsOf(before, after, origins).map((o) => {\n if (!o) return Number.NaN;\n let entry: VertexWidth = widths[o.a] ?? Number.NaN;\n if (o.t !== 0 && o.b !== o.a) {\n const a = vertexHalfWidths(widths[o.a], fallback);\n const b = vertexHalfWidths(widths[o.b], fallback);\n entry = vertexWidthFromSides(a.left + (b.left - a.left) * o.t, a.right + (b.right - a.right) * o.t);\n }\n return o.reversed ? swapped(entry) : entry;\n });\n}\n\n/**\n * The per-anchor fields of `data`, renumbered for the edit from `before` to\n * `after`, to lay over it; empty when it holds none. Only what `data`\n * already has is written.\n */\nexport function anchorDataPatch(\n data: unknown,\n before: PolygonPath,\n after: PolygonPath,\n origins: readonly (PointOrigin | null)[],\n): Record<string, unknown> {\n if (!data || typeof data !== 'object') return {};\n const d = data as AnchorDataCarrier;\n const edit = { before, after, origins };\n const colors = (values: number[]) => remapAnchorValues(values, COLOR_STRIDE, before, after, origins);\n const patch: AnchorDataCarrier = {};\n if (d.skin) patch.skin = remapSkin(d.skin, origins);\n if (d.vertexColors) patch.vertexColors = colors(d.vertexColors);\n const stroke = d.stroke;\n if (stroke?.vertexColors || stroke?.vertexWidths) {\n patch.stroke = {\n ...stroke,\n ...(stroke.vertexColors ? { vertexColors: colors(stroke.vertexColors) } : {}),\n ...(stroke.vertexWidths ? { vertexWidths: carryWidths(stroke.vertexWidths, stroke.width, edit) } : {}),\n };\n }\n return patch as Record<string, unknown>;\n}\n"],"mappings":";;;;;;;;;;;;AAcA,IAAM,eAAe;AAUrB,IAAM,WAAW,MAAiC,OAAO,MAAM,WAAW,IAAI;CAAE,MAAM,EAAE;CAAO,OAAO,EAAE;AAAK;;;;;;;AAQ7G,SAAS,YAAY,QAAgC,OAAgB,EAAE,QAAQ,OAAO,WAAgC;CACpH,MAAM,WAAW,OAAO,UAAU,WAAW,QAAQ;CACrD,OAAO,gBAAgB,QAAQ,OAAO,OAAO,CAAC,CAAC,KAAK,MAAM;EACxD,IAAI,CAAC,GAAG,OAAO;EACf,IAAI,QAAqB,OAAO,EAAE,MAAM;EACxC,IAAI,EAAE,MAAM,KAAK,EAAE,MAAM,EAAE,GAAG;GAC5B,MAAM,IAAI,iBAAiB,OAAO,EAAE,IAAI,QAAQ;GAChD,MAAM,IAAI,iBAAiB,OAAO,EAAE,IAAI,QAAQ;GAChD,QAAQ,qBAAqB,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;EACpG;EACA,OAAO,EAAE,WAAW,QAAQ,KAAK,IAAI;CACvC,CAAC;AACH;;;;;;AAOA,SAAgB,gBACd,MACA,QACA,OACA,SACyB;CACzB,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO,CAAC;CAC/C,MAAM,IAAI;CACV,MAAM,OAAO;EAAE;EAAQ;EAAO;CAAQ;CACtC,MAAM,UAAU,WAAqB,kBAAkB,QAAQ,cAAc,QAAQ,OAAO,OAAO;CACnG,MAAM,QAA2B,CAAC;CAClC,IAAI,EAAE,MAAM,MAAM,OAAO,UAAU,EAAE,MAAM,OAAO;CAClD,IAAI,EAAE,cAAc,MAAM,eAAe,OAAO,EAAE,YAAY;CAC9D,MAAM,SAAS,EAAE;CACjB,IAAI,QAAQ,gBAAgB,QAAQ,cAClC,MAAM,SAAS;EACb,GAAG;EACH,GAAI,OAAO,eAAe,EAAE,cAAc,OAAO,OAAO,YAAY,EAAE,IAAI,CAAC;EAC3E,GAAI,OAAO,eAAe,EAAE,cAAc,YAAY,OAAO,cAAc,OAAO,OAAO,IAAI,EAAE,IAAI,CAAC;CACtG;CAEF,OAAO;AACT"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"anchorMarkers.js","names":[],"sources":["../../../src/features/paths/anchorMarkers.ts"],"sourcesContent":["/**\n * The draw commands path edit paints for the open path, in screen space: its\n * outline, then each anchor's handles and marker.\n */\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport {\n circlePath, linePath, pathToAnchors, resolveCorners, squarePath, type PenAnchor, type PolygonPath, type View,\n} from '@weasel-js/geom';\nimport type { AnchorMarkerShape } from './pathRepresentation';\n\n/** Appearance of the outline and of the anchor and handle markers. */\nexport interface PathEditingOverlayStyle {\n anchorSizePx?: number;\n handleDotRadiusPx?: number;\n anchorFill?: string;\n anchorStroke?: string;\n handleStroke?: string;\n /** Fill of the control-point circle. Defaults to the handleStroke color\n * at 50% alpha. */\n handleDotFill?: string;\n /** Fill of a selected anchor square. Defaults to the anchor stroke color. */\n anchorFillSelected?: string;\n /** The open path's outline, drawn over its own fill and stroke so the path\n * shows however its paint and its markers cover it. */\n outlineStroke?: string;\n outlineWidthPx?: number;\n /** Radius of the dot an unselected anchor shrinks to where anchors sit\n * closer together on screen than a marker is wide. */\n denseDotRadiusPx?: number;\n /** Stroke of the marquee rubber band. */\n marqueeStroke?: string;\n /** Fill of the marquee rubber band. */\n marqueeFill?: string;\n}\n\n/** The path-edit overlay's look where a {@link PathEditingOverlayStyle} leaves a field out. */\nexport const DEFAULT_OVERLAY_STYLE: Required<PathEditingOverlayStyle> = {\n anchorSizePx: 8,\n handleDotRadiusPx: 5,\n anchorFill: '#ffffff',\n anchorStroke: '#3478f6',\n handleStroke: '#7da7e8',\n // 50%-opacity handle-stroke color. #7da7e8 → rgba(125, 167, 232, 0.5).\n handleDotFill: 'rgba(125, 167, 232, 0.5)',\n anchorFillSelected: '#3478f6',\n outlineStroke: '#3478f6',\n outlineWidthPx: 1,\n denseDotRadiusPx: 1.5,\n marqueeStroke: '#3478f6',\n marqueeFill: 'rgba(52, 120, 246, 0.08)',\n};\n\ntype Point = { x: number; y: number };\n\nexport function worldToScreen(p: Point, view: View): Point {\n return { x: (p.x - view.x) * view.scale.x, y: (p.y - view.y) * view.scale.y };\n}\n\n/** `path`, its corners expanded, with every coordinate carried to screen\n * through `view`: a radius left on it would stay in world units. */\nfunction onScreen(path: PolygonPath, view: View): PolygonPath {\n path = resolveCorners(path);\n const coords = new Float32Array(path.coords.length);\n for (let i = 0; i < coords.length; i += 2) {\n coords[i] = (path.coords[i] - view.x) * view.scale.x;\n coords[i + 1] = (path.coords[i + 1] - view.y) * view.scale.y;\n }\n return { ...path, coords };\n}\n\n/** Whether `sub[i]` sits closer to a neighbor along its subpath than `px` on screen. */\nfunction crowded(sub: readonly Point[], closed: boolean, i: number, px: number): boolean {\n const n = sub.length;\n const near = (j: number): boolean => {\n if (!closed && (j < 0 || j >= n)) return false;\n const q = sub[(j + n) % n];\n return q !== sub[i] && Math.hypot(q.x - sub[i].x, q.y - sub[i].y) < px;\n };\n return near(i - 1) || near(i + 1);\n}\n\n/** The on-screen neighbors of `sub[i]` along its subpath, null past an open end. */\nfunction neighbors(sub: readonly Point[], closed: boolean, i: number): [Point | null, Point | null] {\n const n = sub.length;\n const prev = i > 0 || closed ? sub[(i - 1 + n) % n] : null;\n const next = i < n - 1 || closed ? sub[(i + 1) % n] : null;\n return [prev, next];\n}\n\nfunction handleCommands(a: Point, handle: Point, style: Required<PathEditingOverlayStyle>): DrawCommand[] {\n return [\n { kind: 'path', path: linePath(a, handle), stroke: { paint: { kind: 'solid', color: style.handleStroke }, width: 1 } },\n {\n kind: 'path',\n path: circlePath(handle.x, handle.y, style.handleDotRadiusPx),\n fill: { paint: { kind: 'solid', color: style.handleDotFill } },\n },\n ];\n}\n\n/**\n * The open path's outline, then its anchors in walk order — each anchor's\n * handles under its marker. A selected anchor's marker is filled, an\n * unselected one's hollow, or a dot where its neighbors crowd it. The marker\n * is a square, or the shape `markers` gives the anchor's flat index. Flat\n * indices count the way `enumerateAnchors` does, as the selection does.\n */\nexport function anchorMarkerCommands(\n path: PolygonPath,\n view: View,\n selected: ReadonlySet<number>,\n style: Required<PathEditingOverlayStyle>,\n markers: readonly (AnchorMarkerShape | null)[] | null = null,\n): DrawCommand[] {\n const out: DrawCommand[] = [{\n kind: 'path',\n path: onScreen(path, view),\n stroke: { paint: { kind: 'solid', color: style.outlineStroke }, width: style.outlineWidthPx },\n }];\n const { anchors, closed } = pathToAnchors(path);\n let flat = 0;\n anchors.forEach((sub: PenAnchor[], s) => {\n const screen = sub.map((a) => worldToScreen(a, view));\n const isClosed = closed[s];\n sub.forEach((a, i) => {\n const shape = markers?.[flat] ?? null;\n const isSelected = selected.has(flat++);\n const at = screen[i];\n if (a.inHandle) out.push(...handleCommands(at, worldToScreen(a.inHandle, view), style));\n if (a.outHandle) out.push(...handleCommands(at, worldToScreen(a.outHandle, view), style));\n if (!isSelected && crowded(screen, closed[s], i, style.anchorSizePx)) {\n out.push({\n kind: 'path',\n path: circlePath(at.x, at.y, style.denseDotRadiusPx),\n fill: { paint: { kind: 'solid', color: style.anchorStroke } },\n });\n return;\n }\n out.push({\n kind: 'path',\n path: shape ? shape(at, style.anchorSizePx, ...neighbors(screen, isClosed, i)) : squarePath(at.x, at.y, style.anchorSizePx),\n fill: { paint: { kind: 'solid', color: isSelected ? style.anchorFillSelected : style.anchorFill } },\n stroke: { paint: { kind: 'solid', color: style.anchorStroke }, width: 1 },\n });\n });\n });\n return out;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"editablePath.js","names":[],"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 * A rect `data.path` is edited as its four corners and is stored as a polygon\n * from the first edit on; reading it changes nothing. A leaf drawn as its rect\n * pose alone reads as a rect `data.path` the size of its pose, and gains one\n * on the first edit.\n */\nimport { asPolygonPath, pathInWorld, rectPath, worldEditToStorage, type Path, type PolygonPath } from '@weasel-js/geom';\nimport type { GesturePreviewSource } from 'canvas/gestureBounds';\nimport { findNodeShape } from 'canvas/NodeShape';\nimport { RECT_FALLBACK_PAINTER } from 'canvas/nodeShapes/rectFallbackPainter';\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';\nimport type { PathEditExtension } from './pathEditExtension';\nimport { pathFormOf, representationOf, withoutPathForm, type AnchorMarkerShape } from './pathRepresentation';\n\n/** The fields of a node the editable path is read from. A node's `kind`\n * says whether a rect pose with no path is a leaf drawn as that rect. */\nexport interface EditableShape {\n pose: unknown;\n data: unknown;\n kind?: string;\n}\n\n/** Whether `node` is a leaf whose only geometry is its rect pose, drawn by\n * the kit's rect fallback. */\nfunction drawsPoseAlone(node: EditableShape): boolean {\n if (node.kind !== 'mark' || (node.data as { path?: unknown } | null)?.path != null) return false;\n return findNodeShape(node as never) === RECT_FALLBACK_PAINTER;\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 or rect on `data.path`.\n * A rect comes back as the polygon it draws. */\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 if (isRectPose(node.pose) && drawsPoseAlone(node)) {\n const { width, height } = node.pose;\n return { kind: 'data', pose: node.pose, data: { ...(node.data as object), path: asPolygonPath(rectPath(0, 0, width, height)) } };\n }\n const data = node.data as { path?: Path } | null;\n const kind = (data?.path as { kind?: string } | undefined)?.kind;\n if (!data?.path || (kind !== 'polygon' && kind !== 'rect') || !isRectPose(node.pose)) return null;\n const path = asPolygonPath(data.path);\n return {\n kind: 'data',\n pose: node.pose,\n data: (path === data.path ? data : { ...data, path }) as { path: PolygonPath },\n };\n}\n\n/** Whether `node` has a path that path edit can show anchors for. */\nexport function hasEditablePath(node: EditableShape | undefined | null): boolean {\n return classifyPathStorage(node) !== 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 * What path edit edits on a node, in world coords: the edit path of the form\n * the node's path was drawn from, where one of `extensions` reads it\n * (`PathRepresentation`), and otherwise its editable polygon. Null when the\n * node has no editable polygon.\n */\nexport function pathEditPathOf(\n node: EditableShape | undefined | null,\n extensions: readonly PathEditExtension[] = [],\n): PolygonPath | null {\n const path = resolveEditablePathOf(node);\n const held = path ? representationOf(extensions, node!.data) : null;\n return (held && held.representation.editPath(path!, held.form)) ?? path;\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. On a node whose path was drawn from a form one of\n * `extensions` reads, `worldPath` is an edit of what {@link pathEditPathOf}\n * gave, and the representation turns it into the drawn path; a form none\n * reads, or that no longer describes the path, is dropped. Null when `node`\n * has no editable polygon.\n */\nexport function storeEditablePath(\n node: EditableShape | undefined | null,\n worldPath: PolygonPath,\n extensions: readonly PathEditExtension[] = [],\n): EditableShape | null {\n const storage = classifyPathStorage(node);\n if (!storage) return null;\n let path = worldPath;\n let data = node!.data;\n if (pathFormOf(data)) {\n const held = representationOf(extensions, data);\n const solved = held ? held.representation.store(resolveEditablePathOf(node)!, held.form, worldPath) : null;\n path = solved?.path ?? worldPath;\n data = solved ? { ...(data as object), representation: solved.form } : withoutPathForm(data);\n }\n if (storage.kind === 'pose') return { pose: path, data };\n const stored = worldEditToStorage(storage.pose, path);\n return { pose: stored.pose, data: { ...(data as object), path: stored.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 const kind = node.kind;\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, kind };\n}\n\n/**\n * What path edit shows of node `id` right now ({@link pathEditPathOf}): 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 extensions: readonly PathEditExtension[] = [],\n): PolygonPath | null {\n if (!node) return null;\n return pathFromPlane(pathEditPathOf(previewedShape(id, node, previews), extensions), plane);\n}\n\n/** The marker of each anchor {@link liveEditablePath} shows for node `id`, as\n * the representation reading its form draws them; null for a node it reads\n * none of. */\nexport function liveAnchorMarkers(\n id: string,\n node: EditableShape | undefined | null,\n previews: readonly GesturePreviewSource[],\n extensions: readonly PathEditExtension[],\n): readonly (AnchorMarkerShape | null)[] | null {\n if (!node) return null;\n const held = representationOf(extensions, previewedShape(id, node, previews).data);\n return held?.representation.anchorMarkers?.(held.form) ?? null;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"originProjection.js","names":[],"sources":["../../../src/features/paths/originProjection.ts"],"sourcesContent":["import { boundsOfPath, translatePath, type Path } from '@weasel-js/geom';\nimport type { OriginProjection } from 'interactions/gestures/shared/originProjection';\n\n/**\n * `OriginProjection` for `Path` poses. The \"origin\" is the top-left of the\n * path's AABB; `translate` shifts every coord by the requested delta. It is\n * where a snap session puts a Path's `origin` probe, so a grid source snaps a\n * Path's bounds top-left.\n */\nexport const pathOriginProjection: OriginProjection<Path> = {\n getOrigin: (path) => {\n const b = boundsOfPath(path);\n return { x: b.x, y: b.y };\n },\n translate: translatePath,\n};\n"],"mappings":";;;;;;;;;AASA,IAAa,uBAA+C;CAC1D,YAAY,SAAS;EACnB,MAAM,IAAI,aAAa,IAAI;EAC3B,OAAO;GAAE,GAAG,EAAE;GAAG,GAAG,EAAE;EAAE;CAC1B;CACA,WAAW;AACb"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"pathEditExtension.js","names":[],"sources":["../../../src/features/paths/pathEditExtension.ts"],"sourcesContent":["/**\n * What a feature adds to path edit without path edit knowing it: marks drawn\n * on the open path, parts of it to press, marks a click selects and Delete\n * removes, a share in shaping an anchor or handle drag, what an edit must keep\n * true when it commits, and how a path drawn from another form is edited. A\n * preset carries its extensions as `FeaturePreset.pathEdit`; core consults\n * them in preset order.\n */\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { AnchorSet, PointOrigin, PolygonPath, View } from '@weasel-js/geom';\nimport type { OngoingOverlay } from '@weasel-js/routing';\nimport type { HandleLocks } from 'interactions/actions/edit-anchors/params';\nimport type { SnapDep } from 'interactions/actions/snapDep';\nimport type { PathRepresentation } from './pathRepresentation';\n\n/** A mark an extension drew that is selected in place of anchors: Delete\n * acts on it, and selecting anchors clears it. */\nexport interface PathEditMark {\n /** The extension that drew it. */\n owner: string;\n /** Which of its marks, in the extension's own numbering. */\n index: number;\n}\n\n/** The open path as an extension's marks and parts read it. */\nexport interface PathEditTarget {\n /** The node being edited. */\n id: string;\n /** Its committed pose and data. */\n node: { pose: unknown; data: unknown };\n /** The path as drawn now, in-flight edits included, in world space. */\n path: PolygonPath;\n /** `path`'s anchors. */\n anchors: AnchorSet;\n}\n\n/** What {@link PathEditExtension.marks} draws with. */\nexport interface PathEditMarksCtx extends PathEditTarget {\n view: View;\n /** World space to the screen space the marks are drawn in. */\n toScreen(p: { x: number; y: number }): { x: number; y: number };\n /** Chrome-caps' answer for a chrome id; marks gated by one ask it. */\n isVisible(chromeId: string): boolean;\n /** The selected mark's index when this extension owns it, else null. */\n selected: number | null;\n}\n\n/** A grabbable point an extension adds to the open path. A press on it\n * routes as an affordance hit of kind `hitKind`. */\nexport interface PathEditPart {\n x: number;\n y: number;\n hitKind: string;\n /** Chrome-caps id it is hit-tested under, beside `path-edit.anchors`. */\n chromeId?: string;\n /** Hit radius in screen px; path edit's anchor radius when left out. */\n hitRadiusPx?: number;\n /** Pushes the part `px` screen pixels from (x, y) along the world-space\n * `normal`, so it keeps its distance at any zoom. */\n standoff?: { px: number; normal: { x: number; y: number } };\n}\n\ntype Point = { x: number; y: number };\n\n/** An anchor or handle drag on the open path, as it starts. */\nexport interface PathEditDragStart extends Omit<PathEditTarget, 'node'> {\n /** The node's committed pose and data; null when it cannot be read. */\n node: PathEditTarget['node'] | null;\n /** What the press grabbed: an anchor, or one of its handles. */\n part: 'anchor' | 'controlIn' | 'controlOut';\n /** The pressed anchor, as a flat index. */\n anchorIndex: number;\n /** The anchors an anchor drag moves, as flat indices; empty for a handle\n * drag, or when Shift took the pressed anchor out of the selection. */\n dragging: readonly number[];\n /** The view's world-to-screen scale at the press, to measure in screen\n * pixels by; absent without a view. */\n scale?: { x: number; y: number };\n /** The canvas's `snap` dep, to begin a session on; absent without one. */\n snap?: SnapDep;\n}\n\n/** A handle drag's move, as {@link PathEditDrag.handle} reads it. */\nexport interface PathEditHandleMove {\n /** The handle locks in force this move, modifiers included. */\n locks: HandleLocks;\n}\n\n/** One extension's share of a drag in flight. Every member is optional. */\nexport interface PathEditDrag {\n /**\n * For an anchor drag, anchors that follow it and the share of its delta\n * each moves by, keyed by flat index. Read every move. When any extension\n * gives one, the drag moves its dragged anchors in full and every anchor\n * named by the largest share given, and moves no symmetric images.\n */\n weights?(): ReadonlyMap<number, number>;\n /** For a handle drag, where the handle goes, given `to`: where the pointer\n * and the locks in force put it. Extensions answer in turn, each from the\n * last one's answer. */\n handle?(to: Point, move: PathEditHandleMove): Point;\n /** A wheel turned while the drag is in flight. Return true to claim it;\n * the drag then redoes its last move. */\n wheel?(wheel: { deltaX: number; deltaY: number; deltaZ: number }): boolean;\n /** Overlays to draw while the drag is in flight, for `current`, the path\n * as the drag has made it. */\n overlays?(current: AnchorSet): readonly OngoingOverlay[];\n /** The drag is over, committed or canceled. */\n end?(): void;\n}\n\n/** Another node an edit changes along with the edited one. */\nexport interface PathEditOther {\n id: string;\n /** Its new path, in world space, stored where the node keeps its own. */\n path?: PolygonPath;\n /** Data fields to set on it. */\n data?: Record<string, unknown>;\n /** Delete it: what it kept true no longer has anything to hold. */\n remove?: boolean;\n}\n\n/** What an edit commits once an extension has finished it. */\nexport interface FinishedPathEdit {\n path: PolygonPath;\n /** Data fields to set beside the path. */\n data?: Record<string, unknown>;\n /** Other nodes the edit changes, committed in the same history entry and previewed with it. */\n others?: readonly PathEditOther[];\n}\n\n/** The scene as {@link PathEditExtension.finishEdit} reads it, for an edit that reaches past one node. */\nexport interface PathEditFinishCtx {\n /** The edited node's id. */\n id: string;\n /** A node's committed pose, data and dependencies; null when there is none. */\n node(id: string): { pose: unknown; data: unknown; dependsOn?: readonly string[] | 'children' } | null;\n /** A node's committed editable path, in world space; null when it has none. */\n path(id: string): PolygonPath | null;\n /** The nodes that list `id` in their `dependsOn`, directly or through others. */\n dependents(id: string): readonly string[];\n}\n\n/**\n * The scene as {@link PathEditExtension.finishTransform} reads it: `node` and\n * `path` answer each node as the transform leaves it, `before` as it was.\n */\nexport interface PathTransformFinishCtx extends Omit<PathEditFinishCtx, 'id'> {\n /** The nodes the transform moves, resizes or turns. */\n ids: readonly string[];\n /** A node's committed editable path before the transform, in world space; null when it has none. */\n before(id: string): PolygonPath | null;\n}\n\n/** One feature's share of path edit. Every member is optional. */\nexport interface PathEditExtension {\n /** Names it as a {@link PathEditMark}'s owner. Unique per canvas. */\n readonly id: string;\n /** Draw commands for the open path, in screen space, under its anchor\n * markers. Called every frame the overlay paints. */\n marks?(ctx: PathEditMarksCtx): DrawCommand[];\n /** Grabbable points on the open path, pressed ahead of its segments and\n * behind its anchors. */\n parts?(target: PathEditTarget): readonly PathEditPart[];\n /** The index of this extension's mark a click at `screen` (in the\n * overlay's screen space) lands on, or -1. Asked when a click misses\n * every anchor. */\n markAt?(target: PathEditTarget, toScreen: PathEditMarksCtx['toScreen'], screen: { x: number; y: number }): number;\n /** Remove mark `index` of the node `target` names, committing through\n * `store`. Delete in path edit calls it on the selected mark. */\n deleteMark?(target: PathEditTarget, index: number, store: PathEditStore): void;\n /** Its share of an anchor or handle drag starting on the open path, or\n * null to sit it out. Asked once, at the press. */\n drag?(start: PathEditDragStart): PathEditDrag | null;\n /**\n * `worldPath`, an edit of `node`'s committed path, finished so whatever\n * this extension keeps true holds — the path, and any data fields that\n * change with it, and any other nodes it has to move to keep it. Null when\n * it cannot hold, and the edit should then not happen. Runs on every commit\n * of an edit and every frame of one in flight. `ctx` reads the rest of the\n * scene; it is absent where an edit has no scene behind it.\n */\n finishEdit?(\n node: { pose: unknown; data: unknown },\n worldPath: PolygonPath,\n origins?: readonly (PointOrigin | null)[],\n ctx?: PathEditFinishCtx,\n ): FinishedPathEdit | null;\n /**\n * The other nodes a whole-node transform — a move, resize, rotate, flip or\n * nudge — changes along with the nodes it transforms, so whatever this\n * extension keeps true across nodes holds; committed in the transform's\n * history entry. Null when the transform should not happen. An entry\n * naming a node the transform itself moves is ignored.\n */\n finishTransform?(ctx: PathTransformFinishCtx): readonly PathEditOther[] | null;\n /** How path edit reads and writes the nodes whose `data.representation`\n * is of its kind. */\n representation?: PathRepresentation;\n}\n\n/** Commits a node's new path and data as one history entry, with no\n * extension finishing it. */\nexport type PathEditStore = (id: string, worldPath: PolygonPath, label: string, data?: Record<string, unknown>) => void;\n\n/**\n * Data fields that describe how a node's geometry was made, or name its\n * anchors, and so mean nothing once the geometry is replaced some other way.\n * A writer replacing a node's path drops them: a held warp, path\n * constraints, a skin, the form a path was drawn from.\n */\nexport const GEOMETRY_BOUND_DATA: readonly string[] = ['skin', 'constraints', 'warp', 'representation'];\n\n/** `data` without the fields in {@link GEOMETRY_BOUND_DATA}, other than `keep`. */\nexport function withoutGeometryBoundData<T>(data: T, keep: readonly string[] = []): T {\n if (data === null || typeof data !== 'object') return data;\n const drop = GEOMETRY_BOUND_DATA.filter((k) => k in (data as object) && !keep.includes(k));\n if (drop.length === 0) return data;\n const out = { ...(data as Record<string, unknown>) };\n for (const k of drop) delete out[k];\n return out as T;\n}\n"],"mappings":";;;;;;;AAmNA,IAAa,sBAAyC;CAAC;CAAQ;CAAe;CAAQ;AAAgB;;AAGtG,SAAgB,yBAA4B,MAAS,OAA0B,CAAC,GAAM;CACpF,IAAI,SAAS,QAAQ,OAAO,SAAS,UAAU,OAAO;CACtD,MAAM,OAAO,oBAAoB,QAAQ,MAAM,KAAM,QAAmB,CAAC,KAAK,SAAS,CAAC,CAAC;CACzF,IAAI,KAAK,WAAW,GAAG,OAAO;CAC9B,MAAM,MAAM,EAAE,GAAI,KAAiC;CACnD,KAAK,MAAM,KAAK,MAAM,OAAO,IAAI;CACjC,OAAO;AACT"}