@weasel-js/core 1.13.0 → 1.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (741) hide show
  1. package/CHANGELOG.md +326 -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
@@ -0,0 +1,23 @@
1
+ import { pathExclude, pathIntersect, pathSubtract, pathUnion } from "@weasel-js/geom/booleans";
2
+
3
+ //#region src/core/geometry/booleanOfPaths.ts
4
+ /**
5
+ * `op` over `paths`, given back to front, as one path. Subtract is
6
+ * Illustrator's "Minus Front": the backmost less everything in front of it.
7
+ * Curves are kept where an operand has them.
8
+ */
9
+ function booleanOfPaths(op, paths) {
10
+ switch (op) {
11
+ case "union": return pathUnion(...paths);
12
+ case "intersect": return pathIntersect(...paths);
13
+ case "exclude": return pathExclude(...paths);
14
+ case "subtract": {
15
+ const front = paths.length === 2 ? paths[1] : pathUnion(...paths.slice(1));
16
+ return pathSubtract(paths[0], front);
17
+ }
18
+ }
19
+ }
20
+
21
+ //#endregion
22
+ export { booleanOfPaths };
23
+ //# sourceMappingURL=booleanOfPaths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"booleanOfPaths.js","names":[],"sources":["../../../src/core/geometry/booleanOfPaths.ts"],"sourcesContent":["import { pathUnion, pathIntersect, pathSubtract, pathExclude } from '@weasel-js/geom/booleans';\nimport type { Path, PolygonPath } from '@weasel-js/geom';\n\n/** The booleans that make one shape of their operands: the Pathfinder's four\n * shape modes, and what a boolean group can be set to. */\nexport type BooleanShapeOp = 'union' | 'intersect' | 'subtract' | 'exclude';\n\n/**\n * `op` over `paths`, given back to front, as one path. Subtract is\n * Illustrator's \"Minus Front\": the backmost less everything in front of it.\n * Curves are kept where an operand has them.\n */\nexport function booleanOfPaths(op: BooleanShapeOp, paths: readonly Path[]): PolygonPath {\n switch (op) {\n case 'union': return pathUnion(...paths);\n case 'intersect': return pathIntersect(...paths);\n case 'exclude': return pathExclude(...paths);\n case 'subtract': {\n const front = paths.length === 2 ? paths[1]! : pathUnion(...paths.slice(1));\n return pathSubtract(paths[0]!, front);\n }\n }\n}\n"],"mappings":";;;;;;;;AAYA,SAAgB,eAAe,IAAoB,OAAqC;CACtF,QAAQ,IAAR;EACE,KAAK,SAAS,OAAO,UAAU,GAAG,KAAK;EACvC,KAAK,aAAa,OAAO,cAAc,GAAG,KAAK;EAC/C,KAAK,WAAW,OAAO,YAAY,GAAG,KAAK;EAC3C,KAAK,YAAY;GACf,MAAM,QAAQ,MAAM,WAAW,IAAI,MAAM,KAAM,UAAU,GAAG,MAAM,MAAM,CAAC,CAAC;GAC1E,OAAO,aAAa,MAAM,IAAK,KAAK;EACtC;CACF;AACF"}
@@ -1,4 +1,4 @@
1
- import { boundsOfPath, boxToBox, lerpCorners, transformPath, translatePath } from "@weasel-js/geom";
1
+ import { boundsOfPath, boxToBox, coordsFrom, lerpCorners, transformPath, translatePath, worldCoords } from "@weasel-js/geom";
2
2
 
3
3
  //#region src/core/geometry/pathPoseDescriptor.ts
4
4
  /**
@@ -31,8 +31,10 @@ var pathPoseDescriptor = {
31
31
  height: a.height + (b.height - a.height) * t
32
32
  };
33
33
  if (a.kind === "polygon" && b.kind === "polygon" && a.coords.length === b.coords.length) {
34
- const next = new Float32Array(a.coords.length);
35
- for (let i = 0; i < a.coords.length; i++) next[i] = a.coords[i] + (b.coords[i] - a.coords[i]) * t;
34
+ const aw = worldCoords(a), bw = worldCoords(b);
35
+ const mid = new Float64Array(aw.length);
36
+ for (let i = 0; i < aw.length; i++) mid[i] = aw[i] + (bw[i] - aw[i]) * t;
37
+ const next = coordsFrom(a.origin, mid);
36
38
  const { corners: _a, ...bare } = a;
37
39
  const corners = lerpCorners(a.corners, b.corners, t);
38
40
  return corners ? {
@@ -85,23 +87,24 @@ var pathPoseDescriptor = {
85
87
  };
86
88
  return {
87
89
  count: like.coords.length,
88
- to: (p) => p.kind === "polygon" ? Array.from(p.coords) : [],
90
+ to: (p) => p.kind === "polygon" ? Array.from(worldCoords(p)) : [],
89
91
  from: (a) => ({
90
92
  ...like,
91
- coords: Float32Array.from(a)
93
+ coords: coordsFrom(like.origin, a)
92
94
  }),
93
95
  add: (p, d) => {
94
96
  if (p.kind !== "polygon") return p;
95
- const coords = new Float32Array(p.coords.length);
96
- for (let i = 0; i < coords.length; i++) coords[i] = p.coords[i] + d[i];
97
+ const moved = Float64Array.from(worldCoords(p));
98
+ for (let i = 0; i < moved.length; i++) moved[i] += d[i];
97
99
  return {
98
100
  ...like,
99
- coords
101
+ coords: coordsFrom(like.origin, moved)
100
102
  };
101
103
  },
102
104
  write: (p, out, at) => {
103
105
  if (p.kind !== "polygon") return;
104
- for (let i = 0; i < p.coords.length; i++) out[at + i] = p.coords[i];
106
+ const world = worldCoords(p);
107
+ for (let i = 0; i < world.length; i++) out[at + i] = world[i];
105
108
  }
106
109
  };
107
110
  },
@@ -1 +1 @@
1
- {"version":3,"file":"pathPoseDescriptor.js","names":[],"sources":["../../../src/core/geometry/pathPoseDescriptor.ts"],"sourcesContent":["import {\n boxToBox,\n boundsOfPath,\n lerpCorners,\n translatePath,\n transformPath,\n type Path,\n} from '@weasel-js/geom';\nimport type { PoseDescriptor } from 'core/geometry/poseDescriptor';\n\n/**\n * `PoseDescriptor` for `Path` poses — wires `useResize` to operate\n * on `Path` directly. `getBounds` defers to the same `boundsOfPath` kernel\n * the rest of the kit uses.\n *\n * `remapBounds` mirrors `scalePathToBounds` but takes `src` explicitly: the\n * resize hook knows the group's origin AABB and uses it for every leaf,\n * instead of each leaf scaling against its own AABB (which would ignore\n * group context).\n */\nexport const pathPoseDescriptor: PoseDescriptor<Path> = {\n getBounds: (path) => boundsOfPath(path),\n remapBounds: (path, src, dst) => transformPath(\n path,\n boxToBox(src.x, src.y, src.width, src.height, dst.x, dst.y, dst.width, dst.height),\n ),\n fromBounds: (b) => ({ kind: 'rect', x: b.x, y: b.y, width: b.width, height: b.height }),\n translate: (path, dx, dy) => translatePath(path, dx, dy),\n lerp: (a, b, t) => {\n if (a.kind === 'rect' && b.kind === 'rect') {\n return {\n kind: 'rect',\n x: a.x + (b.x - a.x) * t,\n y: a.y + (b.y - a.y) * t,\n width: a.width + (b.width - a.width) * t,\n height: a.height + (b.height - a.height) * t,\n };\n }\n if (a.kind === 'polygon' && b.kind === 'polygon' && a.coords.length === b.coords.length) {\n const next = new Float32Array(a.coords.length);\n for (let i = 0; i < a.coords.length; i++) {\n next[i] = a.coords[i] + (b.coords[i] - a.coords[i]) * t;\n }\n const { corners: _a, ...bare } = a;\n const corners = lerpCorners(a.corners, b.corners, t);\n return corners ? { ...bare, coords: next, corners } : { ...bare, coords: next };\n }\n throw new Error('pathPoseDescriptor.lerp: incompatible path shapes');\n },\n // A polygon's axes are its coordinates, so a move needs both ends to have as many points.\n axes: (like) => {\n if (like.kind === 'rect') {\n return {\n count: 4,\n names: ['x', 'y', 'width', 'height'],\n to: (p) => (p.kind === 'rect' ? [p.x, p.y, p.width, p.height] : []),\n from: (a) => ({ kind: 'rect', x: a[0]!, y: a[1]!, width: a[2]!, height: a[3]! }),\n add: (p, d) => (p.kind !== 'rect' ? p : {\n kind: 'rect', x: p.x + d[0]!, y: p.y + d[1]!, width: p.width + d[2]!, height: p.height + d[3]!,\n }),\n write: (p, out, at) => {\n if (p.kind !== 'rect') return;\n out[at] = p.x;\n out[at + 1] = p.y;\n out[at + 2] = p.width;\n out[at + 3] = p.height;\n },\n };\n }\n return {\n count: like.coords.length,\n to: (p) => (p.kind === 'polygon' ? Array.from(p.coords) : []),\n from: (a) => ({ ...like, coords: Float32Array.from(a as ArrayLike<number>) }),\n add: (p, d) => {\n if (p.kind !== 'polygon') return p;\n const coords = new Float32Array(p.coords.length);\n for (let i = 0; i < coords.length; i++) coords[i] = p.coords[i]! + d[i]!;\n return { ...like, coords };\n },\n write: (p, out, at) => {\n if (p.kind !== 'polygon') return;\n for (let i = 0; i < p.coords.length; i++) out[at + i] = p.coords[i]!;\n },\n };\n },\n // Path poses carry no x/y/width/height/rotation fields — the kit's\n // `wrapWithPoseRotation` helper has nothing to bind a rotation to, and\n // `remapBounds` would drop a synthetic rotation field on every commit.\n // Returning false here hides the rotation affordance for Path consumers\n // so the rotate cursor doesn't appear without a working interaction.\n supportsRotation: () => false,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"pathPoseDescriptor.js","names":[],"sources":["../../../src/core/geometry/pathPoseDescriptor.ts"],"sourcesContent":["import {\n boxToBox,\n boundsOfPath,\n coordsFrom,\n lerpCorners,\n translatePath,\n transformPath,\n worldCoords,\n type Path,\n} from '@weasel-js/geom';\nimport type { PoseDescriptor } from 'core/geometry/poseDescriptor';\n\n/**\n * `PoseDescriptor` for `Path` poses — wires `useResize` to operate\n * on `Path` directly. `getBounds` defers to the same `boundsOfPath` kernel\n * the rest of the kit uses.\n *\n * `remapBounds` mirrors `scalePathToBounds` but takes `src` explicitly: the\n * resize hook knows the group's origin AABB and uses it for every leaf,\n * instead of each leaf scaling against its own AABB (which would ignore\n * group context).\n */\nexport const pathPoseDescriptor: PoseDescriptor<Path> = {\n getBounds: (path) => boundsOfPath(path),\n remapBounds: (path, src, dst) => transformPath(\n path,\n boxToBox(src.x, src.y, src.width, src.height, dst.x, dst.y, dst.width, dst.height),\n ),\n fromBounds: (b) => ({ kind: 'rect', x: b.x, y: b.y, width: b.width, height: b.height }),\n translate: (path, dx, dy) => translatePath(path, dx, dy),\n lerp: (a, b, t) => {\n if (a.kind === 'rect' && b.kind === 'rect') {\n return {\n kind: 'rect',\n x: a.x + (b.x - a.x) * t,\n y: a.y + (b.y - a.y) * t,\n width: a.width + (b.width - a.width) * t,\n height: a.height + (b.height - a.height) * t,\n };\n }\n if (a.kind === 'polygon' && b.kind === 'polygon' && a.coords.length === b.coords.length) {\n const aw = worldCoords(a), bw = worldCoords(b);\n const mid = new Float64Array(aw.length);\n for (let i = 0; i < aw.length; i++) mid[i] = aw[i] + (bw[i] - aw[i]) * t;\n const next = coordsFrom(a.origin, mid);\n const { corners: _a, ...bare } = a;\n const corners = lerpCorners(a.corners, b.corners, t);\n return corners ? { ...bare, coords: next, corners } : { ...bare, coords: next };\n }\n throw new Error('pathPoseDescriptor.lerp: incompatible path shapes');\n },\n // A polygon's axes are its coordinates, so a move needs both ends to have as many points.\n axes: (like) => {\n if (like.kind === 'rect') {\n return {\n count: 4,\n names: ['x', 'y', 'width', 'height'],\n to: (p) => (p.kind === 'rect' ? [p.x, p.y, p.width, p.height] : []),\n from: (a) => ({ kind: 'rect', x: a[0]!, y: a[1]!, width: a[2]!, height: a[3]! }),\n add: (p, d) => (p.kind !== 'rect' ? p : {\n kind: 'rect', x: p.x + d[0]!, y: p.y + d[1]!, width: p.width + d[2]!, height: p.height + d[3]!,\n }),\n write: (p, out, at) => {\n if (p.kind !== 'rect') return;\n out[at] = p.x;\n out[at + 1] = p.y;\n out[at + 2] = p.width;\n out[at + 3] = p.height;\n },\n };\n }\n return {\n count: like.coords.length,\n to: (p) => (p.kind === 'polygon' ? Array.from(worldCoords(p)) : []),\n from: (a) => ({ ...like, coords: coordsFrom(like.origin, a as ArrayLike<number>) }),\n add: (p, d) => {\n if (p.kind !== 'polygon') return p;\n const moved = Float64Array.from(worldCoords(p));\n for (let i = 0; i < moved.length; i++) moved[i]! += d[i]!;\n return { ...like, coords: coordsFrom(like.origin, moved) };\n },\n write: (p, out, at) => {\n if (p.kind !== 'polygon') return;\n const world = worldCoords(p);\n for (let i = 0; i < world.length; i++) out[at + i] = world[i]!;\n },\n };\n },\n // Path poses carry no x/y/width/height/rotation fields — the kit's\n // `wrapWithPoseRotation` helper has nothing to bind a rotation to, and\n // `remapBounds` would drop a synthetic rotation field on every commit.\n // Returning false here hides the rotation affordance for Path consumers\n // so the rotate cursor doesn't appear without a working interaction.\n supportsRotation: () => false,\n};\n"],"mappings":";;;;;;;;;;;;;AAsBA,IAAa,qBAA2C;CACtD,YAAY,SAAS,aAAa,IAAI;CACtC,cAAc,MAAM,KAAK,QAAQ,cAC/B,MACA,SAAS,IAAI,GAAG,IAAI,GAAG,IAAI,OAAO,IAAI,QAAQ,IAAI,GAAG,IAAI,GAAG,IAAI,OAAO,IAAI,MAAM,CACnF;CACA,aAAa,OAAO;EAAE,MAAM;EAAQ,GAAG,EAAE;EAAG,GAAG,EAAE;EAAG,OAAO,EAAE;EAAO,QAAQ,EAAE;CAAO;CACrF,YAAY,MAAM,IAAI,OAAO,cAAc,MAAM,IAAI,EAAE;CACvD,OAAO,GAAG,GAAG,MAAM;EACjB,IAAI,EAAE,SAAS,UAAU,EAAE,SAAS,QAClC,OAAO;GACL,MAAM;GACN,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK;GACvB,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK;GACvB,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS;GACvC,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU;EAC7C;EAEF,IAAI,EAAE,SAAS,aAAa,EAAE,SAAS,aAAa,EAAE,OAAO,WAAW,EAAE,OAAO,QAAQ;GACvF,MAAM,KAAK,YAAY,CAAC,GAAG,KAAK,YAAY,CAAC;GAC7C,MAAM,MAAM,IAAI,aAAa,GAAG,MAAM;GACtC,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,QAAQ,KAAK,IAAI,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM;GACvE,MAAM,OAAO,WAAW,EAAE,QAAQ,GAAG;GACrC,MAAM,EAAE,SAAS,IAAI,GAAG,SAAS;GACjC,MAAM,UAAU,YAAY,EAAE,SAAS,EAAE,SAAS,CAAC;GACnD,OAAO,UAAU;IAAE,GAAG;IAAM,QAAQ;IAAM;GAAQ,IAAI;IAAE,GAAG;IAAM,QAAQ;GAAK;EAChF;EACA,MAAM,IAAI,MAAM,mDAAmD;CACrE;CAEA,OAAO,SAAS;EACd,IAAI,KAAK,SAAS,QAChB,OAAO;GACL,OAAO;GACP,OAAO;IAAC;IAAK;IAAK;IAAS;GAAQ;GACnC,KAAK,MAAO,EAAE,SAAS,SAAS;IAAC,EAAE;IAAG,EAAE;IAAG,EAAE;IAAO,EAAE;GAAM,IAAI,CAAC;GACjE,OAAO,OAAO;IAAE,MAAM;IAAQ,GAAG,EAAE;IAAK,GAAG,EAAE;IAAK,OAAO,EAAE;IAAK,QAAQ,EAAE;GAAI;GAC9E,MAAM,GAAG,MAAO,EAAE,SAAS,SAAS,IAAI;IACtC,MAAM;IAAQ,GAAG,EAAE,IAAI,EAAE;IAAK,GAAG,EAAE,IAAI,EAAE;IAAK,OAAO,EAAE,QAAQ,EAAE;IAAK,QAAQ,EAAE,SAAS,EAAE;GAC7F;GACA,QAAQ,GAAG,KAAK,OAAO;IACrB,IAAI,EAAE,SAAS,QAAQ;IACvB,IAAI,MAAM,EAAE;IACZ,IAAI,KAAK,KAAK,EAAE;IAChB,IAAI,KAAK,KAAK,EAAE;IAChB,IAAI,KAAK,KAAK,EAAE;GAClB;EACF;EAEF,OAAO;GACL,OAAO,KAAK,OAAO;GACnB,KAAK,MAAO,EAAE,SAAS,YAAY,MAAM,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC;GACjE,OAAO,OAAO;IAAE,GAAG;IAAM,QAAQ,WAAW,KAAK,QAAQ,CAAsB;GAAE;GACjF,MAAM,GAAG,MAAM;IACb,IAAI,EAAE,SAAS,WAAW,OAAO;IACjC,MAAM,QAAQ,aAAa,KAAK,YAAY,CAAC,CAAC;IAC9C,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,MAAM,MAAO,EAAE;IACtD,OAAO;KAAE,GAAG;KAAM,QAAQ,WAAW,KAAK,QAAQ,KAAK;IAAE;GAC3D;GACA,QAAQ,GAAG,KAAK,OAAO;IACrB,IAAI,EAAE,SAAS,WAAW;IAC1B,MAAM,QAAQ,YAAY,CAAC;IAC3B,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,IAAI,KAAK,KAAK,MAAM;GAC7D;EACF;CACF;CAMA,wBAAwB;AAC1B"}
@@ -2,6 +2,15 @@ import { axisAlignedBounds } from "./unionBounds.js";
2
2
  import { multiply, poseLinearOf, rotate } from "@weasel-js/geom";
3
3
 
4
4
  //#region src/core/geometry/poseDescriptor.ts
5
+ /** A world `lead` expressed in the frame whose world pose is `frameWorld`, for a
6
+ * pose stored there; `null` is the root frame, where it is unchanged. */
7
+ function leadUnder(pc, frameWorld, lead) {
8
+ if (frameWorld === null) return lead;
9
+ return {
10
+ from: pc.decompose(frameWorld, lead.from),
11
+ to: pc.decompose(frameWorld, lead.to)
12
+ };
13
+ }
5
14
  /** The descriptor `node` should be read through: its own specialization when
6
15
  * it declares one, and otherwise the descriptor unchanged. */
7
16
  function poseDescriptorForNode(descriptor, node) {
@@ -214,8 +223,8 @@ function framedBounds(pose, geometry) {
214
223
  }
215
224
  /** Translate through `geometry.translate`, or via `remapBounds` onto a shifted
216
225
  * copy of the pose's own bounds when the descriptor has none. */
217
- function translatePoseViaDescriptor(pose, dx, dy, geometry) {
218
- if (geometry.translate) return geometry.translate(pose, dx, dy);
226
+ function translatePoseViaDescriptor(pose, dx, dy, geometry, lead) {
227
+ if (geometry.translate) return lead ? geometry.translate(pose, dx, dy, lead) : geometry.translate(pose, dx, dy);
219
228
  const src = geometry.getBounds(pose);
220
229
  const dst = {
221
230
  x: src.x + dx,
@@ -227,5 +236,5 @@ function translatePoseViaDescriptor(pose, dx, dy, geometry) {
227
236
  }
228
237
 
229
238
  //#endregion
230
- export { RECT_POSE_DESCRIPTOR, ROTATED_POSE_DESCRIPTOR, framedBounds, poseDescriptorForNode, remapRotatedLeaf, translatePoseViaDescriptor, visualBoundsViaDescriptor };
239
+ export { RECT_POSE_DESCRIPTOR, ROTATED_POSE_DESCRIPTOR, framedBounds, leadUnder, poseDescriptorForNode, remapRotatedLeaf, translatePoseViaDescriptor, visualBoundsViaDescriptor };
231
240
  //# sourceMappingURL=poseDescriptor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"poseDescriptor.js","names":[],"sources":["../../../src/core/geometry/poseDescriptor.ts"],"sourcesContent":["import type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { axisAlignedBounds } from 'core/geometry/unionBounds';\nimport { multiply, poseLinearOf, rotate } from '@weasel-js/geom';\nimport type { TargetAxes } from '@weasel-js/animator';\n\n/** `Bounds` with a required rotation angle (radians). Pivot is the AABB\n * center of the unrotated `{x, y, width, height}`. */\nexport interface RotatedPose extends Bounds {\n rotation: number;\n}\n\n/**\n * Bridges arbitrary `TPose` shapes into the kit's bounds-driven math: bounds\n * are read via `getBounds`, and `remapBounds` projects a result back into\n * TPose.\n *\n * `remapBounds(pose, src, dst)` subsumes both \"set my own AABB to dst\"\n * (single-leaf resize) and \"scale me as a leaf inside parent's src→dst rect\"\n * (group resize) — they're the same affine map.\n */\nexport interface PoseDescriptor<TPose, TNode = unknown> {\n getBounds(pose: TPose): Bounds;\n remapBounds(pose: TPose, src: Bounds, dst: Bounds): TPose;\n /** A pose occupying `bounds`, of the same kind as `template`. Unlike\n * `remapBounds`, the result carries none of the template's shape. */\n fromBounds(bounds: Bounds, template: TPose): TPose;\n /** Translate the pose by (dx, dy). Optional — when omitted, callers fall\n * back to a translation derived from `remapBounds` (origin shifted, no\n * scale). Path-shaped poses should provide this for performance. */\n translate?(pose: TPose, dx: number, dy: number): TPose;\n /** Interpolate between two poses. Optional — animation helpers fall back to\n * rect-shape lerp when omitted (which fails for non-rect poses). */\n lerp?(a: TPose, b: TPose, t: number): TPose;\n /** How a pose of `like`'s kind maps to the flat numbers an animation moves, and which of them\n * are angles. `from` keeps the fields of `like` that are not axes. Null for a pose this\n * descriptor cannot map; absent, a pose animates by its flat numeric fields. */\n axes?(like: TPose): TargetAxes<TPose> | null;\n /** Read the pose's rotation in radians. Pivot is the AABB center\n * (`getBounds(pose)` center). Default 0 when omitted. */\n getRotation?(pose: TPose): number;\n /** True iff this pose shape can carry a rotation; the rotation affordance\n * hides when it is `false`. When omitted, the kit assumes `true`, so a\n * descriptor whose poses cannot rotate (e.g. polygon Paths) should\n * return `false`. */\n supportsRotation?(pose: TPose): boolean;\n /** Write a rotation (radians) into the pose, bounds unchanged. Absent means\n * the pose cannot carry one, and the rotate action leaves it alone. */\n withRotation?(pose: TPose, rotation: number): TPose;\n /**\n * A descriptor specialized to one node. Every other method here sees only\n * the pose, so shapes sharing a pose type are indistinguishable to them.\n * Implement this to read `node.data` once and hand back a descriptor that\n * knows which shape it is describing. Callers reach it through\n * {@link poseDescriptorForNode}; sites with only an id keep using the\n * unspecialized descriptor.\n */\n forNode?(node: TNode): PoseDescriptor<TPose, TNode>;\n}\n\n/** The descriptor `node` should be read through: its own specialization when\n * it declares one, and otherwise the descriptor unchanged. */\nexport function poseDescriptorForNode<TPose, TNode>(\n descriptor: PoseDescriptor<TPose, TNode>,\n node: TNode,\n): PoseDescriptor<TPose, TNode> {\n return descriptor.forNode?.(node) ?? descriptor;\n}\n\n/** Identity geometry for `TPose extends Bounds`. Treats the pose as its\n * own bounds and remaps via affine scale against `src`/`dst`. */\nexport const RECT_POSE_DESCRIPTOR: PoseDescriptor<Bounds> = {\n getBounds: (p) => p,\n remapBounds: (p, src, dst) => {\n const sx = src.width === 0 ? 1 : dst.width / src.width;\n const sy = src.height === 0 ? 1 : dst.height / src.height;\n return {\n ...p,\n x: dst.x + (p.x - src.x) * sx,\n y: dst.y + (p.y - src.y) * sy,\n width: p.width * sx,\n height: p.height * sy,\n };\n },\n fromBounds: (b) => ({ x: b.x, y: b.y, width: b.width, height: b.height }),\n translate: (p, dx, dy) => ({ ...p, x: p.x + dx, y: p.y + dy }),\n lerp: (a, b, t) => ({\n ...a,\n x: a.x + (b.x - a.x) * t,\n y: a.y + (b.y - a.y) * t,\n width: a.width + (b.width - a.width) * t,\n height: a.height + (b.height - a.height) * t,\n }),\n axes: (like) => {\n const flat = [like.x, like.y, like.width, like.height];\n if (!flat.every((n) => typeof n === 'number')) return null;\n return {\n count: 5,\n names: ['x', 'y', 'width', 'height', 'rotation'],\n angles: [4],\n to: (p) => [p.x, p.y, p.width, p.height, p.rotation ?? 0],\n from: (a) => {\n const pose = { ...like, x: a[0]!, y: a[1]!, width: a[2]!, height: a[3]! };\n if (like.rotation !== undefined || a[4] !== 0) pose.rotation = a[4]!;\n return pose;\n },\n add: (p, d) => {\n const pose = { ...p, x: p.x + d[0]!, y: p.y + d[1]!, width: p.width + d[2]!, height: p.height + d[3]! };\n const rotation = (p.rotation ?? 0) + d[4]!;\n if (p.rotation !== undefined || rotation !== 0) pose.rotation = rotation;\n return pose;\n },\n write: (p, out, at) => {\n out[at] = p.x;\n out[at + 1] = p.y;\n out[at + 2] = p.width;\n out[at + 3] = p.height;\n out[at + 4] = p.rotation ?? 0;\n },\n };\n },\n // `withRotation` writes into `Bounds`' optional `rotation`, so this reads\n // the same channel, or a pose reports itself unrotated while painting rotated.\n getRotation: (p) => p.rotation ?? 0,\n withRotation: (p, rotation) => ({ ...p, rotation }),\n};\n\n/** Identity geometry for `RotatedPose`: `RECT_POSE_DESCRIPTOR`'s projection,\n * with a required `rotation`. */\nexport const ROTATED_POSE_DESCRIPTOR: PoseDescriptor<RotatedPose> = {\n getBounds: RECT_POSE_DESCRIPTOR.getBounds as PoseDescriptor<RotatedPose>['getBounds'],\n remapBounds: RECT_POSE_DESCRIPTOR.remapBounds as PoseDescriptor<RotatedPose>['remapBounds'],\n translate: RECT_POSE_DESCRIPTOR.translate as PoseDescriptor<RotatedPose>['translate'],\n lerp: RECT_POSE_DESCRIPTOR.lerp as PoseDescriptor<RotatedPose>['lerp'],\n axes: RECT_POSE_DESCRIPTOR.axes as PoseDescriptor<RotatedPose>['axes'],\n fromBounds: (b) => ({ x: b.x, y: b.y, width: b.width, height: b.height, rotation: 0 }),\n getRotation: (p) => p.rotation,\n withRotation: (p, rotation) => ({ ...p, rotation }),\n};\n\n/**\n * Remap a leaf drawn in a frame of its own, turned or skewed, inside a group\n * resize.\n *\n * The naive `remapBounds` scales a leaf's axis-aligned `width`/`height` by the\n * group's per-axis factors and carries `rotation` through untouched. That is\n * right when `src`/`dst` are expressed in the leaf's own frame. In a group\n * resize they are world-frame: at 90° a horizontal stretch should grow the\n * leaf's `height`, and the naive map grows its `width`.\n *\n * What this computes is the group affine applied to the leaf's frame. The\n * center moves exactly; each box axis takes the length of its own image under\n * the turn; the rotation follows the image of the box's x-axis; and the\n * parallelogram a non-uniform scale makes of a turned box, which those cannot\n * hold, is written to the leaf's `transform`.\n */\nexport function remapRotatedLeaf<TPose extends RotatedPose>(\n pose: TPose,\n src: Bounds,\n dst: Bounds,\n): TPose {\n const sx = src.width === 0 ? 1 : dst.width / src.width;\n const sy = src.height === 0 ? 1 : dst.height / src.height;\n const theta = pose.rotation ?? 0;\n const cos = Math.cos(theta);\n const sin = Math.sin(theta);\n\n // Images of the leaf's local unit axes under the group's diagonal scale.\n const ux = sx * cos, uy = sy * sin;\n const vx = -sx * sin, vy = sy * cos;\n const kx = Math.hypot(ux, uy);\n const ky = Math.hypot(vx, vy);\n\n const width = pose.width * kx;\n const height = pose.height * ky;\n const rotation = Math.atan2(uy, ux);\n\n // The center is a point, so it takes the group affine directly.\n const cxNew = dst.x + (pose.x + pose.width / 2 - src.x) * sx;\n const cyNew = dst.y + (pose.y + pose.height / 2 - src.y) * sy;\n\n const next: RotatedPose = { ...pose, x: cxNew - width / 2, y: cyNew - height / 2, width, height, rotation };\n // A collapsed axis leaves no frame to solve for; the leaf keeps the transform it had.\n if (kx === 0 || ky === 0) return next as TPose;\n const [ta, tb, tc, td] = poseLinearOf(pose) ?? [1, 0, 0, 1];\n // The linear part the leaf must draw with, less what the new box and rotation already hold.\n const [a, b, c, d] = multiply(\n multiply(rotate(-rotation), [ux, uy, vx, vy, 0, 0]),\n [ta / kx, tb / kx, tc / ky, td / ky, 0, 0],\n );\n if (Math.abs(a - 1) < 1e-12 && Math.abs(b) < 1e-12 && Math.abs(c) < 1e-12 && Math.abs(d - 1) < 1e-12) {\n delete next.transform;\n } else {\n next.transform = [a, b, c, d];\n }\n return next as TPose;\n}\n\n/** The pose's visual bounds: its box expanded to cover the box as drawn\n * under its rotation and `transform`, so a turned or skewed shape reports\n * the extent of its ink. */\nexport function visualBoundsViaDescriptor<TPose>(\n pose: TPose,\n geometry: PoseDescriptor<TPose>,\n): Bounds {\n return axisAlignedBounds(framedBounds(pose, geometry));\n}\n\n/** The pose's box with the rotation and `transform` it is drawn in: what\n * `poseFrameOf` reads, for a pose only its descriptor can read. */\nexport function framedBounds<TPose>(pose: TPose, geometry: PoseDescriptor<TPose>): Bounds {\n const b = geometry.getBounds(pose);\n const rotation = geometry.getRotation?.(pose) ?? b.rotation ?? 0;\n const transform = poseLinearOf(pose) ?? poseLinearOf(b);\n if (!rotation && !transform) return b;\n return { ...b, ...(rotation ? { rotation } : {}), ...(transform ? { transform } : {}) };\n}\n\n/** Translate through `geometry.translate`, or via `remapBounds` onto a shifted\n * copy of the pose's own bounds when the descriptor has none. */\nexport function translatePoseViaDescriptor<TPose>(\n pose: TPose,\n dx: number,\n dy: number,\n geometry: PoseDescriptor<TPose>,\n): TPose {\n if (geometry.translate) return geometry.translate(pose, dx, dy);\n const src = geometry.getBounds(pose);\n const dst = { x: src.x + dx, y: src.y + dy, width: src.width, height: src.height };\n return geometry.remapBounds(pose, src, dst);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"poseDescriptor.js","names":[],"sources":["../../../src/core/geometry/poseDescriptor.ts"],"sourcesContent":["import type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { axisAlignedBounds } from 'core/geometry/unionBounds';\nimport { multiply, poseLinearOf, rotate } from '@weasel-js/geom';\nimport type { TargetAxes } from '@weasel-js/animator';\nimport type { PoseComposition } from 'core/scene/composeFrame';\n\n/** `Bounds` with a required rotation angle (radians). Pivot is the AABB\n * center of the unrotated `{x, y, width, height}`. */\nexport interface RotatedPose extends Bounds {\n rotation: number;\n}\n\n/** One pose before and after a translate, handed to the translate of another\n * pose in the same move. Both are in the frame the translated pose is stored\n * in: world for a root, its frame's coordinates for a pose under a container. */\nexport interface TranslateLead<TPose> {\n readonly from: TPose;\n readonly to: TPose;\n}\n\n/** A world `lead` expressed in the frame whose world pose is `frameWorld`, for a\n * pose stored there; `null` is the root frame, where it is unchanged. */\nexport function leadUnder<TPose>(\n pc: Pick<PoseComposition<TPose>, 'decompose'>,\n frameWorld: TPose | null,\n lead: TranslateLead<TPose>,\n): TranslateLead<TPose> {\n if (frameWorld === null) return lead;\n return { from: pc.decompose(frameWorld, lead.from), to: pc.decompose(frameWorld, lead.to) };\n}\n\n/**\n * Bridges arbitrary `TPose` shapes into the kit's bounds-driven math: bounds\n * are read via `getBounds`, and `remapBounds` projects a result back into\n * TPose.\n *\n * `remapBounds(pose, src, dst)` subsumes both \"set my own AABB to dst\"\n * (single-leaf resize) and \"scale me as a leaf inside parent's src→dst rect\"\n * (group resize) — they're the same affine map.\n */\nexport interface PoseDescriptor<TPose, TNode = unknown> {\n getBounds(pose: TPose): Bounds;\n remapBounds(pose: TPose, src: Bounds, dst: Bounds): TPose;\n /** A pose occupying `bounds`, of the same kind as `template`. Unlike\n * `remapBounds`, the result carries none of the template's shape. */\n fromBounds(bounds: Bounds, template: TPose): TPose;\n /** Translate the pose by (dx, dy). Optional — when omitted, callers fall\n * back to a translation derived from `remapBounds` (origin shifted, no\n * scale). Path-shaped poses should provide this for performance.\n *\n * `lead`, when given, is how another pose in the same move went under this\n * same (dx, dy). A descriptor for which (dx, dy) is one translation for\n * every pose ignores it. One that resolves it from where the pose sits, as\n * under a perspective camera, moves `pose` by the lead's own change, so a\n * move of several lands each of them by the amount it landed the lead. */\n translate?(pose: TPose, dx: number, dy: number, lead?: TranslateLead<TPose>): TPose;\n /** Interpolate between two poses. Optional — animation helpers fall back to\n * rect-shape lerp when omitted (which fails for non-rect poses). */\n lerp?(a: TPose, b: TPose, t: number): TPose;\n /** How a pose of `like`'s kind maps to the flat numbers an animation moves, and which of them\n * are angles. `from` keeps the fields of `like` that are not axes. Null for a pose this\n * descriptor cannot map; absent, a pose animates by its flat numeric fields. */\n axes?(like: TPose): TargetAxes<TPose> | null;\n /** Read the pose's rotation in radians. Pivot is the AABB center\n * (`getBounds(pose)` center). Default 0 when omitted. */\n getRotation?(pose: TPose): number;\n /** True iff this pose shape can carry a rotation; the rotation affordance\n * hides when it is `false`. When omitted, the kit assumes `true`, so a\n * descriptor whose poses cannot rotate (e.g. polygon Paths) should\n * return `false`. */\n supportsRotation?(pose: TPose): boolean;\n /** Write a rotation (radians) into the pose, bounds unchanged. Absent means\n * the pose cannot carry one, and the rotate action leaves it alone. */\n withRotation?(pose: TPose, rotation: number): TPose;\n /**\n * A descriptor specialized to one node. Every other method here sees only\n * the pose, so shapes sharing a pose type are indistinguishable to them.\n * Implement this to read `node.data` once and hand back a descriptor that\n * knows which shape it is describing. Callers reach it through\n * {@link poseDescriptorForNode}; sites with only an id keep using the\n * unspecialized descriptor.\n */\n forNode?(node: TNode): PoseDescriptor<TPose, TNode>;\n}\n\n/** The descriptor `node` should be read through: its own specialization when\n * it declares one, and otherwise the descriptor unchanged. */\nexport function poseDescriptorForNode<TPose, TNode>(\n descriptor: PoseDescriptor<TPose, TNode>,\n node: TNode,\n): PoseDescriptor<TPose, TNode> {\n return descriptor.forNode?.(node) ?? descriptor;\n}\n\n/** Identity geometry for `TPose extends Bounds`. Treats the pose as its\n * own bounds and remaps via affine scale against `src`/`dst`. */\nexport const RECT_POSE_DESCRIPTOR: PoseDescriptor<Bounds> = {\n getBounds: (p) => p,\n remapBounds: (p, src, dst) => {\n const sx = src.width === 0 ? 1 : dst.width / src.width;\n const sy = src.height === 0 ? 1 : dst.height / src.height;\n return {\n ...p,\n x: dst.x + (p.x - src.x) * sx,\n y: dst.y + (p.y - src.y) * sy,\n width: p.width * sx,\n height: p.height * sy,\n };\n },\n fromBounds: (b) => ({ x: b.x, y: b.y, width: b.width, height: b.height }),\n translate: (p, dx, dy) => ({ ...p, x: p.x + dx, y: p.y + dy }),\n lerp: (a, b, t) => ({\n ...a,\n x: a.x + (b.x - a.x) * t,\n y: a.y + (b.y - a.y) * t,\n width: a.width + (b.width - a.width) * t,\n height: a.height + (b.height - a.height) * t,\n }),\n axes: (like) => {\n const flat = [like.x, like.y, like.width, like.height];\n if (!flat.every((n) => typeof n === 'number')) return null;\n return {\n count: 5,\n names: ['x', 'y', 'width', 'height', 'rotation'],\n angles: [4],\n to: (p) => [p.x, p.y, p.width, p.height, p.rotation ?? 0],\n from: (a) => {\n const pose = { ...like, x: a[0]!, y: a[1]!, width: a[2]!, height: a[3]! };\n if (like.rotation !== undefined || a[4] !== 0) pose.rotation = a[4]!;\n return pose;\n },\n add: (p, d) => {\n const pose = { ...p, x: p.x + d[0]!, y: p.y + d[1]!, width: p.width + d[2]!, height: p.height + d[3]! };\n const rotation = (p.rotation ?? 0) + d[4]!;\n if (p.rotation !== undefined || rotation !== 0) pose.rotation = rotation;\n return pose;\n },\n write: (p, out, at) => {\n out[at] = p.x;\n out[at + 1] = p.y;\n out[at + 2] = p.width;\n out[at + 3] = p.height;\n out[at + 4] = p.rotation ?? 0;\n },\n };\n },\n // `withRotation` writes into `Bounds`' optional `rotation`, so this reads\n // the same channel, or a pose reports itself unrotated while painting rotated.\n getRotation: (p) => p.rotation ?? 0,\n withRotation: (p, rotation) => ({ ...p, rotation }),\n};\n\n/** Identity geometry for `RotatedPose`: `RECT_POSE_DESCRIPTOR`'s projection,\n * with a required `rotation`. */\nexport const ROTATED_POSE_DESCRIPTOR: PoseDescriptor<RotatedPose> = {\n getBounds: RECT_POSE_DESCRIPTOR.getBounds as PoseDescriptor<RotatedPose>['getBounds'],\n remapBounds: RECT_POSE_DESCRIPTOR.remapBounds as PoseDescriptor<RotatedPose>['remapBounds'],\n translate: RECT_POSE_DESCRIPTOR.translate as PoseDescriptor<RotatedPose>['translate'],\n lerp: RECT_POSE_DESCRIPTOR.lerp as PoseDescriptor<RotatedPose>['lerp'],\n axes: RECT_POSE_DESCRIPTOR.axes as PoseDescriptor<RotatedPose>['axes'],\n fromBounds: (b) => ({ x: b.x, y: b.y, width: b.width, height: b.height, rotation: 0 }),\n getRotation: (p) => p.rotation,\n withRotation: (p, rotation) => ({ ...p, rotation }),\n};\n\n/**\n * Remap a leaf drawn in a frame of its own, turned or skewed, inside a group\n * resize.\n *\n * The naive `remapBounds` scales a leaf's axis-aligned `width`/`height` by the\n * group's per-axis factors and carries `rotation` through untouched. That is\n * right when `src`/`dst` are expressed in the leaf's own frame. In a group\n * resize they are world-frame: at 90° a horizontal stretch should grow the\n * leaf's `height`, and the naive map grows its `width`.\n *\n * What this computes is the group affine applied to the leaf's frame. The\n * center moves exactly; each box axis takes the length of its own image under\n * the turn; the rotation follows the image of the box's x-axis; and the\n * parallelogram a non-uniform scale makes of a turned box, which those cannot\n * hold, is written to the leaf's `transform`.\n */\nexport function remapRotatedLeaf<TPose extends RotatedPose>(\n pose: TPose,\n src: Bounds,\n dst: Bounds,\n): TPose {\n const sx = src.width === 0 ? 1 : dst.width / src.width;\n const sy = src.height === 0 ? 1 : dst.height / src.height;\n const theta = pose.rotation ?? 0;\n const cos = Math.cos(theta);\n const sin = Math.sin(theta);\n\n // Images of the leaf's local unit axes under the group's diagonal scale.\n const ux = sx * cos, uy = sy * sin;\n const vx = -sx * sin, vy = sy * cos;\n const kx = Math.hypot(ux, uy);\n const ky = Math.hypot(vx, vy);\n\n const width = pose.width * kx;\n const height = pose.height * ky;\n const rotation = Math.atan2(uy, ux);\n\n // The center is a point, so it takes the group affine directly.\n const cxNew = dst.x + (pose.x + pose.width / 2 - src.x) * sx;\n const cyNew = dst.y + (pose.y + pose.height / 2 - src.y) * sy;\n\n const next: RotatedPose = { ...pose, x: cxNew - width / 2, y: cyNew - height / 2, width, height, rotation };\n // A collapsed axis leaves no frame to solve for; the leaf keeps the transform it had.\n if (kx === 0 || ky === 0) return next as TPose;\n const [ta, tb, tc, td] = poseLinearOf(pose) ?? [1, 0, 0, 1];\n // The linear part the leaf must draw with, less what the new box and rotation already hold.\n const [a, b, c, d] = multiply(\n multiply(rotate(-rotation), [ux, uy, vx, vy, 0, 0]),\n [ta / kx, tb / kx, tc / ky, td / ky, 0, 0],\n );\n if (Math.abs(a - 1) < 1e-12 && Math.abs(b) < 1e-12 && Math.abs(c) < 1e-12 && Math.abs(d - 1) < 1e-12) {\n delete next.transform;\n } else {\n next.transform = [a, b, c, d];\n }\n return next as TPose;\n}\n\n/** The pose's visual bounds: its box expanded to cover the box as drawn\n * under its rotation and `transform`, so a turned or skewed shape reports\n * the extent of its ink. */\nexport function visualBoundsViaDescriptor<TPose>(\n pose: TPose,\n geometry: PoseDescriptor<TPose>,\n): Bounds {\n return axisAlignedBounds(framedBounds(pose, geometry));\n}\n\n/** The pose's box with the rotation and `transform` it is drawn in: what\n * `poseFrameOf` reads, for a pose only its descriptor can read. */\nexport function framedBounds<TPose>(pose: TPose, geometry: PoseDescriptor<TPose>): Bounds {\n const b = geometry.getBounds(pose);\n const rotation = geometry.getRotation?.(pose) ?? b.rotation ?? 0;\n const transform = poseLinearOf(pose) ?? poseLinearOf(b);\n if (!rotation && !transform) return b;\n return { ...b, ...(rotation ? { rotation } : {}), ...(transform ? { transform } : {}) };\n}\n\n/** Translate through `geometry.translate`, or via `remapBounds` onto a shifted\n * copy of the pose's own bounds when the descriptor has none. */\nexport function translatePoseViaDescriptor<TPose>(\n pose: TPose,\n dx: number,\n dy: number,\n geometry: PoseDescriptor<TPose>,\n lead?: TranslateLead<TPose>,\n): TPose {\n if (geometry.translate) return lead ? geometry.translate(pose, dx, dy, lead) : geometry.translate(pose, dx, dy);\n const src = geometry.getBounds(pose);\n const dst = { x: src.x + dx, y: src.y + dy, width: src.width, height: src.height };\n return geometry.remapBounds(pose, src, dst);\n}\n"],"mappings":";;;;;;AAsBA,SAAgB,UACd,IACA,YACA,MACsB;CACtB,IAAI,eAAe,MAAM,OAAO;CAChC,OAAO;EAAE,MAAM,GAAG,UAAU,YAAY,KAAK,IAAI;EAAG,IAAI,GAAG,UAAU,YAAY,KAAK,EAAE;CAAE;AAC5F;;;AA0DA,SAAgB,sBACd,YACA,MAC8B;CAC9B,OAAO,WAAW,UAAU,IAAI,KAAK;AACvC;;;AAIA,IAAa,uBAA+C;CAC1D,YAAY,MAAM;CAClB,cAAc,GAAG,KAAK,QAAQ;EAC5B,MAAM,KAAK,IAAI,UAAU,IAAI,IAAI,IAAI,QAAQ,IAAI;EACjD,MAAM,KAAK,IAAI,WAAW,IAAI,IAAI,IAAI,SAAS,IAAI;EACnD,OAAO;GACL,GAAG;GACH,GAAG,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK;GAC3B,GAAG,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK;GAC3B,OAAO,EAAE,QAAQ;GACjB,QAAQ,EAAE,SAAS;EACrB;CACF;CACA,aAAa,OAAO;EAAE,GAAG,EAAE;EAAG,GAAG,EAAE;EAAG,OAAO,EAAE;EAAO,QAAQ,EAAE;CAAO;CACvE,YAAY,GAAG,IAAI,QAAQ;EAAE,GAAG;EAAG,GAAG,EAAE,IAAI;EAAI,GAAG,EAAE,IAAI;CAAG;CAC5D,OAAO,GAAG,GAAG,OAAO;EAClB,GAAG;EACH,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK;EACvB,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK;EACvB,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS;EACvC,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU;CAC7C;CACA,OAAO,SAAS;EAEd,IAAI,CAAC;GADS,KAAK;GAAG,KAAK;GAAG,KAAK;GAAO,KAAK;EAC1C,CAAA,CAAK,OAAO,MAAM,OAAO,MAAM,QAAQ,GAAG,OAAO;EACtD,OAAO;GACL,OAAO;GACP,OAAO;IAAC;IAAK;IAAK;IAAS;IAAU;GAAU;GAC/C,QAAQ,CAAC,CAAC;GACV,KAAK,MAAM;IAAC,EAAE;IAAG,EAAE;IAAG,EAAE;IAAO,EAAE;IAAQ,EAAE,YAAY;GAAC;GACxD,OAAO,MAAM;IACX,MAAM,OAAO;KAAE,GAAG;KAAM,GAAG,EAAE;KAAK,GAAG,EAAE;KAAK,OAAO,EAAE;KAAK,QAAQ,EAAE;IAAI;IACxE,IAAI,KAAK,aAAa,UAAa,EAAE,OAAO,GAAG,KAAK,WAAW,EAAE;IACjE,OAAO;GACT;GACA,MAAM,GAAG,MAAM;IACb,MAAM,OAAO;KAAE,GAAG;KAAG,GAAG,EAAE,IAAI,EAAE;KAAK,GAAG,EAAE,IAAI,EAAE;KAAK,OAAO,EAAE,QAAQ,EAAE;KAAK,QAAQ,EAAE,SAAS,EAAE;IAAI;IACtG,MAAM,YAAY,EAAE,YAAY,KAAK,EAAE;IACvC,IAAI,EAAE,aAAa,UAAa,aAAa,GAAG,KAAK,WAAW;IAChE,OAAO;GACT;GACA,QAAQ,GAAG,KAAK,OAAO;IACrB,IAAI,MAAM,EAAE;IACZ,IAAI,KAAK,KAAK,EAAE;IAChB,IAAI,KAAK,KAAK,EAAE;IAChB,IAAI,KAAK,KAAK,EAAE;IAChB,IAAI,KAAK,KAAK,EAAE,YAAY;GAC9B;EACF;CACF;CAGA,cAAc,MAAM,EAAE,YAAY;CAClC,eAAe,GAAG,cAAc;EAAE,GAAG;EAAG;CAAS;AACnD;;;AAIA,IAAa,0BAAuD;CAClE,WAAW,qBAAqB;CAChC,aAAa,qBAAqB;CAClC,WAAW,qBAAqB;CAChC,MAAM,qBAAqB;CAC3B,MAAM,qBAAqB;CAC3B,aAAa,OAAO;EAAE,GAAG,EAAE;EAAG,GAAG,EAAE;EAAG,OAAO,EAAE;EAAO,QAAQ,EAAE;EAAQ,UAAU;CAAE;CACpF,cAAc,MAAM,EAAE;CACtB,eAAe,GAAG,cAAc;EAAE,GAAG;EAAG;CAAS;AACnD;;;;;;;;;;;;;;;;;AAkBA,SAAgB,iBACd,MACA,KACA,KACO;CACP,MAAM,KAAK,IAAI,UAAU,IAAI,IAAI,IAAI,QAAQ,IAAI;CACjD,MAAM,KAAK,IAAI,WAAW,IAAI,IAAI,IAAI,SAAS,IAAI;CACnD,MAAM,QAAQ,KAAK,YAAY;CAC/B,MAAM,MAAM,KAAK,IAAI,KAAK;CAC1B,MAAM,MAAM,KAAK,IAAI,KAAK;CAG1B,MAAM,KAAK,KAAK,KAAK,KAAK,KAAK;CAC/B,MAAM,KAAK,CAAC,KAAK,KAAK,KAAK,KAAK;CAChC,MAAM,KAAK,KAAK,MAAM,IAAI,EAAE;CAC5B,MAAM,KAAK,KAAK,MAAM,IAAI,EAAE;CAE5B,MAAM,QAAQ,KAAK,QAAQ;CAC3B,MAAM,SAAS,KAAK,SAAS;CAC7B,MAAM,WAAW,KAAK,MAAM,IAAI,EAAE;CAGlC,MAAM,QAAQ,IAAI,KAAK,KAAK,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK;CAC1D,MAAM,QAAQ,IAAI,KAAK,KAAK,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK;CAE3D,MAAM,OAAoB;EAAE,GAAG;EAAM,GAAG,QAAQ,QAAQ;EAAG,GAAG,QAAQ,SAAS;EAAG;EAAO;EAAQ;CAAS;CAE1G,IAAI,OAAO,KAAK,OAAO,GAAG,OAAO;CACjC,MAAM,CAAC,IAAI,IAAI,IAAI,MAAM,aAAa,IAAI,KAAK;EAAC;EAAG;EAAG;EAAG;CAAC;CAE1D,MAAM,CAAC,GAAG,GAAG,GAAG,KAAK,SACnB,SAAS,OAAO,CAAC,QAAQ,GAAG;EAAC;EAAI;EAAI;EAAI;EAAI;EAAG;CAAC,CAAC,GAClD;EAAC,KAAK;EAAI,KAAK;EAAI,KAAK;EAAI,KAAK;EAAI;EAAG;CAAC,CAC3C;CACA,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,SAAS,KAAK,IAAI,CAAC,IAAI,SAAS,KAAK,IAAI,CAAC,IAAI,SAAS,KAAK,IAAI,IAAI,CAAC,IAAI,OAC7F,OAAO,KAAK;MAEZ,KAAK,YAAY;EAAC;EAAG;EAAG;EAAG;CAAC;CAE9B,OAAO;AACT;;;;AAKA,SAAgB,0BACd,MACA,UACQ;CACR,OAAO,kBAAkB,aAAa,MAAM,QAAQ,CAAC;AACvD;;;AAIA,SAAgB,aAAoB,MAAa,UAAyC;CACxF,MAAM,IAAI,SAAS,UAAU,IAAI;CACjC,MAAM,WAAW,SAAS,cAAc,IAAI,KAAK,EAAE,YAAY;CAC/D,MAAM,YAAY,aAAa,IAAI,KAAK,aAAa,CAAC;CACtD,IAAI,CAAC,YAAY,CAAC,WAAW,OAAO;CACpC,OAAO;EAAE,GAAG;EAAG,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;EAAI,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;CAAG;AACxF;;;AAIA,SAAgB,2BACd,MACA,IACA,IACA,UACA,MACO;CACP,IAAI,SAAS,WAAW,OAAO,OAAO,SAAS,UAAU,MAAM,IAAI,IAAI,IAAI,IAAI,SAAS,UAAU,MAAM,IAAI,EAAE;CAC9G,MAAM,MAAM,SAAS,UAAU,IAAI;CACnC,MAAM,MAAM;EAAE,GAAG,IAAI,IAAI;EAAI,GAAG,IAAI,IAAI;EAAI,OAAO,IAAI;EAAO,QAAQ,IAAI;CAAO;CACjF,OAAO,SAAS,YAAY,MAAM,KAAK,GAAG;AAC5C"}
@@ -1,4 +1,4 @@
1
- import { applyToPoint, invert, poseFrameOf } from "@weasel-js/geom";
1
+ import { applyToPoint, invert, poseFrameOf, pxExtentInFrame } from "@weasel-js/geom";
2
2
 
3
3
  //#region src/core/geometry/targetFrame.ts
4
4
  /**
@@ -90,18 +90,7 @@ function normalToWorld(xf, n) {
90
90
  /** `pxExtent` along the target's own axes: how far along each an edge across
91
91
  * it must move to move `px` on screen. */
92
92
  function pxExtentIn(px, scale, xf) {
93
- const [a, b, c, d] = xf.toWorld;
94
- const ux = a * scale.x, uy = b * scale.y;
95
- const vx = c * scale.x, vy = d * scale.y;
96
- const det = Math.abs(ux * vy - uy * vx);
97
- if (det === 0) return {
98
- x: 0,
99
- y: 0
100
- };
101
- return {
102
- x: px * Math.hypot(vx, vy) / det,
103
- y: px * Math.hypot(ux, uy) / det
104
- };
93
+ return pxExtentInFrame(px, scale, xf.toWorld);
105
94
  }
106
95
  /** The target's box corners in world, TL/TR/BR/BL. */
107
96
  function targetCorners(b, xf) {
@@ -1 +1 @@
1
- {"version":3,"file":"targetFrame.js","names":[],"sources":["../../../src/core/geometry/targetFrame.ts"],"sourcesContent":["/**\n * The frame selection chrome reads a target in: `poseFrameOf` over the\n * target's bounds, with the helpers the outline, the handles, their hit\n * regions, and the resize and rotate actions all place themselves by.\n */\nimport { applyToPoint, invert, poseFrameOf, type Affine, type Scale2 } from '@weasel-js/geom';\n\n/** Local↔world maps for a chrome target. Local is the target's unrotated box\n * frame; world is where the node is drawn. */\nexport interface TargetTransform {\n /** No rotation and no `transform`: local is world. */\n identity: boolean;\n /** Target box frame → world. */\n toWorld: Affine;\n /** World → target box frame. `null` when the target's `transform` is\n * singular: it draws nothing, and no world point maps back. */\n toLocal: Affine | null;\n}\n\nconst IDENTITY: Affine = [1, 0, 0, 1, 0, 0];\n\nexport const IDENTITY_TARGET: TargetTransform = { identity: true, toWorld: IDENTITY, toLocal: IDENTITY };\n\n/** The frame `bounds` is drawn in: its `rotation`, its `transform`, or both. */\nexport function targetTransformOf(bounds: unknown): TargetTransform {\n const toWorld = poseFrameOf(bounds);\n if (!toWorld) return IDENTITY_TARGET;\n return { identity: false, toWorld, toLocal: invert(toWorld) };\n}\n\n/** How far the target's x axis is turned from world's, radians. */\nexport function angleOf(xf: TargetTransform): number {\n return xf.identity ? 0 : Math.atan2(xf.toWorld[1], xf.toWorld[0]);\n}\n\n/** Whether the frame only turns the target: no skew, no stretch, no flip. */\nexport function isRigid(xf: TargetTransform): boolean {\n if (xf.identity) return true;\n const [a, b, c, d] = xf.toWorld;\n return Math.abs(a - d) < 1e-9 && Math.abs(b + c) < 1e-9 && Math.abs(a * a + b * b - 1) < 1e-9;\n}\n\n/** local point → world point. */\nexport function localToWorld(xf: TargetTransform, lx: number, ly: number): { x: number; y: number } {\n if (xf.identity) return { x: lx, y: ly };\n const [x, y] = applyToPoint(xf.toWorld, lx, ly);\n return { x, y };\n}\n\n/** world point → local point; `null` where nothing maps back. */\nexport function worldToLocal(xf: TargetTransform, wx: number, wy: number): { x: number; y: number } | null {\n if (xf.identity) return { x: wx, y: wy };\n if (!xf.toLocal) return null;\n const [x, y] = applyToPoint(xf.toLocal, wx, wy);\n return { x, y };\n}\n\n/** A world delta in the target's own axes; `null` where nothing maps back. */\nexport function worldDeltaToLocal(xf: TargetTransform, dx: number, dy: number): { x: number; y: number } | null {\n if (xf.identity) return { x: dx, y: dy };\n const m = xf.toLocal;\n return m && { x: m[0] * dx + m[2] * dy, y: m[1] * dx + m[3] * dy };\n}\n\n/** A local edge's outward normal as a world direction, unnormalized. A normal\n * goes through the inverse transpose, so it stays square to a skewed edge. */\nexport function normalToWorld(xf: TargetTransform, n: { x: number; y: number }): { x: number; y: number } {\n if (xf.identity) return n;\n const [a, b, c, d] = xf.toWorld;\n const k = Math.sign(a * d - b * c) || 1;\n return { x: (d * n.x - b * n.y) * k, y: (a * n.y - c * n.x) * k };\n}\n\n/** `pxExtent` along the target's own axes: how far along each an edge across\n * it must move to move `px` on screen. */\nexport function pxExtentIn(px: number, scale: Scale2, xf: TargetTransform): { x: number; y: number } {\n const [a, b, c, d] = xf.toWorld;\n const ux = a * scale.x, uy = b * scale.y;\n const vx = c * scale.x, vy = d * scale.y;\n const det = Math.abs(ux * vy - uy * vx);\n if (det === 0) return { x: 0, y: 0 };\n return { x: (px * Math.hypot(vx, vy)) / det, y: (px * Math.hypot(ux, uy)) / det };\n}\n\n/** The target's box corners in world, TL/TR/BR/BL. */\nexport function targetCorners(\n b: { x: number; y: number; width: number; height: number },\n xf: TargetTransform,\n): { x: number; y: number }[] {\n return [\n localToWorld(xf, b.x, b.y),\n localToWorld(xf, b.x + b.width, b.y),\n localToWorld(xf, b.x + b.width, b.y + b.height),\n localToWorld(xf, b.x, b.y + b.height),\n ];\n}\n"],"mappings":";;;;;;;;AAmBA,IAAM,WAAmB;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;AAE1C,IAAa,kBAAmC;CAAE,UAAU;CAAM,SAAS;CAAU,SAAS;AAAS;;AAGvG,SAAgB,kBAAkB,QAAkC;CAClE,MAAM,UAAU,YAAY,MAAM;CAClC,IAAI,CAAC,SAAS,OAAO;CACrB,OAAO;EAAE,UAAU;EAAO;EAAS,SAAS,OAAO,OAAO;CAAE;AAC9D;;AAGA,SAAgB,QAAQ,IAA6B;CACnD,OAAO,GAAG,WAAW,IAAI,KAAK,MAAM,GAAG,QAAQ,IAAI,GAAG,QAAQ,EAAE;AAClE;;AAGA,SAAgB,QAAQ,IAA8B;CACpD,IAAI,GAAG,UAAU,OAAO;CACxB,MAAM,CAAC,GAAG,GAAG,GAAG,KAAK,GAAG;CACxB,OAAO,KAAK,IAAI,IAAI,CAAC,IAAI,QAAQ,KAAK,IAAI,IAAI,CAAC,IAAI,QAAQ,KAAK,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI;AAC3F;;AAGA,SAAgB,aAAa,IAAqB,IAAY,IAAsC;CAClG,IAAI,GAAG,UAAU,OAAO;EAAE,GAAG;EAAI,GAAG;CAAG;CACvC,MAAM,CAAC,GAAG,KAAK,aAAa,GAAG,SAAS,IAAI,EAAE;CAC9C,OAAO;EAAE;EAAG;CAAE;AAChB;;AAGA,SAAgB,aAAa,IAAqB,IAAY,IAA6C;CACzG,IAAI,GAAG,UAAU,OAAO;EAAE,GAAG;EAAI,GAAG;CAAG;CACvC,IAAI,CAAC,GAAG,SAAS,OAAO;CACxB,MAAM,CAAC,GAAG,KAAK,aAAa,GAAG,SAAS,IAAI,EAAE;CAC9C,OAAO;EAAE;EAAG;CAAE;AAChB;;AAGA,SAAgB,kBAAkB,IAAqB,IAAY,IAA6C;CAC9G,IAAI,GAAG,UAAU,OAAO;EAAE,GAAG;EAAI,GAAG;CAAG;CACvC,MAAM,IAAI,GAAG;CACb,OAAO,KAAK;EAAE,GAAG,EAAE,KAAK,KAAK,EAAE,KAAK;EAAI,GAAG,EAAE,KAAK,KAAK,EAAE,KAAK;CAAG;AACnE;;;AAIA,SAAgB,cAAc,IAAqB,GAAuD;CACxG,IAAI,GAAG,UAAU,OAAO;CACxB,MAAM,CAAC,GAAG,GAAG,GAAG,KAAK,GAAG;CACxB,MAAM,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,CAAC,KAAK;CACtC,OAAO;EAAE,IAAI,IAAI,EAAE,IAAI,IAAI,EAAE,KAAK;EAAG,IAAI,IAAI,EAAE,IAAI,IAAI,EAAE,KAAK;CAAE;AAClE;;;AAIA,SAAgB,WAAW,IAAY,OAAe,IAA+C;CACnG,MAAM,CAAC,GAAG,GAAG,GAAG,KAAK,GAAG;CACxB,MAAM,KAAK,IAAI,MAAM,GAAG,KAAK,IAAI,MAAM;CACvC,MAAM,KAAK,IAAI,MAAM,GAAG,KAAK,IAAI,MAAM;CACvC,MAAM,MAAM,KAAK,IAAI,KAAK,KAAK,KAAK,EAAE;CACtC,IAAI,QAAQ,GAAG,OAAO;EAAE,GAAG;EAAG,GAAG;CAAE;CACnC,OAAO;EAAE,GAAI,KAAK,KAAK,MAAM,IAAI,EAAE,IAAK;EAAK,GAAI,KAAK,KAAK,MAAM,IAAI,EAAE,IAAK;CAAI;AAClF;;AAGA,SAAgB,cACd,GACA,IAC4B;CAC5B,OAAO;EACL,aAAa,IAAI,EAAE,GAAG,EAAE,CAAC;EACzB,aAAa,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;EACnC,aAAa,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM;EAC9C,aAAa,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM;CACtC;AACF"}
1
+ {"version":3,"file":"targetFrame.js","names":[],"sources":["../../../src/core/geometry/targetFrame.ts"],"sourcesContent":["/**\n * The frame selection chrome reads a target in: `poseFrameOf` over the\n * target's bounds, with the helpers the outline, the handles, their hit\n * regions, and the resize and rotate actions all place themselves by.\n */\nimport { applyToPoint, invert, poseFrameOf, pxExtentInFrame, type Affine, type Scale2 } from '@weasel-js/geom';\n\n/** Local↔world maps for a chrome target. Local is the target's unrotated box\n * frame; world is where the node is drawn. */\nexport interface TargetTransform {\n /** No rotation and no `transform`: local is world. */\n identity: boolean;\n /** Target box frame → world. */\n toWorld: Affine;\n /** World → target box frame. `null` when the target's `transform` is\n * singular: it draws nothing, and no world point maps back. */\n toLocal: Affine | null;\n}\n\nconst IDENTITY: Affine = [1, 0, 0, 1, 0, 0];\n\nexport const IDENTITY_TARGET: TargetTransform = { identity: true, toWorld: IDENTITY, toLocal: IDENTITY };\n\n/** The frame `bounds` is drawn in: its `rotation`, its `transform`, or both. */\nexport function targetTransformOf(bounds: unknown): TargetTransform {\n const toWorld = poseFrameOf(bounds);\n if (!toWorld) return IDENTITY_TARGET;\n return { identity: false, toWorld, toLocal: invert(toWorld) };\n}\n\n/** How far the target's x axis is turned from world's, radians. */\nexport function angleOf(xf: TargetTransform): number {\n return xf.identity ? 0 : Math.atan2(xf.toWorld[1], xf.toWorld[0]);\n}\n\n/** Whether the frame only turns the target: no skew, no stretch, no flip. */\nexport function isRigid(xf: TargetTransform): boolean {\n if (xf.identity) return true;\n const [a, b, c, d] = xf.toWorld;\n return Math.abs(a - d) < 1e-9 && Math.abs(b + c) < 1e-9 && Math.abs(a * a + b * b - 1) < 1e-9;\n}\n\n/** local point → world point. */\nexport function localToWorld(xf: TargetTransform, lx: number, ly: number): { x: number; y: number } {\n if (xf.identity) return { x: lx, y: ly };\n const [x, y] = applyToPoint(xf.toWorld, lx, ly);\n return { x, y };\n}\n\n/** world point → local point; `null` where nothing maps back. */\nexport function worldToLocal(xf: TargetTransform, wx: number, wy: number): { x: number; y: number } | null {\n if (xf.identity) return { x: wx, y: wy };\n if (!xf.toLocal) return null;\n const [x, y] = applyToPoint(xf.toLocal, wx, wy);\n return { x, y };\n}\n\n/** A world delta in the target's own axes; `null` where nothing maps back. */\nexport function worldDeltaToLocal(xf: TargetTransform, dx: number, dy: number): { x: number; y: number } | null {\n if (xf.identity) return { x: dx, y: dy };\n const m = xf.toLocal;\n return m && { x: m[0] * dx + m[2] * dy, y: m[1] * dx + m[3] * dy };\n}\n\n/** A local edge's outward normal as a world direction, unnormalized. A normal\n * goes through the inverse transpose, so it stays square to a skewed edge. */\nexport function normalToWorld(xf: TargetTransform, n: { x: number; y: number }): { x: number; y: number } {\n if (xf.identity) return n;\n const [a, b, c, d] = xf.toWorld;\n const k = Math.sign(a * d - b * c) || 1;\n return { x: (d * n.x - b * n.y) * k, y: (a * n.y - c * n.x) * k };\n}\n\n/** `pxExtent` along the target's own axes: how far along each an edge across\n * it must move to move `px` on screen. */\nexport function pxExtentIn(px: number, scale: Scale2, xf: TargetTransform): { x: number; y: number } {\n return pxExtentInFrame(px, scale, xf.toWorld);\n}\n\n/** The target's box corners in world, TL/TR/BR/BL. */\nexport function targetCorners(\n b: { x: number; y: number; width: number; height: number },\n xf: TargetTransform,\n): { x: number; y: number }[] {\n return [\n localToWorld(xf, b.x, b.y),\n localToWorld(xf, b.x + b.width, b.y),\n localToWorld(xf, b.x + b.width, b.y + b.height),\n localToWorld(xf, b.x, b.y + b.height),\n ];\n}\n"],"mappings":";;;;;;;;AAmBA,IAAM,WAAmB;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;AAE1C,IAAa,kBAAmC;CAAE,UAAU;CAAM,SAAS;CAAU,SAAS;AAAS;;AAGvG,SAAgB,kBAAkB,QAAkC;CAClE,MAAM,UAAU,YAAY,MAAM;CAClC,IAAI,CAAC,SAAS,OAAO;CACrB,OAAO;EAAE,UAAU;EAAO;EAAS,SAAS,OAAO,OAAO;CAAE;AAC9D;;AAGA,SAAgB,QAAQ,IAA6B;CACnD,OAAO,GAAG,WAAW,IAAI,KAAK,MAAM,GAAG,QAAQ,IAAI,GAAG,QAAQ,EAAE;AAClE;;AAGA,SAAgB,QAAQ,IAA8B;CACpD,IAAI,GAAG,UAAU,OAAO;CACxB,MAAM,CAAC,GAAG,GAAG,GAAG,KAAK,GAAG;CACxB,OAAO,KAAK,IAAI,IAAI,CAAC,IAAI,QAAQ,KAAK,IAAI,IAAI,CAAC,IAAI,QAAQ,KAAK,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI;AAC3F;;AAGA,SAAgB,aAAa,IAAqB,IAAY,IAAsC;CAClG,IAAI,GAAG,UAAU,OAAO;EAAE,GAAG;EAAI,GAAG;CAAG;CACvC,MAAM,CAAC,GAAG,KAAK,aAAa,GAAG,SAAS,IAAI,EAAE;CAC9C,OAAO;EAAE;EAAG;CAAE;AAChB;;AAGA,SAAgB,aAAa,IAAqB,IAAY,IAA6C;CACzG,IAAI,GAAG,UAAU,OAAO;EAAE,GAAG;EAAI,GAAG;CAAG;CACvC,IAAI,CAAC,GAAG,SAAS,OAAO;CACxB,MAAM,CAAC,GAAG,KAAK,aAAa,GAAG,SAAS,IAAI,EAAE;CAC9C,OAAO;EAAE;EAAG;CAAE;AAChB;;AAGA,SAAgB,kBAAkB,IAAqB,IAAY,IAA6C;CAC9G,IAAI,GAAG,UAAU,OAAO;EAAE,GAAG;EAAI,GAAG;CAAG;CACvC,MAAM,IAAI,GAAG;CACb,OAAO,KAAK;EAAE,GAAG,EAAE,KAAK,KAAK,EAAE,KAAK;EAAI,GAAG,EAAE,KAAK,KAAK,EAAE,KAAK;CAAG;AACnE;;;AAIA,SAAgB,cAAc,IAAqB,GAAuD;CACxG,IAAI,GAAG,UAAU,OAAO;CACxB,MAAM,CAAC,GAAG,GAAG,GAAG,KAAK,GAAG;CACxB,MAAM,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,CAAC,KAAK;CACtC,OAAO;EAAE,IAAI,IAAI,EAAE,IAAI,IAAI,EAAE,KAAK;EAAG,IAAI,IAAI,EAAE,IAAI,IAAI,EAAE,KAAK;CAAE;AAClE;;;AAIA,SAAgB,WAAW,IAAY,OAAe,IAA+C;CACnG,OAAO,gBAAgB,IAAI,OAAO,GAAG,OAAO;AAC9C;;AAGA,SAAgB,cACd,GACA,IAC4B;CAC5B,OAAO;EACL,aAAa,IAAI,EAAE,GAAG,EAAE,CAAC;EACzB,aAAa,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;EACnC,aAAa,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM;EAC9C,aAAa,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM;CACtC;AACF"}
@@ -154,12 +154,13 @@ function drawLayerOnce(layer, data, view, dims, cache, onLayerError, retain) {
154
154
  }
155
155
  if (cmds.length === 0) return [];
156
156
  const effects = layer.effects && layer.effects.length > 0 ? layer.effects : void 0;
157
- const kept = retain && !effects && cache ? retainedId(layer, cache, view, retain) : void 0;
157
+ const kept = retain && cache ? retainedId(layer, cache, view, retain) : void 0;
158
158
  const marked = kept ? { retain: kept } : {};
159
159
  if ((layer.space ?? "world") === "screen") {
160
160
  if (effects) return [{
161
161
  kind: "group",
162
162
  effects,
163
+ ...marked,
163
164
  children: cmds
164
165
  }];
165
166
  return kept ? [{
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","names":[],"sources":["../../../src/core/layers/render.ts"],"sourcesContent":["import { viewToMat3, type DrawCommand } from '@weasel-js/renderer';\nimport type { Effect } from '@weasel-js/renderer';\nimport type { View } from 'core/viewport/view';\nimport { groupMembership, wrapGroup, type LayerGroup } from './layerGroup';\nimport { DEFAULT_LAYER_RETAIN, retainedId, type LayerRetainPolicy } from './retain';\nimport type { RedrawSource } from './redrawSource';\nimport type { Dims, LayerDims } from './dims';\n\nexport type { Dims, LayerDims, LayerGroup, LayerRetainPolicy };\nexport { DEFAULT_LAYER_RETAIN };\nexport { subscribeToSources, type RedrawSource } from './redrawSource';\n\nconst IDENTITY_VIEW: View = { x: 0, y: 0, scale: { x: 1, y: 1 } };\n\n/**\n * What a layer's `draw` threw, and which layer threw it. A throw that escaped\n * the frame loop would take the whole frame with it, every other layer\n * included.\n */\nexport interface LayerDrawFailure {\n layerId: string;\n error: unknown;\n}\n\n/** Default report: name the layer, keep the frame. */\nfunction reportLayerFailure({ layerId, error }: LayerDrawFailure): void {\n console.error(\n `[weasel] layer \"${layerId}\" threw while drawing; it painted nothing this frame.`,\n error,\n );\n}\n\n/** One layer's memoized output, keyed by layer id. Owned by the canvas that\n * calls `drawLayers`, not by `drawLayers` itself — the function is pure. */\nexport type LayerCommandCache = Map<\n string,\n {\n deps: readonly unknown[];\n cmds: DrawCommand[];\n /** Whether the last frame served `cmds` from here rather than drawing. */\n reused?: boolean;\n /** The view the last frame drew under. */\n view?: View;\n }\n>;\n\n/**\n * One layer of a canvas: `draw` turns `data` and the view into draw commands;\n * the other fields control caching, visibility, coordinate space and hit-testing.\n *\n * @template TData - The data object passed to each draw call.\n */\nexport interface RenderLayer<TData> {\n /** Unique identifier used in visibility maps and ordering arrays. When a\n * cache is in use, an id must identify the same logical layer across\n * frames — reusing it for a different layer can serve cross-layer commands. */\n id: string;\n /** Human-readable name for UI toggles. */\n label: string;\n /**\n * Emit a DrawCommand tree, in the coordinates `space` names. A world-space\n * layer must NOT apply the view transform itself.\n */\n draw: (data: TData, view: View, dims: LayerDims) => DrawCommand[];\n /**\n * Optional cache key. When present and a `LayerCommandCache` is supplied to\n * `drawLayers`, the layer's previous `DrawCommand[]` is reused as long as\n * every entry is `Object.is`-equal to the previous call's. A layer with no\n * `deps` rebuilds on every frame.\n *\n * **The returned commands must be treated as immutable.** A cached tree is\n * handed to the renderer again on later frames, so mutating a tree you\n * previously returned corrupts the cache silently rather than erroring.\n *\n * **Screen-space layers are not protected against a stale `view`/`dims`\n * the way world-space layers are** (see `space` below) — include them in\n * `deps` if `draw` reads them.\n */\n deps?: (data: TData, view: View, dims: LayerDims) => readonly unknown[];\n /** Whether the layer is shown when no explicit visibility entry exists.\n * Defaults to `true` when absent. */\n defaultVisible?: boolean;\n /** When true, the layer is always drawn regardless of the visibility map. */\n alwaysOn?: boolean;\n /**\n * Coordinate space the layer draws in.\n *\n * - `'world'` (default): the layer's `draw` returns world-space commands;\n * `drawLayers` wraps them in a `kind: 'group'` with `viewToMat3(view)`\n * automatically.\n * - `'screen'`: the layer's `draw` returns screen-space (CSS-pixel)\n * commands; `drawLayers` passes them through unchanged. World-anchored\n * chrome inside a screen-space layer must call `worldToScreen` or wrap\n * the relevant subset with `viewToMat3(view)` manually.\n */\n space?: 'world' | 'screen';\n /**\n * Where the layer learns that state `draw` reads, outside the scene and the\n * props, has changed. The canvas painting it subscribes while the layer is\n * mounted and repaints on every notification.\n */\n subscribe?: RedrawSource['subscribe'];\n /**\n * Full-screen passes run over this layer's own pixels before it joins the\n * frame. See `GroupDrawCommand.effects` for what a pass may read, and\n * {@link LayerGroup} to run one chain over several layers at once.\n */\n effects?: readonly Effect[];\n /**\n * Whether the renderer may keep this layer's pixels between frames and\n * composite them as one quad instead of drawing its commands again — see\n * {@link LayerRetainPolicy} for when `drawLayers` does. `false` never\n * retains it, `true` retains it whatever its size; either way only a layer\n * with `deps`, drawn through a cache, and without `effects`.\n */\n retain?: boolean;\n /**\n * Optional hit-test for **consumer-attached** layers.\n *\n * Only layers registered through `CanvasExtensionApi.registerLayer` are\n * hit-tested, last-registered-first, ahead of the kit's own selection\n * chrome. First non-null result wins; null means \"I don't claim this hit,\n * try the next layer.\"\n *\n * Layers that reach the draw stack some other way — a `Tool.overlay`, an\n * entry in the `layers` map — are painted but never hit-tested, so defining\n * `hitTest` on one has no effect.\n *\n * Coordinates are world-space.\n */\n hitTest?: (\n worldX: number,\n worldY: number,\n data: TData,\n view: View,\n dims: Dims,\n /** Chrome-caps visibility predicate. When supplied, the layer must\n * not return a hit from any chrome element whose id reports\n * `false`. Absent → every element is hittable. */\n isVisible?: (id: string) => boolean,\n ) => import('../../affordances/types').LayerHit | null;\n /**\n * Called on every pointermove when no gesture is currently captured, so a\n * layer can track hover state. Coords are world-space, and the layer does\n * its own throttling.\n */\n onUncapturedMove?: (\n worldX: number,\n worldY: number,\n evt: PointerEvent,\n view: View,\n dims: Dims,\n ) => void;\n /** Called when the cursor leaves the canvas element. */\n onUncapturedLeave?: () => void;\n}\n\n/** Where one layer's commands landed in {@link drawLayers}' output,\n * `[start, end)`, and the CPU milliseconds its `draw` took. A layer group's\n * run is one span under the group's id. */\nexport interface LayerDrawSpan {\n id: string;\n start: number;\n end: number;\n buildMs: number;\n}\n\nfunction clockMs(): number {\n return typeof performance !== 'undefined' ? performance.now() : Date.now();\n}\n\n/**\n * Walk visible layers and concatenate their emitted DrawCommand arrays into\n * one flat list, ready to feed to `WeaselRenderer.render(commands)`.\n *\n * A layer whose `draw` throws is dropped for the frame and reported through\n * `onLayerError` — the rest of the frame still paints.\n *\n * `groups` brackets runs of consecutive layers so they composite as one — see\n * `LayerGroup`. `spans`, when passed, receives one {@link LayerDrawSpan} per\n * layer drawn. `retain` decides which unchanged layers the renderer keeps\n * as pixels between frames; `false` keeps none.\n */\nexport function drawLayers<TData>(\n layers: RenderLayer<TData>[],\n data: TData,\n visibility: Record<string, boolean>,\n order: string[] | undefined,\n view: View | undefined,\n dims: LayerDims,\n cache?: LayerCommandCache,\n onLayerError: (failure: LayerDrawFailure) => void = reportLayerFailure,\n groups?: readonly LayerGroup[],\n spans?: LayerDrawSpan[],\n retain: LayerRetainPolicy | false = DEFAULT_LAYER_RETAIN,\n): DrawCommand[] {\n const layerById = new Map(layers.map((l) => [l.id, l]));\n const v = view ?? IDENTITY_VIEW;\n const out: DrawCommand[] = [];\n\n if (cache) {\n for (const id of [...cache.keys()]) {\n if (!layerById.has(id)) cache.delete(id);\n }\n }\n\n const groupOf = groupMembership(groups, layerById.keys());\n const bracketed = new Set<LayerGroup>();\n let run: { group: LayerGroup; children: DrawCommand[]; buildMs: number } | null = null;\n\n const closeRun = () => {\n if (!run) return;\n const start = out.length;\n const t0 = spans ? clockMs() : 0;\n for (const c of wrapGroup(run.group, run.children, v, dims)) out.push(c);\n spans?.push({ id: run.group.id, start, end: out.length, buildMs: run.buildMs + clockMs() - t0 });\n run = null;\n };\n\n for (const layer of paintedLayers(layers, visibility, order)) {\n const t0 = spans ? clockMs() : 0;\n const cmds = drawLayerOnce(layer, data, v, dims, cache, onLayerError, retain);\n const buildMs = spans ? clockMs() - t0 : 0;\n // A layer that painted nothing neither joins a run nor breaks one.\n if (cmds.length === 0) {\n spans?.push({ id: layer.id, start: out.length, end: out.length, buildMs });\n continue;\n }\n\n const group = groupOf?.get(layer.id);\n if (run && run.group !== group) closeRun();\n if (!group) {\n const start = out.length;\n for (const c of cmds) out.push(c);\n spans?.push({ id: layer.id, start, end: out.length, buildMs });\n continue;\n }\n if (!run) {\n if (bracketed.has(group)) {\n console.warn(\n `[weasel] layer group \"${group.id}\" is not one run — another layer is drawn ` +\n 'between its members, so it composites as more than one pass over more than ' +\n 'one buffer. Move its members together in the layer order.',\n );\n }\n bracketed.add(group);\n run = { group, children: [], buildMs: 0 };\n }\n run.buildMs += buildMs;\n for (const c of cmds) run.children.push(c);\n }\n closeRun();\n\n return out;\n}\n\n/**\n * Resolve one layer's visibility: `alwaysOn` wins, then an explicit entry in\n * `visibility`, then `defaultVisible`, defaulting to shown.\n */\nexport function isLayerVisible<TData>(\n layer: RenderLayer<TData>,\n visibility: Record<string, boolean>,\n): boolean {\n if (layer.alwaysOn) return true;\n if (layer.id in visibility) return visibility[layer.id]!;\n return layer.defaultVisible ?? true;\n}\n\n/**\n * Does this layer reach the screen at all — membership of\n * {@link paintedLayers}, asked of one layer.\n *\n * **A listed `order` is the whole list**, so omission from it drops a layer\n * that `visibility` would have shown, and `alwaysOn` does not rescue it.\n * Hit-testing asks this, not `isLayerVisible`.\n */\nexport function isLayerPainted<TData>(\n layer: RenderLayer<TData>,\n visibility: Record<string, boolean>,\n order: readonly string[] | undefined,\n): boolean {\n if (order && !order.includes(layer.id)) return false;\n return isLayerVisible(layer, visibility);\n}\n\n/**\n * The layers that reach the screen, in the order they are drawn: `order`\n * sequences them and drops what it leaves out, then `visibility` drops what is\n * hidden. What `drawLayers` paints, and what a view narrows its stack with.\n */\nexport function paintedLayers<TData>(\n layers: readonly RenderLayer<TData>[],\n visibility: Record<string, boolean>,\n order: readonly string[] | undefined,\n): RenderLayer<TData>[] {\n if (!order) return layers.filter((l) => isLayerVisible(l, visibility));\n const byId = new Map(layers.map((l) => [l.id, l]));\n return order\n .map((id) => byId.get(id))\n .filter((l): l is RenderLayer<TData> => l !== undefined && isLayerVisible(l, visibility));\n}\n\n/**\n * Draw one layer and put its commands in the space its `space` declares:\n * world-space output wrapped in a `viewToMat3(view)` group, screen-space\n * output passed through.\n *\n * Anything rendering layers through a view goes through here.\n *\n * A `draw` that throws yields no commands, and `onLayerError` is told which\n * layer it was. The layer's cache entry goes with it, so the next frame is a\n * real re-attempt.\n */\nexport function drawOneLayer<TData>(\n layer: RenderLayer<TData>,\n data: TData,\n view: View,\n dims: LayerDims,\n cache?: LayerCommandCache,\n onLayerError: (failure: LayerDrawFailure) => void = reportLayerFailure,\n): DrawCommand[] {\n return drawLayerOnce(layer, data, view, dims, cache, onLayerError, false);\n}\n\nfunction drawLayerOnce<TData>(\n layer: RenderLayer<TData>,\n data: TData,\n view: View,\n dims: LayerDims,\n cache: LayerCommandCache | undefined,\n onLayerError: (failure: LayerDrawFailure) => void,\n retain: LayerRetainPolicy | false,\n): DrawCommand[] {\n let cmds: DrawCommand[];\n try {\n cmds = layerCommands(layer, data, view, dims, cache);\n } catch (error) {\n cache?.delete(layer.id);\n onLayerError({ layerId: layer.id, error });\n return [];\n }\n if (cmds.length === 0) return [];\n const effects = layer.effects && layer.effects.length > 0 ? layer.effects : undefined;\n const kept = retain && !effects && cache ? retainedId(layer, cache, view, retain) : undefined;\n const marked = kept ? { retain: kept } : {};\n if ((layer.space ?? 'world') === 'screen') {\n if (effects) return [{ kind: 'group', effects, children: cmds }];\n return kept ? [{ kind: 'group', ...marked, children: cmds }] : cmds;\n }\n return [{ kind: 'group', transform: viewToMat3(view), ...(effects ? { effects } : {}), ...marked, children: cmds }];\n}\n\n/**\n * One layer's own commands, from cache when its deps are unchanged.\n *\n * What is cached is the layer's raw output, before the space wrap above. The\n * wrap is a function of `view`, and a layer whose content does not depend on\n * the camera is entitled to leave `view` out of its deps — caching the wrapped\n * group would then serve a stale transform the moment the camera moved.\n */\nfunction layerCommands<TData>(\n layer: RenderLayer<TData>,\n data: TData,\n view: View,\n dims: LayerDims,\n cache: LayerCommandCache | undefined,\n): DrawCommand[] {\n if (!cache || !layer.deps) return layer.draw(data, view, dims);\n\n const deps = layer.deps(data, view, dims);\n const entry = cache.get(layer.id);\n if (entry && depsUnchanged(entry.deps, deps)) {\n entry.reused = true;\n return entry.cmds;\n }\n\n const cmds = layer.draw(data, view, dims);\n cache.set(layer.id, { deps, cmds, reused: false, view: entry?.view });\n return cmds;\n}\n\n/** Whether `b` matches `a` entry for entry under `Object.is` — how\n * `RenderLayer.deps` is compared, and any cache keyed the same way. */\nexport function depsUnchanged(a: readonly unknown[], b: readonly unknown[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!Object.is(a[i], b[i])) return false;\n }\n return true;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"render.js","names":[],"sources":["../../../src/core/layers/render.ts"],"sourcesContent":["import { viewToMat3, type DrawCommand } from '@weasel-js/renderer';\nimport type { Effect } from '@weasel-js/renderer';\nimport type { View } from 'core/viewport/view';\nimport { groupMembership, wrapGroup, type LayerGroup } from './layerGroup';\nimport { DEFAULT_LAYER_RETAIN, retainedId, type LayerRetainPolicy } from './retain';\nimport type { RedrawSource } from './redrawSource';\nimport type { Dims, LayerDims } from './dims';\n\nexport type { Dims, LayerDims, LayerGroup, LayerRetainPolicy };\nexport { DEFAULT_LAYER_RETAIN };\nexport { subscribeToSources, type RedrawSource } from './redrawSource';\n\nconst IDENTITY_VIEW: View = { x: 0, y: 0, scale: { x: 1, y: 1 } };\n\n/**\n * What a layer's `draw` threw, and which layer threw it. A throw that escaped\n * the frame loop would take the whole frame with it, every other layer\n * included.\n */\nexport interface LayerDrawFailure {\n layerId: string;\n error: unknown;\n}\n\n/** Default report: name the layer, keep the frame. */\nfunction reportLayerFailure({ layerId, error }: LayerDrawFailure): void {\n console.error(\n `[weasel] layer \"${layerId}\" threw while drawing; it painted nothing this frame.`,\n error,\n );\n}\n\n/** One layer's memoized output, keyed by layer id. Owned by the canvas that\n * calls `drawLayers`, not by `drawLayers` itself — the function is pure. */\nexport type LayerCommandCache = Map<\n string,\n {\n deps: readonly unknown[];\n cmds: DrawCommand[];\n /** Whether the last frame served `cmds` from here rather than drawing. */\n reused?: boolean;\n /** The view the last frame drew under. */\n view?: View;\n }\n>;\n\n/**\n * One layer of a canvas: `draw` turns `data` and the view into draw commands;\n * the other fields control caching, visibility, coordinate space and hit-testing.\n *\n * @template TData - The data object passed to each draw call.\n */\nexport interface RenderLayer<TData> {\n /** Unique identifier used in visibility maps and ordering arrays. When a\n * cache is in use, an id must identify the same logical layer across\n * frames — reusing it for a different layer can serve cross-layer commands. */\n id: string;\n /** Human-readable name for UI toggles. */\n label: string;\n /**\n * Emit a DrawCommand tree, in the coordinates `space` names. A world-space\n * layer must NOT apply the view transform itself.\n */\n draw: (data: TData, view: View, dims: LayerDims) => DrawCommand[];\n /**\n * Optional cache key. When present and a `LayerCommandCache` is supplied to\n * `drawLayers`, the layer's previous `DrawCommand[]` is reused as long as\n * every entry is `Object.is`-equal to the previous call's. A layer with no\n * `deps` rebuilds on every frame.\n *\n * **The returned commands must be treated as immutable.** A cached tree is\n * handed to the renderer again on later frames, so mutating a tree you\n * previously returned corrupts the cache silently rather than erroring.\n *\n * **Screen-space layers are not protected against a stale `view`/`dims`\n * the way world-space layers are** (see `space` below) — include them in\n * `deps` if `draw` reads them.\n */\n deps?: (data: TData, view: View, dims: LayerDims) => readonly unknown[];\n /** Whether the layer is shown when no explicit visibility entry exists.\n * Defaults to `true` when absent. */\n defaultVisible?: boolean;\n /** When true, the layer is always drawn regardless of the visibility map. */\n alwaysOn?: boolean;\n /**\n * Coordinate space the layer draws in.\n *\n * - `'world'` (default): the layer's `draw` returns world-space commands;\n * `drawLayers` wraps them in a `kind: 'group'` with `viewToMat3(view)`\n * automatically.\n * - `'screen'`: the layer's `draw` returns screen-space (CSS-pixel)\n * commands; `drawLayers` passes them through unchanged. World-anchored\n * chrome inside a screen-space layer must call `worldToScreen` or wrap\n * the relevant subset with `viewToMat3(view)` manually.\n */\n space?: 'world' | 'screen';\n /**\n * Where the layer learns that state `draw` reads, outside the scene and the\n * props, has changed. The canvas painting it subscribes while the layer is\n * mounted and repaints on every notification.\n */\n subscribe?: RedrawSource['subscribe'];\n /**\n * Full-screen passes run over this layer's own pixels before it joins the\n * frame. See `GroupDrawCommand.effects` for what a pass may read, and\n * {@link LayerGroup} to run one chain over several layers at once.\n */\n effects?: readonly Effect[];\n /**\n * Whether the renderer may keep this layer's pixels between frames and\n * composite them as one quad instead of drawing its commands again — see\n * {@link LayerRetainPolicy} for when `drawLayers` does. `false` never\n * retains it, `true` retains it whatever its size; either way only a layer\n * with `deps`, drawn through a cache, and without `effects`.\n */\n retain?: boolean;\n /**\n * Optional hit-test for **consumer-attached** layers.\n *\n * Only layers registered through `CanvasExtensionApi.registerLayer` are\n * hit-tested, last-registered-first, ahead of the kit's own selection\n * chrome. First non-null result wins; null means \"I don't claim this hit,\n * try the next layer.\"\n *\n * Layers that reach the draw stack some other way — a `Tool.overlay`, an\n * entry in the `layers` map — are painted but never hit-tested, so defining\n * `hitTest` on one has no effect.\n *\n * Coordinates are world-space.\n */\n hitTest?: (\n worldX: number,\n worldY: number,\n data: TData,\n view: View,\n dims: Dims,\n /** Chrome-caps visibility predicate. When supplied, the layer must\n * not return a hit from any chrome element whose id reports\n * `false`. Absent → every element is hittable. */\n isVisible?: (id: string) => boolean,\n ) => import('../../affordances/types').LayerHit | null;\n /**\n * Called on every pointermove when no gesture is currently captured, so a\n * layer can track hover state. Coords are world-space, and the layer does\n * its own throttling.\n */\n onUncapturedMove?: (\n worldX: number,\n worldY: number,\n evt: PointerEvent,\n view: View,\n dims: Dims,\n ) => void;\n /** Called when the cursor leaves the canvas element. */\n onUncapturedLeave?: () => void;\n}\n\n/** Where one layer's commands landed in {@link drawLayers}' output,\n * `[start, end)`, and the CPU milliseconds its `draw` took. A layer group's\n * run is one span under the group's id. */\nexport interface LayerDrawSpan {\n id: string;\n start: number;\n end: number;\n buildMs: number;\n}\n\nfunction clockMs(): number {\n return typeof performance !== 'undefined' ? performance.now() : Date.now();\n}\n\n/**\n * Walk visible layers and concatenate their emitted DrawCommand arrays into\n * one flat list, ready to feed to `WeaselRenderer.render(commands)`.\n *\n * A layer whose `draw` throws is dropped for the frame and reported through\n * `onLayerError` — the rest of the frame still paints.\n *\n * `groups` brackets runs of consecutive layers so they composite as one — see\n * `LayerGroup`. `spans`, when passed, receives one {@link LayerDrawSpan} per\n * layer drawn. `retain` decides which unchanged layers the renderer keeps\n * as pixels between frames; `false` keeps none.\n */\nexport function drawLayers<TData>(\n layers: RenderLayer<TData>[],\n data: TData,\n visibility: Record<string, boolean>,\n order: string[] | undefined,\n view: View | undefined,\n dims: LayerDims,\n cache?: LayerCommandCache,\n onLayerError: (failure: LayerDrawFailure) => void = reportLayerFailure,\n groups?: readonly LayerGroup[],\n spans?: LayerDrawSpan[],\n retain: LayerRetainPolicy | false = DEFAULT_LAYER_RETAIN,\n): DrawCommand[] {\n const layerById = new Map(layers.map((l) => [l.id, l]));\n const v = view ?? IDENTITY_VIEW;\n const out: DrawCommand[] = [];\n\n if (cache) {\n for (const id of [...cache.keys()]) {\n if (!layerById.has(id)) cache.delete(id);\n }\n }\n\n const groupOf = groupMembership(groups, layerById.keys());\n const bracketed = new Set<LayerGroup>();\n let run: { group: LayerGroup; children: DrawCommand[]; buildMs: number } | null = null;\n\n const closeRun = () => {\n if (!run) return;\n const start = out.length;\n const t0 = spans ? clockMs() : 0;\n for (const c of wrapGroup(run.group, run.children, v, dims)) out.push(c);\n spans?.push({ id: run.group.id, start, end: out.length, buildMs: run.buildMs + clockMs() - t0 });\n run = null;\n };\n\n for (const layer of paintedLayers(layers, visibility, order)) {\n const t0 = spans ? clockMs() : 0;\n const cmds = drawLayerOnce(layer, data, v, dims, cache, onLayerError, retain);\n const buildMs = spans ? clockMs() - t0 : 0;\n // A layer that painted nothing neither joins a run nor breaks one.\n if (cmds.length === 0) {\n spans?.push({ id: layer.id, start: out.length, end: out.length, buildMs });\n continue;\n }\n\n const group = groupOf?.get(layer.id);\n if (run && run.group !== group) closeRun();\n if (!group) {\n const start = out.length;\n for (const c of cmds) out.push(c);\n spans?.push({ id: layer.id, start, end: out.length, buildMs });\n continue;\n }\n if (!run) {\n if (bracketed.has(group)) {\n console.warn(\n `[weasel] layer group \"${group.id}\" is not one run — another layer is drawn ` +\n 'between its members, so it composites as more than one pass over more than ' +\n 'one buffer. Move its members together in the layer order.',\n );\n }\n bracketed.add(group);\n run = { group, children: [], buildMs: 0 };\n }\n run.buildMs += buildMs;\n for (const c of cmds) run.children.push(c);\n }\n closeRun();\n\n return out;\n}\n\n/**\n * Resolve one layer's visibility: `alwaysOn` wins, then an explicit entry in\n * `visibility`, then `defaultVisible`, defaulting to shown.\n */\nexport function isLayerVisible<TData>(\n layer: RenderLayer<TData>,\n visibility: Record<string, boolean>,\n): boolean {\n if (layer.alwaysOn) return true;\n if (layer.id in visibility) return visibility[layer.id]!;\n return layer.defaultVisible ?? true;\n}\n\n/**\n * Does this layer reach the screen at all — membership of\n * {@link paintedLayers}, asked of one layer.\n *\n * **A listed `order` is the whole list**, so omission from it drops a layer\n * that `visibility` would have shown, and `alwaysOn` does not rescue it.\n * Hit-testing asks this, not `isLayerVisible`.\n */\nexport function isLayerPainted<TData>(\n layer: RenderLayer<TData>,\n visibility: Record<string, boolean>,\n order: readonly string[] | undefined,\n): boolean {\n if (order && !order.includes(layer.id)) return false;\n return isLayerVisible(layer, visibility);\n}\n\n/**\n * The layers that reach the screen, in the order they are drawn: `order`\n * sequences them and drops what it leaves out, then `visibility` drops what is\n * hidden. What `drawLayers` paints, and what a view narrows its stack with.\n */\nexport function paintedLayers<TData>(\n layers: readonly RenderLayer<TData>[],\n visibility: Record<string, boolean>,\n order: readonly string[] | undefined,\n): RenderLayer<TData>[] {\n if (!order) return layers.filter((l) => isLayerVisible(l, visibility));\n const byId = new Map(layers.map((l) => [l.id, l]));\n return order\n .map((id) => byId.get(id))\n .filter((l): l is RenderLayer<TData> => l !== undefined && isLayerVisible(l, visibility));\n}\n\n/**\n * Draw one layer and put its commands in the space its `space` declares:\n * world-space output wrapped in a `viewToMat3(view)` group, screen-space\n * output passed through.\n *\n * Anything rendering layers through a view goes through here.\n *\n * A `draw` that throws yields no commands, and `onLayerError` is told which\n * layer it was. The layer's cache entry goes with it, so the next frame is a\n * real re-attempt.\n */\nexport function drawOneLayer<TData>(\n layer: RenderLayer<TData>,\n data: TData,\n view: View,\n dims: LayerDims,\n cache?: LayerCommandCache,\n onLayerError: (failure: LayerDrawFailure) => void = reportLayerFailure,\n): DrawCommand[] {\n return drawLayerOnce(layer, data, view, dims, cache, onLayerError, false);\n}\n\nfunction drawLayerOnce<TData>(\n layer: RenderLayer<TData>,\n data: TData,\n view: View,\n dims: LayerDims,\n cache: LayerCommandCache | undefined,\n onLayerError: (failure: LayerDrawFailure) => void,\n retain: LayerRetainPolicy | false,\n): DrawCommand[] {\n let cmds: DrawCommand[];\n try {\n cmds = layerCommands(layer, data, view, dims, cache);\n } catch (error) {\n cache?.delete(layer.id);\n onLayerError({ layerId: layer.id, error });\n return [];\n }\n if (cmds.length === 0) return [];\n const effects = layer.effects && layer.effects.length > 0 ? layer.effects : undefined;\n const kept = retain && cache ? retainedId(layer, cache, view, retain) : undefined;\n const marked = kept ? { retain: kept } : {};\n if ((layer.space ?? 'world') === 'screen') {\n if (effects) return [{ kind: 'group', effects, ...marked, children: cmds }];\n return kept ? [{ kind: 'group', ...marked, children: cmds }] : cmds;\n }\n return [{ kind: 'group', transform: viewToMat3(view), ...(effects ? { effects } : {}), ...marked, children: cmds }];\n}\n\n/**\n * One layer's own commands, from cache when its deps are unchanged.\n *\n * What is cached is the layer's raw output, before the space wrap above. The\n * wrap is a function of `view`, and a layer whose content does not depend on\n * the camera is entitled to leave `view` out of its deps — caching the wrapped\n * group would then serve a stale transform the moment the camera moved.\n */\nfunction layerCommands<TData>(\n layer: RenderLayer<TData>,\n data: TData,\n view: View,\n dims: LayerDims,\n cache: LayerCommandCache | undefined,\n): DrawCommand[] {\n if (!cache || !layer.deps) return layer.draw(data, view, dims);\n\n const deps = layer.deps(data, view, dims);\n const entry = cache.get(layer.id);\n if (entry && depsUnchanged(entry.deps, deps)) {\n entry.reused = true;\n return entry.cmds;\n }\n\n const cmds = layer.draw(data, view, dims);\n cache.set(layer.id, { deps, cmds, reused: false, view: entry?.view });\n return cmds;\n}\n\n/** Whether `b` matches `a` entry for entry under `Object.is` — how\n * `RenderLayer.deps` is compared, and any cache keyed the same way. */\nexport function depsUnchanged(a: readonly unknown[], b: readonly unknown[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!Object.is(a[i], b[i])) return false;\n }\n return true;\n}\n"],"mappings":";;;;;;AAYA,IAAM,gBAAsB;CAAE,GAAG;CAAG,GAAG;CAAG,OAAO;EAAE,GAAG;EAAG,GAAG;CAAE;AAAE;;AAahE,SAAS,mBAAmB,EAAE,SAAS,SAAiC;CACtE,QAAQ,MACN,mBAAmB,QAAQ,wDAC3B,KACF;AACF;AAyIA,SAAS,UAAkB;CACzB,OAAO,OAAO,gBAAgB,cAAc,YAAY,IAAI,IAAI,KAAK,IAAI;AAC3E;;;;;;;;;;;;;AAcA,SAAgB,WACd,QACA,MACA,YACA,OACA,MACA,MACA,OACA,eAAoD,oBACpD,QACA,OACA,SAAoC,sBACrB;CACf,MAAM,YAAY,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CACtD,MAAM,IAAI,QAAQ;CAClB,MAAM,MAAqB,CAAC;CAE5B,IAAI,OACF;OAAK,MAAM,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,GAC/B,IAAI,CAAC,UAAU,IAAI,EAAE,GAAG,MAAM,OAAO,EAAE;CACzC;CAGF,MAAM,UAAU,gBAAgB,QAAQ,UAAU,KAAK,CAAC;CACxD,MAAM,4BAAY,IAAI,IAAgB;CACtC,IAAI,MAA8E;CAElF,MAAM,iBAAiB;EACrB,IAAI,CAAC,KAAK;EACV,MAAM,QAAQ,IAAI;EAClB,MAAM,KAAK,QAAQ,QAAQ,IAAI;EAC/B,KAAK,MAAM,KAAK,UAAU,IAAI,OAAO,IAAI,UAAU,GAAG,IAAI,GAAG,IAAI,KAAK,CAAC;EACvE,OAAO,KAAK;GAAE,IAAI,IAAI,MAAM;GAAI;GAAO,KAAK,IAAI;GAAQ,SAAS,IAAI,UAAU,QAAQ,IAAI;EAAG,CAAC;EAC/F,MAAM;CACR;CAEA,KAAK,MAAM,SAAS,cAAc,QAAQ,YAAY,KAAK,GAAG;EAC5D,MAAM,KAAK,QAAQ,QAAQ,IAAI;EAC/B,MAAM,OAAO,cAAc,OAAO,MAAM,GAAG,MAAM,OAAO,cAAc,MAAM;EAC5E,MAAM,UAAU,QAAQ,QAAQ,IAAI,KAAK;EAEzC,IAAI,KAAK,WAAW,GAAG;GACrB,OAAO,KAAK;IAAE,IAAI,MAAM;IAAI,OAAO,IAAI;IAAQ,KAAK,IAAI;IAAQ;GAAQ,CAAC;GACzE;EACF;EAEA,MAAM,QAAQ,SAAS,IAAI,MAAM,EAAE;EACnC,IAAI,OAAO,IAAI,UAAU,OAAO,SAAS;EACzC,IAAI,CAAC,OAAO;GACV,MAAM,QAAQ,IAAI;GAClB,KAAK,MAAM,KAAK,MAAM,IAAI,KAAK,CAAC;GAChC,OAAO,KAAK;IAAE,IAAI,MAAM;IAAI;IAAO,KAAK,IAAI;IAAQ;GAAQ,CAAC;GAC7D;EACF;EACA,IAAI,CAAC,KAAK;GACR,IAAI,UAAU,IAAI,KAAK,GACrB,QAAQ,KACN,yBAAyB,MAAM,GAAG,+KAGpC;GAEF,UAAU,IAAI,KAAK;GACnB,MAAM;IAAE;IAAO,UAAU,CAAC;IAAG,SAAS;GAAE;EAC1C;EACA,IAAI,WAAW;EACf,KAAK,MAAM,KAAK,MAAM,IAAI,SAAS,KAAK,CAAC;CAC3C;CACA,SAAS;CAET,OAAO;AACT;;;;;AAMA,SAAgB,eACd,OACA,YACS;CACT,IAAI,MAAM,UAAU,OAAO;CAC3B,IAAI,MAAM,MAAM,YAAY,OAAO,WAAW,MAAM;CACpD,OAAO,MAAM,kBAAkB;AACjC;;;;;;;;;AAUA,SAAgB,eACd,OACA,YACA,OACS;CACT,IAAI,SAAS,CAAC,MAAM,SAAS,MAAM,EAAE,GAAG,OAAO;CAC/C,OAAO,eAAe,OAAO,UAAU;AACzC;;;;;;AAOA,SAAgB,cACd,QACA,YACA,OACsB;CACtB,IAAI,CAAC,OAAO,OAAO,OAAO,QAAQ,MAAM,eAAe,GAAG,UAAU,CAAC;CACrE,MAAM,OAAO,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CACjD,OAAO,MACJ,KAAK,OAAO,KAAK,IAAI,EAAE,CAAC,CAAC,CACzB,QAAQ,MAA+B,MAAM,UAAa,eAAe,GAAG,UAAU,CAAC;AAC5F;;;;;;;;;;;;AAaA,SAAgB,aACd,OACA,MACA,MACA,MACA,OACA,eAAoD,oBACrC;CACf,OAAO,cAAc,OAAO,MAAM,MAAM,MAAM,OAAO,cAAc,KAAK;AAC1E;AAEA,SAAS,cACP,OACA,MACA,MACA,MACA,OACA,cACA,QACe;CACf,IAAI;CACJ,IAAI;EACF,OAAO,cAAc,OAAO,MAAM,MAAM,MAAM,KAAK;CACrD,SAAS,OAAO;EACd,OAAO,OAAO,MAAM,EAAE;EACtB,aAAa;GAAE,SAAS,MAAM;GAAI;EAAM,CAAC;EACzC,OAAO,CAAC;CACV;CACA,IAAI,KAAK,WAAW,GAAG,OAAO,CAAC;CAC/B,MAAM,UAAU,MAAM,WAAW,MAAM,QAAQ,SAAS,IAAI,MAAM,UAAU;CAC5E,MAAM,OAAO,UAAU,QAAQ,WAAW,OAAO,OAAO,MAAM,MAAM,IAAI;CACxE,MAAM,SAAS,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;CAC1C,KAAK,MAAM,SAAS,aAAa,UAAU;EACzC,IAAI,SAAS,OAAO,CAAC;GAAE,MAAM;GAAS;GAAS,GAAG;GAAQ,UAAU;EAAK,CAAC;EAC1E,OAAO,OAAO,CAAC;GAAE,MAAM;GAAS,GAAG;GAAQ,UAAU;EAAK,CAAC,IAAI;CACjE;CACA,OAAO,CAAC;EAAE,MAAM;EAAS,WAAW,WAAW,IAAI;EAAG,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;EAAI,GAAG;EAAQ,UAAU;CAAK,CAAC;AACpH;;;;;;;;;AAUA,SAAS,cACP,OACA,MACA,MACA,MACA,OACe;CACf,IAAI,CAAC,SAAS,CAAC,MAAM,MAAM,OAAO,MAAM,KAAK,MAAM,MAAM,IAAI;CAE7D,MAAM,OAAO,MAAM,KAAK,MAAM,MAAM,IAAI;CACxC,MAAM,QAAQ,MAAM,IAAI,MAAM,EAAE;CAChC,IAAI,SAAS,cAAc,MAAM,MAAM,IAAI,GAAG;EAC5C,MAAM,SAAS;EACf,OAAO,MAAM;CACf;CAEA,MAAM,OAAO,MAAM,KAAK,MAAM,MAAM,IAAI;CACxC,MAAM,IAAI,MAAM,IAAI;EAAE;EAAM;EAAM,QAAQ;EAAO,MAAM,OAAO;CAAK,CAAC;CACpE,OAAO;AACT;;;AAIA,SAAgB,cAAc,GAAuB,GAAgC;CACnF,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;CAClC,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAC5B,IAAI,CAAC,OAAO,GAAG,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO;CAErC,OAAO;AACT"}
@@ -13,14 +13,16 @@ function countDrawCommands(cmds) {
13
13
  counts.set(cmds, n);
14
14
  return n;
15
15
  }
16
- function sameView(a, b) {
17
- return a !== void 0 && a.x === b.x && a.y === b.y && a.scale.x === b.scale.x && a.scale.y === b.scale.y;
16
+ /** Whether the view held since the frame before, or only panned: a renderer
17
+ * can move a kept surface with a pan, and decides itself when it cannot. */
18
+ function sameZoom(a, b) {
19
+ return a !== void 0 && a.scale.x === b.scale.x && a.scale.y === b.scale.y;
18
20
  }
19
21
  /** The layer's id when the renderer should keep its pixels this frame. */
20
22
  function retainedId(layer, cache, view, policy) {
21
23
  const entry = cache.get(layer.id);
22
24
  if (!entry || !layer.deps) return void 0;
23
- const viewHeld = (layer.space ?? "world") === "screen" || sameView(entry.view, view);
25
+ const viewHeld = (layer.space ?? "world") === "screen" || sameZoom(entry.view, view);
24
26
  entry.view = {
25
27
  x: view.x,
26
28
  y: view.y,
@@ -1 +1 @@
1
- {"version":3,"file":"retain.js","names":[],"sources":["../../../src/core/layers/retain.ts"],"sourcesContent":["import type { DrawCommand } from '@weasel-js/renderer';\nimport type { View } from 'core/viewport/view';\nimport type { LayerCommandCache, RenderLayer } from './render';\n\n/**\n * When `drawLayers` asks the renderer to keep a layer's pixels between frames\n * (`GroupDrawCommand.retain`) rather than submit its commands again.\n *\n * A layer is retained once its `deps` have held for a frame, the view has held\n * since the frame before (for a world-space layer, whose pixels move with\n * it), and it holds at least `minCommands` drawing commands. A layer changing\n * every frame is never retained: drawing it into its surface and compositing\n * that costs more than drawing it.\n */\nexport interface LayerRetainPolicy {\n minCommands: number;\n}\n\n/** Measured on 1280x800 at dpr 2: an unchanged layer composites cheaper than\n * it submits from about 130 to 250 commands, depending on the layer count. */\nexport const DEFAULT_LAYER_RETAIN: LayerRetainPolicy = { minCommands: 250 };\n\nconst counts = new WeakMap<readonly DrawCommand[], number>();\n\n/** Drawing commands in a tree, groups left out. Memoized by array, which a\n * layer's cache serves unchanged while its deps hold. */\nexport function countDrawCommands(cmds: readonly DrawCommand[]): number {\n const known = counts.get(cmds);\n if (known !== undefined) return known;\n let n = 0;\n for (const cmd of cmds) n += cmd.kind === 'group' ? countDrawCommands(cmd.children) : 1;\n counts.set(cmds, n);\n return n;\n}\n\nfunction sameView(a: View | undefined, b: View): boolean {\n return a !== undefined && a.x === b.x && a.y === b.y\n && a.scale.x === b.scale.x && a.scale.y === b.scale.y;\n}\n\n/** The layer's id when the renderer should keep its pixels this frame. */\nexport function retainedId<TData>(\n layer: RenderLayer<TData>,\n cache: LayerCommandCache,\n view: View,\n policy: LayerRetainPolicy,\n): string | undefined {\n const entry = cache.get(layer.id);\n if (!entry || !layer.deps) return undefined;\n const viewHeld = (layer.space ?? 'world') === 'screen' || sameView(entry.view, view);\n entry.view = { x: view.x, y: view.y, scale: { x: view.scale.x, y: view.scale.y } };\n if (layer.retain === false || !entry.reused || !viewHeld) return undefined;\n return layer.retain === true || countDrawCommands(entry.cmds) >= policy.minCommands\n ? layer.id\n : undefined;\n}\n"],"mappings":";;;AAoBA,IAAa,uBAA0C,EAAE,aAAa,IAAI;AAE1E,IAAM,yBAAS,IAAI,QAAwC;;;AAI3D,SAAgB,kBAAkB,MAAsC;CACtE,MAAM,QAAQ,OAAO,IAAI,IAAI;CAC7B,IAAI,UAAU,QAAW,OAAO;CAChC,IAAI,IAAI;CACR,KAAK,MAAM,OAAO,MAAM,KAAK,IAAI,SAAS,UAAU,kBAAkB,IAAI,QAAQ,IAAI;CACtF,OAAO,IAAI,MAAM,CAAC;CAClB,OAAO;AACT;AAEA,SAAS,SAAS,GAAqB,GAAkB;CACvD,OAAO,MAAM,UAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAC9C,EAAE,MAAM,MAAM,EAAE,MAAM,KAAK,EAAE,MAAM,MAAM,EAAE,MAAM;AACxD;;AAGA,SAAgB,WACd,OACA,OACA,MACA,QACoB;CACpB,MAAM,QAAQ,MAAM,IAAI,MAAM,EAAE;CAChC,IAAI,CAAC,SAAS,CAAC,MAAM,MAAM,OAAO;CAClC,MAAM,YAAY,MAAM,SAAS,aAAa,YAAY,SAAS,MAAM,MAAM,IAAI;CACnF,MAAM,OAAO;EAAE,GAAG,KAAK;EAAG,GAAG,KAAK;EAAG,OAAO;GAAE,GAAG,KAAK,MAAM;GAAG,GAAG,KAAK,MAAM;EAAE;CAAE;CACjF,IAAI,MAAM,WAAW,SAAS,CAAC,MAAM,UAAU,CAAC,UAAU,OAAO;CACjE,OAAO,MAAM,WAAW,QAAQ,kBAAkB,MAAM,IAAI,KAAK,OAAO,cACpE,MAAM,KACN;AACN"}
1
+ {"version":3,"file":"retain.js","names":[],"sources":["../../../src/core/layers/retain.ts"],"sourcesContent":["import type { DrawCommand } from '@weasel-js/renderer';\nimport type { View } from 'core/viewport/view';\nimport type { LayerCommandCache, RenderLayer } from './render';\n\n/**\n * When `drawLayers` asks the renderer to keep a layer's pixels between frames\n * (`GroupDrawCommand.retain`) rather than submit its commands again.\n *\n * A layer is retained once its `deps` have held for a frame, the view's zoom\n * has held since the frame before (for a world-space layer, whose pixels move\n * with it; the renderer moves a kept surface with a pan where it can), and it\n * holds at least `minCommands` drawing commands. A layer changing\n * every frame is never retained: drawing it into its surface and compositing\n * that costs more than drawing it.\n */\nexport interface LayerRetainPolicy {\n minCommands: number;\n}\n\n/** Measured on 1280x800 at dpr 2: an unchanged layer composites cheaper than\n * it submits from about 130 to 250 commands, depending on the layer count. */\nexport const DEFAULT_LAYER_RETAIN: LayerRetainPolicy = { minCommands: 250 };\n\nconst counts = new WeakMap<readonly DrawCommand[], number>();\n\n/** Drawing commands in a tree, groups left out. Memoized by array, which a\n * layer's cache serves unchanged while its deps hold. */\nexport function countDrawCommands(cmds: readonly DrawCommand[]): number {\n const known = counts.get(cmds);\n if (known !== undefined) return known;\n let n = 0;\n for (const cmd of cmds) n += cmd.kind === 'group' ? countDrawCommands(cmd.children) : 1;\n counts.set(cmds, n);\n return n;\n}\n\n/** Whether the view held since the frame before, or only panned: a renderer\n * can move a kept surface with a pan, and decides itself when it cannot. */\nfunction sameZoom(a: View | undefined, b: View): boolean {\n return a !== undefined && a.scale.x === b.scale.x && a.scale.y === b.scale.y;\n}\n\n/** The layer's id when the renderer should keep its pixels this frame. */\nexport function retainedId<TData>(\n layer: RenderLayer<TData>,\n cache: LayerCommandCache,\n view: View,\n policy: LayerRetainPolicy,\n): string | undefined {\n const entry = cache.get(layer.id);\n if (!entry || !layer.deps) return undefined;\n const viewHeld = (layer.space ?? 'world') === 'screen' || sameZoom(entry.view, view);\n entry.view = { x: view.x, y: view.y, scale: { x: view.scale.x, y: view.scale.y } };\n if (layer.retain === false || !entry.reused || !viewHeld) return undefined;\n return layer.retain === true || countDrawCommands(entry.cmds) >= policy.minCommands\n ? layer.id\n : undefined;\n}\n"],"mappings":";;;AAqBA,IAAa,uBAA0C,EAAE,aAAa,IAAI;AAE1E,IAAM,yBAAS,IAAI,QAAwC;;;AAI3D,SAAgB,kBAAkB,MAAsC;CACtE,MAAM,QAAQ,OAAO,IAAI,IAAI;CAC7B,IAAI,UAAU,QAAW,OAAO;CAChC,IAAI,IAAI;CACR,KAAK,MAAM,OAAO,MAAM,KAAK,IAAI,SAAS,UAAU,kBAAkB,IAAI,QAAQ,IAAI;CACtF,OAAO,IAAI,MAAM,CAAC;CAClB,OAAO;AACT;;;AAIA,SAAS,SAAS,GAAqB,GAAkB;CACvD,OAAO,MAAM,UAAa,EAAE,MAAM,MAAM,EAAE,MAAM,KAAK,EAAE,MAAM,MAAM,EAAE,MAAM;AAC7E;;AAGA,SAAgB,WACd,OACA,OACA,MACA,QACoB;CACpB,MAAM,QAAQ,MAAM,IAAI,MAAM,EAAE;CAChC,IAAI,CAAC,SAAS,CAAC,MAAM,MAAM,OAAO;CAClC,MAAM,YAAY,MAAM,SAAS,aAAa,YAAY,SAAS,MAAM,MAAM,IAAI;CACnF,MAAM,OAAO;EAAE,GAAG,KAAK;EAAG,GAAG,KAAK;EAAG,OAAO;GAAE,GAAG,KAAK,MAAM;GAAG,GAAG,KAAK,MAAM;EAAE;CAAE;CACjF,IAAI,MAAM,WAAW,SAAS,CAAC,MAAM,UAAU,CAAC,UAAU,OAAO;CACjE,OAAO,MAAM,WAAW,QAAQ,kBAAkB,MAAM,IAAI,KAAK,OAAO,cACpE,MAAM,KACN;AACN"}
@@ -1,4 +1,5 @@
1
1
  import { registerOpFactory } from "./registry.js";
2
+ import { originOf } from "@weasel-js/geom";
2
3
 
3
4
  //#region src/core/ops/setPath.ts
4
5
  /**
@@ -55,6 +56,8 @@ function pathEqual(a, b) {
55
56
  if (ao.kind === "rect") return shallowEqual(a, b);
56
57
  const aa = a;
57
58
  const bb = b;
59
+ const oa = originOf(aa), ob = originOf(bb);
60
+ if (oa.x !== ob.x || oa.y !== ob.y) return false;
58
61
  if (aa.commands.length !== bb.commands.length) return false;
59
62
  if (aa.coords.length !== bb.coords.length) return false;
60
63
  for (let i = 0; i < aa.commands.length; i++) if (aa.commands[i] !== bb.commands[i]) return false;
@@ -1 +1 @@
1
- {"version":3,"file":"setPath.js","names":[],"sources":["../../../src/core/ops/setPath.ts"],"sourcesContent":["import type { Op } from './types';\nimport { registerOpFactory } from './registry';\n\n/** The geometry fields a `setPath` op replaces together — the path itself,\n * whether it is closed, and the parameters it was generated from. They move\n * as one so a parametric shape and its baked path never disagree. */\nexport interface SetPathFields {\n path: unknown; // Path | RectPath; left unknown so kit doesn't depend on features/paths/types here\n closed: boolean;\n params: unknown;\n}\n\ninterface SetPathAdapter {\n setPath(id: string, fields: SetPathFields): void;\n}\n\n/** Arguments to {@link createSetPathOp}: replace a path node's geometry fields `from` → `to`. */\nexport interface SetPathArgs {\n id: string;\n from: SetPathFields;\n to: SetPathFields;\n label?: string;\n coalesceKey?: string;\n}\n\n/**\n * Op: atomically replace a PathObj's geometric fields (path + closed + params).\n * Used for pen-edit anchor mutations and parametric-shape trapdoor conversions.\n * Reports no-op when `from` and `to` are structurally equal (same path bytes,\n * closed, params), letting history skip the entry.\n *\n * `coalesceKey` defaults to `setPath:${id}` so successive geometry writes to\n * the same node (dragging a bezier anchor) merge into one undo entry within the\n * history's coalesce window. Pass an explicit key to opt out.\n */\nexport function createSetPathOp(args: SetPathArgs): Op {\n const { id, from, to, label, coalesceKey = `setPath:${id}` } = args;\n return {\n name: 'setPath',\n args: { id, from, to, label, coalesceKey },\n label,\n coalesceKey,\n apply(adapter) {\n // Always call setPath so consumers that inspect op behavior see a\n // consistent \"this op writes (id, to)\" signal. When from and to\n // are field-equal, additionally report a no-op to history so the\n // entry can be skipped from the undo stack. setPath with the same\n // value is idempotent.\n (adapter as SetPathAdapter).setPath(id, to);\n if (fieldsEqual(from, to)) return false;\n return undefined;\n },\n invert() {\n return createSetPathOp({ id, from: to, to: from, label, coalesceKey });\n },\n };\n}\n\nregisterOpFactory<SetPathArgs>('setPath', (args) => createSetPathOp(args));\n\nfunction fieldsEqual(a: SetPathFields, b: SetPathFields): boolean {\n if (a === b) return true;\n if (a.closed !== b.closed) return false;\n if (!shallowEqual(a.params, b.params)) return false;\n return pathEqual(a.path, b.path);\n}\n\nfunction pathEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;\n const ao = a as { kind?: string };\n const bo = b as { kind?: string };\n if (ao.kind !== bo.kind) return false;\n if (ao.kind === 'rect') return shallowEqual(a, b);\n // polygon: compare commands + coords arrays\n const aa = a as { commands: ArrayLike<number>; coords: ArrayLike<number> };\n const bb = b as { commands: ArrayLike<number>; coords: ArrayLike<number> };\n if (aa.commands.length !== bb.commands.length) return false;\n if (aa.coords.length !== bb.coords.length) return false;\n for (let i = 0; i < aa.commands.length; i++) if (aa.commands[i] !== bb.commands[i]) return false;\n for (let i = 0; i < aa.coords.length; i++) if (aa.coords[i] !== bb.coords[i]) return false;\n return true;\n}\n\nfunction shallowEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a == null || b == null || typeof a !== 'object' || typeof b !== 'object') return false;\n const ka = Object.keys(a as object);\n const kb = Object.keys(b as object);\n if (ka.length !== kb.length) return false;\n for (const k of ka) {\n if ((a as Record<string, unknown>)[k] !== (b as Record<string, unknown>)[k]) return false;\n }\n return true;\n}\n"],"mappings":";;;;;;;;;;;;;AAmCA,SAAgB,gBAAgB,MAAuB;CACrD,MAAM,EAAE,IAAI,MAAM,IAAI,OAAO,cAAc,WAAW,SAAS;CAC/D,OAAO;EACL,MAAM;EACN,MAAM;GAAE;GAAI;GAAM;GAAI;GAAO;EAAY;EACzC;EACA;EACA,MAAM,SAAS;GAMb,QAA4B,QAAQ,IAAI,EAAE;GAC1C,IAAI,YAAY,MAAM,EAAE,GAAG,OAAO;EAEpC;EACA,SAAS;GACP,OAAO,gBAAgB;IAAE;IAAI,MAAM;IAAI,IAAI;IAAM;IAAO;GAAY,CAAC;EACvE;CACF;AACF;AAEA,kBAA+B,YAAY,SAAS,gBAAgB,IAAI,CAAC;AAEzE,SAAS,YAAY,GAAkB,GAA2B;CAChE,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;CAClC,IAAI,CAAC,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;CAC9C,OAAO,UAAU,EAAE,MAAM,EAAE,IAAI;AACjC;AAEA,SAAS,UAAU,GAAY,GAAqB;CAClD,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,YAAY,MAAM,QAAQ,MAAM,MAAM,OAAO;CACvF,MAAM,KAAK;CACX,MAAM,KAAK;CACX,IAAI,GAAG,SAAS,GAAG,MAAM,OAAO;CAChC,IAAI,GAAG,SAAS,QAAQ,OAAO,aAAa,GAAG,CAAC;CAEhD,MAAM,KAAK;CACX,MAAM,KAAK;CACX,IAAI,GAAG,SAAS,WAAW,GAAG,SAAS,QAAQ,OAAO;CACtD,IAAI,GAAG,OAAO,WAAW,GAAG,OAAO,QAAQ,OAAO;CAClD,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,SAAS,QAAQ,KAAK,IAAI,GAAG,SAAS,OAAO,GAAG,SAAS,IAAI,OAAO;CAC3F,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,OAAO,QAAQ,KAAK,IAAI,GAAG,OAAO,OAAO,GAAG,OAAO,IAAI,OAAO;CACrF,OAAO;AACT;AAEA,SAAS,aAAa,GAAY,GAAqB;CACrD,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,KAAK,QAAQ,KAAK,QAAQ,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU,OAAO;CACrF,MAAM,KAAK,OAAO,KAAK,CAAW;CAClC,MAAM,KAAK,OAAO,KAAK,CAAW;CAClC,IAAI,GAAG,WAAW,GAAG,QAAQ,OAAO;CACpC,KAAK,MAAM,KAAK,IACd,IAAK,EAA8B,OAAQ,EAA8B,IAAI,OAAO;CAEtF,OAAO;AACT"}
1
+ {"version":3,"file":"setPath.js","names":[],"sources":["../../../src/core/ops/setPath.ts"],"sourcesContent":["import type { Op } from './types';\nimport { registerOpFactory } from './registry';\nimport { originOf, type PolygonPath } from '@weasel-js/geom';\n\n/** The geometry fields a `setPath` op replaces together — the path itself,\n * whether it is closed, and the parameters it was generated from. They move\n * as one so a parametric shape and its baked path never disagree. */\nexport interface SetPathFields {\n path: unknown; // Path | RectPath; left unknown so kit doesn't depend on domains/paths/types here\n closed: boolean;\n params: unknown;\n}\n\ninterface SetPathAdapter {\n setPath(id: string, fields: SetPathFields): void;\n}\n\n/** Arguments to {@link createSetPathOp}: replace a path node's geometry fields `from` → `to`. */\nexport interface SetPathArgs {\n id: string;\n from: SetPathFields;\n to: SetPathFields;\n label?: string;\n coalesceKey?: string;\n}\n\n/**\n * Op: atomically replace a PathObj's geometric fields (path + closed + params).\n * Used for pen-edit anchor mutations and parametric-shape trapdoor conversions.\n * Reports no-op when `from` and `to` are structurally equal (same path bytes,\n * closed, params), letting history skip the entry.\n *\n * `coalesceKey` defaults to `setPath:${id}` so successive geometry writes to\n * the same node (dragging a bezier anchor) merge into one undo entry within the\n * history's coalesce window. Pass an explicit key to opt out.\n */\nexport function createSetPathOp(args: SetPathArgs): Op {\n const { id, from, to, label, coalesceKey = `setPath:${id}` } = args;\n return {\n name: 'setPath',\n args: { id, from, to, label, coalesceKey },\n label,\n coalesceKey,\n apply(adapter) {\n // Always call setPath so consumers that inspect op behavior see a\n // consistent \"this op writes (id, to)\" signal. When from and to\n // are field-equal, additionally report a no-op to history so the\n // entry can be skipped from the undo stack. setPath with the same\n // value is idempotent.\n (adapter as SetPathAdapter).setPath(id, to);\n if (fieldsEqual(from, to)) return false;\n return undefined;\n },\n invert() {\n return createSetPathOp({ id, from: to, to: from, label, coalesceKey });\n },\n };\n}\n\nregisterOpFactory<SetPathArgs>('setPath', (args) => createSetPathOp(args));\n\nfunction fieldsEqual(a: SetPathFields, b: SetPathFields): boolean {\n if (a === b) return true;\n if (a.closed !== b.closed) return false;\n if (!shallowEqual(a.params, b.params)) return false;\n return pathEqual(a.path, b.path);\n}\n\nfunction pathEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;\n const ao = a as { kind?: string };\n const bo = b as { kind?: string };\n if (ao.kind !== bo.kind) return false;\n if (ao.kind === 'rect') return shallowEqual(a, b);\n // polygon: compare origin, commands and coords\n const aa = a as PolygonPath;\n const bb = b as PolygonPath;\n const oa = originOf(aa), ob = originOf(bb);\n if (oa.x !== ob.x || oa.y !== ob.y) return false;\n if (aa.commands.length !== bb.commands.length) return false;\n if (aa.coords.length !== bb.coords.length) return false;\n for (let i = 0; i < aa.commands.length; i++) if (aa.commands[i] !== bb.commands[i]) return false;\n for (let i = 0; i < aa.coords.length; i++) if (aa.coords[i] !== bb.coords[i]) return false;\n return true;\n}\n\nfunction shallowEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a == null || b == null || typeof a !== 'object' || typeof b !== 'object') return false;\n const ka = Object.keys(a as object);\n const kb = Object.keys(b as object);\n if (ka.length !== kb.length) return false;\n for (const k of ka) {\n if ((a as Record<string, unknown>)[k] !== (b as Record<string, unknown>)[k]) return false;\n }\n return true;\n}\n"],"mappings":";;;;;;;;;;;;;;AAoCA,SAAgB,gBAAgB,MAAuB;CACrD,MAAM,EAAE,IAAI,MAAM,IAAI,OAAO,cAAc,WAAW,SAAS;CAC/D,OAAO;EACL,MAAM;EACN,MAAM;GAAE;GAAI;GAAM;GAAI;GAAO;EAAY;EACzC;EACA;EACA,MAAM,SAAS;GAMb,QAA4B,QAAQ,IAAI,EAAE;GAC1C,IAAI,YAAY,MAAM,EAAE,GAAG,OAAO;EAEpC;EACA,SAAS;GACP,OAAO,gBAAgB;IAAE;IAAI,MAAM;IAAI,IAAI;IAAM;IAAO;GAAY,CAAC;EACvE;CACF;AACF;AAEA,kBAA+B,YAAY,SAAS,gBAAgB,IAAI,CAAC;AAEzE,SAAS,YAAY,GAAkB,GAA2B;CAChE,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;CAClC,IAAI,CAAC,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;CAC9C,OAAO,UAAU,EAAE,MAAM,EAAE,IAAI;AACjC;AAEA,SAAS,UAAU,GAAY,GAAqB;CAClD,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,YAAY,MAAM,QAAQ,MAAM,MAAM,OAAO;CACvF,MAAM,KAAK;CACX,MAAM,KAAK;CACX,IAAI,GAAG,SAAS,GAAG,MAAM,OAAO;CAChC,IAAI,GAAG,SAAS,QAAQ,OAAO,aAAa,GAAG,CAAC;CAEhD,MAAM,KAAK;CACX,MAAM,KAAK;CACX,MAAM,KAAK,SAAS,EAAE,GAAG,KAAK,SAAS,EAAE;CACzC,IAAI,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,GAAG,OAAO;CAC3C,IAAI,GAAG,SAAS,WAAW,GAAG,SAAS,QAAQ,OAAO;CACtD,IAAI,GAAG,OAAO,WAAW,GAAG,OAAO,QAAQ,OAAO;CAClD,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,SAAS,QAAQ,KAAK,IAAI,GAAG,SAAS,OAAO,GAAG,SAAS,IAAI,OAAO;CAC3F,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,OAAO,QAAQ,KAAK,IAAI,GAAG,OAAO,OAAO,GAAG,OAAO,IAAI,OAAO;CACrF,OAAO;AACT;AAEA,SAAS,aAAa,GAAY,GAAqB;CACrD,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,KAAK,QAAQ,KAAK,QAAQ,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU,OAAO;CACrF,MAAM,KAAK,OAAO,KAAK,CAAW;CAClC,MAAM,KAAK,OAAO,KAAK,CAAW;CAClC,IAAI,GAAG,WAAW,GAAG,QAAQ,OAAO;CACpC,KAAK,MAAM,KAAK,IACd,IAAK,EAA8B,OAAQ,EAA8B,IAAI,OAAO;CAEtF,OAAO;AACT"}